Convert gr-change-list to lit
Change-Id: I47d7e148f1f822ac91b1d4c404270ec27036fd41
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 0621cc2..898de14 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -97,7 +97,6 @@
"elements/admin/gr-permission/gr-permission_html.ts",
"elements/admin/gr-repo-access/gr-repo-access_html.ts",
"elements/admin/gr-rule-editor/gr-rule-editor_html.ts",
- "elements/change-list/gr-change-list/gr-change-list_html.ts",
"elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts",
"elements/change/gr-change-actions/gr-change-actions_html.ts",
"elements/change/gr-change-metadata/gr-change-metadata_html.ts",
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index 2167d31..142abaa 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -189,15 +189,12 @@
.preferences=${this.preferences}
.selectedIndex=${this.viewState.selectedChangeIndex}
.showStar=${loggedIn}
- @selected-index-changed=${(
- e: ValueChangedEvent<ChangeListViewState>
- ) => {
+ @selected-index-changed=${(e: ValueChangedEvent<number>) => {
this.handleSelectedIndexChanged(e);
}}
@toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
this.handleToggleStar(e);
}}
- .observerTarget=${this}
></gr-change-list>
${this.renderChangeListViewNav()}
</div>
@@ -416,11 +413,9 @@
);
}
- private handleSelectedIndexChanged(
- e: ValueChangedEvent<ChangeListViewState>
- ) {
+ private handleSelectedIndexChanged(e: ValueChangedEvent<number>) {
if (!this.viewState) return;
- this.viewState.selectedChangeIndex = Number(e.detail.value);
+ this.viewState.selectedChangeIndex = e.detail.value;
fire(this, 'view-state-changed', {value: this.viewState});
}
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 7f9a9fd..1185fe8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -15,31 +15,19 @@
* limitations under the License.
*/
-import '../../../styles/gr-change-list-styles';
-import '../../../styles/gr-font-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-cursor-manager/gr-cursor-manager';
import '../gr-change-list-item/gr-change-list-item';
+import {GrChangeListItem} from '../gr-change-list-item/gr-change-list-item';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
-import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-change-list_html';
import {getAppContext} from '../../../services/app-context';
import {
- KeyboardShortcutMixin,
- Shortcut,
- ShortcutListener,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
-import {
GerritNav,
- DashboardSection,
YOUR_TURN,
CLOSED,
} from '../../core/gr-navigation/gr-navigation';
import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {isOwner} from '../../../utils/change-util';
-import {customElement, property, observe} from '@polymer/decorators';
import {GrCursorManager} from '../../shared/gr-cursor-manager/gr-cursor-manager';
import {
AccountInfo,
@@ -48,15 +36,22 @@
PreferencesInput,
} from '../../../types/common';
import {hasAttention} from '../../../utils/attention-set-util';
-import {fireEvent, fireReload} from '../../../utils/event-util';
+import {fire, fireEvent, fireReload} from '../../../utils/event-util';
import {ScrollMode} from '../../../constants/constants';
-import {listen} from '../../../services/shortcuts/shortcuts-service';
import {
getRequirements,
showNewSubmitRequirements,
} from '../../../utils/label-util';
import {addGlobalShortcut, Key} from '../../../utils/dom-util';
import {unique} from '../../../utils/common-util';
+import {changeListStyles} from '../../../styles/gr-change-list-styles';
+import {fontStyles} from '../../../styles/gr-font-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, html, css} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {ShortcutController} from '../../lit/shortcut-controller';
+import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {queryAll} from '../../../utils/common-util';
import {ValueChangedEvent} from '../../../types/events';
const NUMBER_FIXED_COLUMNS = 3;
@@ -79,24 +74,15 @@
];
export interface ChangeListSection {
+ countLabel?: string;
+ isOutgoing?: boolean;
name?: string;
query?: string;
results: ChangeInfo[];
}
-export interface GrChangeList {
- $: {};
-}
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = KeyboardShortcutMixin(PolymerElement);
-
@customElement('gr-change-list')
-export class GrChangeList extends base {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrChangeList extends LitElement {
/**
* Fired when next page key shortcut was pressed.
*
@@ -116,7 +102,7 @@
@property({type: Object})
account: AccountInfo | undefined = undefined;
- @property({type: Array, observer: '_changesChanged'})
+ @property({type: Array})
changes?: ChangeInfo[];
/**
@@ -126,13 +112,9 @@
@property({type: Array})
sections: ChangeListSection[] = [];
- @property({type: Array, computed: '_computeLabelNames(sections)'})
- labelNames?: string[];
+ @state() private dynamicHeaderEndpoints?: string[];
- @property({type: Array})
- _dynamicHeaderEndpoints?: string[];
-
- @property({type: Number, notify: true})
+ @property({type: Number})
selectedIndex?: number;
@property({type: Boolean})
@@ -156,24 +138,14 @@
@property({type: Boolean})
isCursorMoving = false;
- @property({type: Object})
- _config?: ServerInfo;
+ // private but used in test
+ @state() config?: ServerInfo;
private readonly flagsService = getAppContext().flagsService;
private readonly restApiService = getAppContext().restApiService;
- override keyboardShortcuts(): ShortcutListener[] {
- return [
- listen(Shortcut.CURSOR_NEXT_CHANGE, _ => this._nextChange()),
- listen(Shortcut.CURSOR_PREV_CHANGE, _ => this._prevChange()),
- listen(Shortcut.NEXT_PAGE, _ => this._nextPage()),
- listen(Shortcut.PREV_PAGE, _ => this._prevPage()),
- listen(Shortcut.OPEN_CHANGE, _ => this.openChange()),
- listen(Shortcut.TOGGLE_CHANGE_STAR, _ => this._toggleChangeStar()),
- listen(Shortcut.REFRESH_CHANGE_LIST, _ => this._refreshChangeList()),
- ];
- }
+ private readonly shortcuts = new ShortcutController(this);
private cursor = new GrCursorManager();
@@ -181,22 +153,33 @@
super();
this.cursor.scrollMode = ScrollMode.KEEP_VISIBLE;
this.cursor.focusOnMove = true;
+ this.shortcuts.addAbstract(Shortcut.CURSOR_NEXT_CHANGE, () =>
+ this.nextChange()
+ );
+ this.shortcuts.addAbstract(Shortcut.CURSOR_PREV_CHANGE, () =>
+ this.prevChange()
+ );
+ this.shortcuts.addAbstract(Shortcut.NEXT_PAGE, () => this.nextPage());
+ this.shortcuts.addAbstract(Shortcut.PREV_PAGE, () => this.prevPage());
+ this.shortcuts.addAbstract(Shortcut.OPEN_CHANGE, () => this.openChange());
+ this.shortcuts.addAbstract(Shortcut.TOGGLE_CHANGE_STAR, () =>
+ this.toggleChangeStar()
+ );
+ this.shortcuts.addAbstract(Shortcut.REFRESH_CHANGE_LIST, () =>
+ this.refreshChangeList()
+ );
addGlobalShortcut({key: Key.ENTER}, () => this.openChange());
}
- override ready() {
- super.ready();
- this.restApiService.getConfig().then(config => {
- this._config = config;
- });
- }
-
override connectedCallback() {
super.connectedCallback();
+ this.restApiService.getConfig().then(config => {
+ this.config = config;
+ });
getPluginLoader()
.awaitPluginsLoaded()
.then(() => {
- this._dynamicHeaderEndpoints =
+ this.dynamicHeaderEndpoints =
getPluginEndpoints().getDynamicEndpoints('change-list-header');
});
}
@@ -206,37 +189,259 @@
super.disconnectedCallback();
}
- _lowerCase(column: string) {
- return column.toLowerCase();
+ static override get styles() {
+ return [
+ changeListStyles,
+ fontStyles,
+ sharedStyles,
+ css`
+ #changeList {
+ border-collapse: collapse;
+ width: 100%;
+ }
+ .section-count-label {
+ color: var(--deemphasized-text-color);
+ font-family: var(--font-family);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-normal);
+ line-height: var(--line-height-small);
+ }
+ a.section-title:hover {
+ text-decoration: none;
+ }
+ a.section-title:hover .section-count-label {
+ text-decoration: none;
+ }
+ a.section-title:hover .section-name {
+ text-decoration: underline;
+ }
+ `,
+ ];
}
- @observe('account', 'preferences', '_config', 'sections')
- _computePreferences(
- account?: AccountInfo,
- preferences?: PreferencesInput,
- config?: ServerInfo,
- sections?: ChangeListSection[]
+ override render() {
+ const labelNames = this.computeLabelNames(this.sections);
+ return html`
+ <table id="changeList">
+ ${this.sections.map((changeSection, sectionIndex) =>
+ this.renderSections(changeSection, sectionIndex, labelNames)
+ )}
+ </table>
+ `;
+ }
+
+ private renderSections(
+ changeSection: ChangeListSection,
+ sectionIndex: number,
+ labelNames: string[]
) {
- if (!config) {
- return;
+ return html`
+ ${this.renderSectionHeader(changeSection, labelNames)}
+ <tbody class="groupContent">
+ ${this.isEmpty(changeSection)
+ ? this.renderNoChangesRow(changeSection, labelNames)
+ : this.renderColumnHeaders(changeSection, labelNames)}
+ ${changeSection.results.map((change, index) =>
+ this.renderChangeRow(
+ changeSection,
+ change,
+ index,
+ sectionIndex,
+ labelNames
+ )
+ )}
+ </tbody>
+ `;
+ }
+
+ private renderSectionHeader(
+ changeSection: ChangeListSection,
+ labelNames: string[]
+ ) {
+ if (!changeSection.name) return;
+
+ return html`
+ <tbody>
+ <tr class="groupHeader">
+ <td aria-hidden="true" class="leftPadding"></td>
+ <td aria-hidden="true" class="star" ?hidden=${!this.showStar}></td>
+ <td
+ class="cell"
+ colspan="${this.computeColspan(changeSection, labelNames)}"
+ >
+ <h2 class="heading-3">
+ <a
+ href="${this.sectionHref(changeSection.query)}"
+ class="section-title"
+ >
+ <span class="section-name">${changeSection.name}</span>
+ <span class="section-count-label"
+ >${changeSection.countLabel}</span
+ >
+ </a>
+ </h2>
+ </td>
+ </tr>
+ </tbody>
+ `;
+ }
+
+ private renderNoChangesRow(
+ changeSection: ChangeListSection,
+ labelNames: string[]
+ ) {
+ return html`
+ <tr class="noChanges">
+ <td class="leftPadding" ?aria-hidden="true"></td>
+ <td
+ class="star"
+ ?aria-hidden=${!this.showStar}
+ ?hidden=${!this.showStar}
+ ></td>
+ <td
+ class="cell"
+ colspan="${this.computeColspan(changeSection, labelNames)}"
+ >
+ ${this.getSpecialEmptySlot(changeSection)
+ ? html`<slot
+ name="${this.getSpecialEmptySlot(changeSection)}"
+ ></slot>`
+ : 'No changes'}
+ </td>
+ </tr>
+ `;
+ }
+
+ private renderColumnHeaders(
+ changeSection: ChangeListSection,
+ labelNames: string[]
+ ) {
+ return html`
+ <tr class="groupTitle">
+ <td class="leftPadding" ?aria-hidden="true"></td>
+ <td
+ class="star"
+ aria-label="Star status column"
+ ?hidden=${!this.showStar}
+ ></td>
+ <td class="number" ?hidden=${!this.showNumber}>#</td>
+ ${this.computeColumns(changeSection).map(item =>
+ this.renderHeaderCell(item)
+ )}
+ ${labelNames?.map(labelName => this.renderLabelHeader(labelName))}
+ ${this.dynamicHeaderEndpoints?.map(pluginHeader =>
+ this.renderEndpointHeader(pluginHeader)
+ )}
+ </tr>
+ `;
+ }
+
+ private renderHeaderCell(item: string) {
+ return html`<td class="${item.toLowerCase()}">${item}</td>`;
+ }
+
+ private renderLabelHeader(labelName: string) {
+ return html`
+ <td class="label" title="${labelName}">
+ ${this.computeLabelShortcut(labelName)}
+ </td>
+ `;
+ }
+
+ private renderEndpointHeader(pluginHeader: string) {
+ return html`
+ <td class="endpoint">
+ <gr-endpoint-decorator .name="${pluginHeader}"></gr-endpoint-decorator>
+ </td>
+ `;
+ }
+
+ private renderChangeRow(
+ changeSection: ChangeListSection,
+ change: ChangeInfo,
+ index: number,
+ sectionIndex: number,
+ labelNames: string[]
+ ) {
+ const ariaLabel = this.computeAriaLabel(change, changeSection.name);
+ const highlight = this.computeItemHighlight(
+ this.account,
+ change,
+ changeSection.name
+ );
+ const selected = this.computeItemSelected(
+ sectionIndex,
+ index,
+ this.selectedIndex
+ );
+ const tabindex = this.computeTabIndex(
+ sectionIndex,
+ index,
+ this.isCursorMoving,
+ this.selectedIndex
+ );
+ const visibleChangeTableColumns = this.computeColumns(changeSection);
+ return html`
+ <gr-change-list-item
+ .account=${this.account}
+ ?selected=${selected}
+ .highlight=${highlight}
+ .change=${change}
+ .config=${this.config}
+ .sectionName=${changeSection.name}
+ .visibleChangeTableColumns=${visibleChangeTableColumns}
+ .showNumber=${this.showNumber}
+ .showStar=${this.showStar}
+ ?tabindex=${tabindex}
+ .labelNames=${labelNames}
+ aria-label=${ariaLabel}
+ ></gr-change-list-item>
+ `;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (
+ changedProperties.has('account') ||
+ changedProperties.has('preferences') ||
+ changedProperties.has('config') ||
+ changedProperties.has('sections')
+ ) {
+ this.computePreferences();
}
- const changes = (sections ?? []).map(section => section.results).flat();
+ if (changedProperties.has('changes')) {
+ this.changesChanged();
+ }
+ }
+
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('sections')) {
+ this.sectionsChanged();
+ }
+ }
+
+ private computePreferences() {
+ if (!this.config) return;
+
+ const changes = (this.sections ?? [])
+ .map(section => section.results)
+ .flat();
this.changeTableColumns = columnNames;
this.showNumber = false;
this.visibleChangeTableColumns = this.changeTableColumns.filter(col =>
- this._isColumnEnabled(col, config, changes)
+ this._isColumnEnabled(col, this.config, changes)
);
- if (account && preferences) {
- this.showNumber = !!(
- preferences && preferences.legacycid_in_change_table
- );
- if (preferences.change_table && preferences.change_table.length > 0) {
- const prefColumns = preferences.change_table.map(column =>
+ if (this.account && this.preferences) {
+ this.showNumber = !!this.preferences?.legacycid_in_change_table;
+ if (
+ this.preferences?.change_table &&
+ this.preferences.change_table.length > 0
+ ) {
+ const prefColumns = this.preferences.change_table.map(column =>
column === 'Project' ? 'Repo' : column
);
this.visibleChangeTableColumns = prefColumns.filter(col =>
- this._isColumnEnabled(col, config, changes)
+ this._isColumnEnabled(col, this.config, changes)
);
}
}
@@ -271,12 +476,9 @@
*
* @param visibleColumns are the columns according to configs and user prefs
*/
- _computeColumns(
- section?: ChangeListSection,
- visibleColumns?: string[]
- ): string[] {
- if (!section || !visibleColumns) return [];
- const cols = [...visibleColumns];
+ private computeColumns(section?: ChangeListSection): string[] {
+ if (!section || !this.visibleChangeTableColumns) return [];
+ const cols = [...this.visibleChangeTableColumns];
const updatedIndex = cols.indexOf('Updated');
if (section.name === YOUR_TURN.name && updatedIndex !== -1) {
cols[updatedIndex] = 'Waiting';
@@ -287,20 +489,16 @@
return cols;
}
- _computeColspan(
- section?: ChangeListSection,
- visibleColumns?: string[],
- labelNames?: string[]
- ) {
- const cols = this._computeColumns(section, visibleColumns);
+ // private but used in test
+ computeColspan(section?: ChangeListSection, labelNames?: string[]) {
+ const cols = this.computeColumns(section);
if (!cols || !labelNames) return 1;
return cols.length + labelNames.length + NUMBER_FIXED_COLUMNS;
}
- _computeLabelNames(sections: ChangeListSection[]) {
- if (!sections) {
- return [];
- }
+ // private but used in test
+ computeLabelNames(sections: ChangeListSection[]) {
+ if (!sections) return [];
let labels: string[] = [];
const nonExistingLabel = function (item: string) {
return !labels.includes(item);
@@ -332,7 +530,8 @@
return labels.sort();
}
- _computeLabelShortcut(labelName: string) {
+ // private but used in test
+ computeLabelShortcut(labelName: string) {
if (labelName.startsWith(LABEL_PREFIX_INVALID_PROLOG)) {
labelName = labelName.slice(LABEL_PREFIX_INVALID_PROLOG.length);
}
@@ -347,11 +546,12 @@
.slice(0, MAX_SHORTCUT_CHARS);
}
- _changesChanged(changes: ChangeInfo[]) {
- this.sections = changes ? [{results: changes}] : [];
+ private changesChanged() {
+ this.sections = this.changes ? [{results: this.changes}] : [];
}
- _processQuery(query: string) {
+ // private but used in test
+ processQuery(query: string) {
let tokens = query.split(' ');
const invalidTokens = ['limit:', 'age:', '-age:'];
tokens = tokens.filter(
@@ -361,19 +561,22 @@
return tokens.join(' ');
}
- _sectionHref(query: string) {
- return GerritNav.getUrlForSearchQuery(this._processQuery(query));
+ private sectionHref(query?: string) {
+ if (!query) return;
+ return GerritNav.getUrlForSearchQuery(this.processQuery(query));
}
/**
* Maps an index local to a particular section to the absolute index
* across all the changes on the page.
*
+ * private but used in test
+ *
* @param sectionIndex index of section
* @param localIndex index of row within section
* @return absolute index of row in the aggregate dashboard
*/
- _computeItemAbsoluteIndex(sectionIndex: number, localIndex: number) {
+ computeItemAbsoluteIndex(sectionIndex: number, localIndex: number) {
let idx = 0;
for (let i = 0; i < sectionIndex; i++) {
idx += this.sections[i].results.length;
@@ -381,28 +584,28 @@
return idx + localIndex;
}
- _computeItemSelected(
+ private computeItemSelected(
sectionIndex: number,
index: number,
- selectedIndex: number
+ selectedIndex?: number
) {
- const idx = this._computeItemAbsoluteIndex(sectionIndex, index);
+ const idx = this.computeItemAbsoluteIndex(sectionIndex, index);
return idx === selectedIndex;
}
- _computeTabIndex(
+ private computeTabIndex(
sectionIndex: number,
index: number,
- selectedIndex: number,
- isCursorMoving: boolean
+ isCursorMoving: boolean,
+ selectedIndex?: number
) {
if (isCursorMoving) return 0;
- return this._computeItemSelected(sectionIndex, index, selectedIndex)
+ return this.computeItemSelected(sectionIndex, index, selectedIndex)
? 0
: undefined;
}
- _computeItemHighlight(
+ private computeItemHighlight(
account?: AccountInfo,
change?: ChangeInfo,
sectionName?: string
@@ -416,48 +619,45 @@
);
}
- _nextChange() {
+ private nextChange() {
this.isCursorMoving = true;
this.cursor.next();
this.isCursorMoving = false;
this.selectedIndex = this.cursor.index;
+ fire(this, 'selected-index-changed', {value: this.cursor.index});
}
- _prevChange() {
+ private prevChange() {
this.isCursorMoving = true;
this.cursor.previous();
this.isCursorMoving = false;
this.selectedIndex = this.cursor.index;
+ fire(this, 'selected-index-changed', {value: this.cursor.index});
}
- openChange() {
- const change = this._changeForIndex(this.selectedIndex);
+ private openChange() {
+ const change = this.changeForIndex(this.selectedIndex);
if (change) GerritNav.navigateToChange(change);
}
- _nextPage() {
+ private nextPage() {
fireEvent(this, 'next-page');
}
- _prevPage() {
- this.dispatchEvent(
- new CustomEvent('previous-page', {
- composed: true,
- bubbles: true,
- })
- );
+ private prevPage() {
+ fireEvent(this, 'previous-page');
}
- _refreshChangeList() {
+ private refreshChangeList() {
fireReload(this);
}
- _toggleChangeStar() {
- this._toggleStarForIndex(this.selectedIndex);
+ private toggleChangeStar() {
+ this.toggleStarForIndex(this.selectedIndex);
}
- _toggleStarForIndex(index?: number) {
- const changeEls = this._getListItems();
+ private toggleStarForIndex(index?: number) {
+ const changeEls = this.getListItems();
if (index === undefined || index >= changeEls.length || !changeEls[index]) {
return;
}
@@ -467,40 +667,38 @@
if (grChangeStar) grChangeStar.toggleStar();
}
- _changeForIndex(index?: number) {
- const changeEls = this._getListItems();
+ private changeForIndex(index?: number) {
+ const changeEls = this.getListItems();
if (index !== undefined && index < changeEls.length && changeEls[index]) {
return changeEls[index].change;
}
return null;
}
- _getListItems() {
- const items = this.root?.querySelectorAll('gr-change-list-item');
+ private getListItems() {
+ const items = queryAll<GrChangeListItem>(this, 'gr-change-list-item');
return !items ? [] : Array.from(items);
}
- @observe('sections.*')
- _sectionsChanged() {
- // Flush DOM operations so that the list item elements will be loaded.
- afterNextRender(this, () => {
- this.cursor.stops = this._getListItems();
- this.cursor.moveToStart();
- if (this.selectedIndex) this.cursor.setCursorAtIndex(this.selectedIndex);
- });
+ private sectionsChanged() {
+ this.cursor.stops = this.getListItems();
+ this.cursor.moveToStart();
+ if (this.selectedIndex) this.cursor.setCursorAtIndex(this.selectedIndex);
}
- _getSpecialEmptySlot(section: DashboardSection) {
+ // private but used in test
+ getSpecialEmptySlot(section: ChangeListSection) {
if (section.isOutgoing) return 'empty-outgoing';
if (section.name === YOUR_TURN.name) return 'empty-your-turn';
return '';
}
- _isEmpty(section: DashboardSection) {
+ // private but used in test
+ isEmpty(section: ChangeListSection) {
return !section.results?.length;
}
- _computeAriaLabel(change?: ChangeInfo, sectionName?: string) {
+ private computeAriaLabel(change?: ChangeInfo, sectionName?: string) {
if (!change) return '';
return change.subject + (sectionName ? `, section: ${sectionName}` : '');
}
@@ -508,7 +706,7 @@
declare global {
interface HTMLElementEventMap {
- 'selected-index-changed': ValueChangedEvent;
+ 'selected-index-changed': ValueChangedEvent<number>;
}
interface HTMLElementTagNameMap {
'gr-change-list': GrChangeList;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
deleted file mode 100644
index 77320b9..0000000
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 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.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-font-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-change-list-styles">
- #changeList {
- border-collapse: collapse;
- width: 100%;
- }
- .section-count-label {
- color: var(--deemphasized-text-color);
- font-family: var(--font-family);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-normal);
- line-height: var(--line-height-small);
- }
- a.section-title:hover {
- text-decoration: none;
- }
- a.section-title:hover .section-count-label {
- text-decoration: none;
- }
- a.section-title:hover .section-name {
- text-decoration: underline;
- }
- </style>
- <table id="changeList">
- <template
- is="dom-repeat"
- items="[[sections]]"
- as="changeSection"
- index-as="sectionIndex"
- >
- <template is="dom-if" if="[[changeSection.name]]">
- <tbody>
- <tr class="groupHeader">
- <td aria-hidden="true" class="leftPadding"></td>
- <td
- aria-hidden="true"
- class="star"
- hidden$="[[!showStar]]"
- hidden=""
- ></td>
- <td
- class="cell"
- colspan$="[[_computeColspan(changeSection, visibleChangeTableColumns, labelNames)]]"
- >
- <h2 class="heading-3">
- <a
- href$="[[_sectionHref(changeSection.query)]]"
- class="section-title"
- >
- <span class="section-name">[[changeSection.name]]</span>
- <span class="section-count-label"
- >[[changeSection.countLabel]]</span
- >
- </a>
- </h2>
- </td>
- </tr>
- </tbody>
- </template>
- <tbody class="groupContent">
- <template is="dom-if" if="[[_isEmpty(changeSection)]]">
- <tr class="noChanges">
- <td aria-hidden="true" class="leftPadding"></td>
- <td
- aria-hidden="[[!showStar]]"
- class="star"
- hidden$="[[!showStar]]"
- ></td>
- <td
- class="cell"
- colspan$="[[_computeColspan(changeSection, visibleChangeTableColumns, labelNames)]]"
- >
- <template
- is="dom-if"
- if="[[_getSpecialEmptySlot(changeSection)]]"
- >
- <slot name="[[_getSpecialEmptySlot(changeSection)]]"></slot>
- </template>
- <template
- is="dom-if"
- if="[[!_getSpecialEmptySlot(changeSection)]]"
- >
- No changes
- </template>
- </td>
- </tr>
- </template>
- <template is="dom-if" if="[[!_isEmpty(changeSection)]]">
- <tr class="groupTitle">
- <td aria-hidden="true" class="leftPadding"></td>
- <td
- aria-label="Star status column"
- class="star"
- hidden$="[[!showStar]]"
- hidden=""
- ></td>
- <td class="number" hidden$="[[!showNumber]]" hidden="">#</td>
- <template
- is="dom-repeat"
- items="[[_computeColumns(changeSection, visibleChangeTableColumns)]]"
- as="item"
- >
- <td class$="[[_lowerCase(item)]]">[[item]]</td>
- </template>
- <template is="dom-repeat" items="[[labelNames]]" as="labelName">
- <td class="label" title$="[[labelName]]">
- [[_computeLabelShortcut(labelName)]]
- </td>
- </template>
- <template
- is="dom-repeat"
- items="[[_dynamicHeaderEndpoints]]"
- as="pluginHeader"
- >
- <td class="endpoint">
- <gr-endpoint-decorator name$="[[pluginHeader]]">
- </gr-endpoint-decorator>
- </td>
- </template>
- </tr>
- </template>
- <template is="dom-repeat" items="[[changeSection.results]]" as="change">
- <gr-change-list-item
- account="[[account]]"
- selected$="[[_computeItemSelected(sectionIndex, index, selectedIndex)]]"
- highlight$="[[_computeItemHighlight(account, change, changeSection.name)]]"
- change="[[change]]"
- config="[[_config]]"
- section-name="[[changeSection.name]]"
- visible-change-table-columns="[[_computeColumns(changeSection, visibleChangeTableColumns)]]"
- show-number="[[showNumber]]"
- show-star="[[showStar]]"
- tabindex$="[[_computeTabIndex(sectionIndex, index, selectedIndex, isCursorMoving)]]"
- label-names="[[labelNames]]"
- aria-label$="[[_computeAriaLabel(change, changeSection.name)]]"
- ></gr-change-list-item>
- </template>
- </tbody>
- </template>
- </table>
-`;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
index ee15b44..50708c0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
@@ -17,10 +17,8 @@
import '../../../test/common-test-setup-karma';
import './gr-change-list';
import {GrChangeList} from './gr-change-list';
-import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {
- mockPromise,
pressKey,
query,
queryAll,
@@ -47,11 +45,12 @@
});
suite('test show change number not logged in', () => {
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.account = undefined;
element.preferences = undefined;
- element._config = createServerInfo();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number disabled', () => {
@@ -60,7 +59,7 @@
});
suite('test show change number preference enabled', () => {
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.preferences = {
legacycid_in_change_table: true,
@@ -68,8 +67,8 @@
change_table: [],
};
element.account = {_account_id: 1001 as AccountId};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number enabled', () => {
@@ -78,7 +77,7 @@
});
suite('test show change number preference disabled', () => {
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
// legacycid_in_change_table is not set when false.
element.preferences = {
@@ -86,8 +85,8 @@
change_table: [],
};
element.account = {_account_id: 1001 as AccountId};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number disabled', () => {
@@ -97,7 +96,7 @@
test('computed fields', () => {
assert.equal(
- element._computeLabelNames([
+ element.computeLabelNames([
{
results: [
{...createChange(), _number: 0 as NumericChangeId, labels: {}},
@@ -107,7 +106,7 @@
0
);
assert.equal(
- element._computeLabelNames([
+ element.computeLabelNames([
{
results: [
{
@@ -137,49 +136,43 @@
3
);
- assert.equal(element._computeLabelShortcut('Code-Review'), 'CR');
- assert.equal(element._computeLabelShortcut('Verified'), 'V');
- assert.equal(element._computeLabelShortcut('Library-Compliance'), 'LC');
- assert.equal(element._computeLabelShortcut('PolyGerrit-Review'), 'PR');
- assert.equal(element._computeLabelShortcut('polygerrit-review'), 'PR');
+ assert.equal(element.computeLabelShortcut('Code-Review'), 'CR');
+ assert.equal(element.computeLabelShortcut('Verified'), 'V');
+ assert.equal(element.computeLabelShortcut('Library-Compliance'), 'LC');
+ assert.equal(element.computeLabelShortcut('PolyGerrit-Review'), 'PR');
+ assert.equal(element.computeLabelShortcut('polygerrit-review'), 'PR');
assert.equal(
- element._computeLabelShortcut(
- 'Invalid-Prolog-Rules-Label-Name--Verified'
- ),
+ element.computeLabelShortcut('Invalid-Prolog-Rules-Label-Name--Verified'),
'V'
);
- assert.equal(element._computeLabelShortcut('Some-Special-Label-7'), 'SSL7');
+ assert.equal(element.computeLabelShortcut('Some-Special-Label-7'), 'SSL7');
assert.equal(
- element._computeLabelShortcut('--Too----many----dashes---'),
+ element.computeLabelShortcut('--Too----many----dashes---'),
'TMD'
);
assert.equal(
- element._computeLabelShortcut(
+ element.computeLabelShortcut(
'Really-rather-entirely-too-long-of-a-label-name'
),
'RRETL'
);
});
- test('colspans', () => {
+ test('colspans', async () => {
element.sections = [{results: [{...createChange()}]}];
- flush();
+ await element.updateComplete;
const tdItemCount = queryAll<HTMLTableElement>(element, 'td').length;
- const changeTableColumns: string[] | undefined = [];
+ element.visibleChangeTableColumns = [];
const labelNames: string[] | undefined = [];
assert.equal(
tdItemCount,
- element._computeColspan(
- {results: [{...createChange()}]},
- changeTableColumns,
- labelNames
- )
+ element.computeColspan({results: [{...createChange()}]}, labelNames)
);
});
test('keyboard shortcuts', async () => {
- sinon.stub(element, '_computeLabelNames');
+ sinon.stub(element, 'computeLabelNames');
element.sections = [{results: new Array(1)}, {results: new Array(2)}];
element.selectedIndex = 0;
element.changes = [
@@ -187,12 +180,7 @@
{...createChange(), _number: 1 as NumericChangeId},
{...createChange(), _number: 2 as NumericChangeId},
];
- await flush();
- const promise = mockPromise();
- afterNextRender(element, () => {
- promise.resolve();
- });
- await promise;
+ await element.updateComplete;
const elementItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -201,15 +189,18 @@
assert.isTrue(elementItems[0].hasAttribute('selected'));
pressKey(element, 'j');
+ await element.updateComplete;
assert.equal(element.selectedIndex, 1);
assert.isTrue(elementItems[1].hasAttribute('selected'));
pressKey(element, 'j');
+ await element.updateComplete;
assert.equal(element.selectedIndex, 2);
assert.isTrue(elementItems[2].hasAttribute('selected'));
const navStub = sinon.stub(GerritNav, 'navigateToChange');
assert.equal(element.selectedIndex, 2);
pressKey(element, Key.ENTER);
+ await element.updateComplete;
assert.deepEqual(
navStub.lastCall.args[0],
{...createChange(), _number: 2 as NumericChangeId},
@@ -217,8 +208,10 @@
);
pressKey(element, 'k');
+ await element.updateComplete;
assert.equal(element.selectedIndex, 1);
pressKey(element, Key.ENTER);
+ await element.updateComplete;
assert.deepEqual(
navStub.lastCall.args[0],
{...createChange(), _number: 1 as NumericChangeId},
@@ -231,9 +224,9 @@
assert.equal(element.selectedIndex, 0);
});
- test('no changes', () => {
+ test('no changes', async () => {
element.changes = [];
- flush();
+ await element.updateComplete;
const listItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -246,9 +239,9 @@
assert.ok(noChangesMsg);
});
- test('empty sections', () => {
+ test('empty sections', async () => {
element.sections = [{results: []}, {results: []}];
- flush();
+ await element.updateComplete;
const listItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -261,8 +254,8 @@
suite('empty section', () => {
test('not shown on empty non-outgoing sections', () => {
const section = {name: 'test', query: 'test', results: []};
- assert.isTrue(element._isEmpty(section));
- assert.equal(element._getSpecialEmptySlot(section), '');
+ assert.isTrue(element.isEmpty(section));
+ assert.equal(element.getSpecialEmptySlot(section), '');
});
test('shown on empty outgoing sections', () => {
@@ -272,14 +265,14 @@
results: [],
isOutgoing: true,
};
- assert.isTrue(element._isEmpty(section));
- assert.equal(element._getSpecialEmptySlot(section), 'empty-outgoing');
+ assert.isTrue(element.isEmpty(section));
+ assert.equal(element.getSpecialEmptySlot(section), 'empty-outgoing');
});
test('shown on empty outgoing sections', () => {
const section = {name: YOUR_TURN.name, query: 'test', results: []};
- assert.isTrue(element._isEmpty(section));
- assert.equal(element._getSpecialEmptySlot(section), 'empty-your-turn');
+ assert.isTrue(element.isEmpty(section));
+ assert.equal(element.getSpecialEmptySlot(section), 'empty-your-turn');
});
test('not shown on non-empty outgoing sections', () => {
@@ -295,14 +288,14 @@
},
],
};
- assert.isFalse(element._isEmpty(section));
+ assert.isFalse(element.isEmpty(section));
});
});
suite('empty column preference', () => {
let element: GrChangeList;
- setup(() => {
+ setup(async () => {
stubFlags('isEnabled').returns(true);
element = basicFixture.instantiate();
element.sections = [{results: [{...createChange()}]}];
@@ -312,8 +305,8 @@
time_format: TimeFormat.HHMM_12,
change_table: [],
};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number enabled', () => {
@@ -333,7 +326,7 @@
suite('full column preference', () => {
let element: GrChangeList;
- setup(() => {
+ setup(async () => {
stubFlags('isEnabled').returns(true);
element = basicFixture.instantiate();
element.sections = [{results: [{...createChange()}]}];
@@ -354,8 +347,8 @@
' Status ',
],
};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('all columns visible', () => {
@@ -371,7 +364,7 @@
suite('partial column preference', () => {
let element: GrChangeList;
- setup(() => {
+ setup(async () => {
stubFlags('isEnabled').returns(true);
element = basicFixture.instantiate();
element.sections = [{results: [{...createChange()}]}];
@@ -391,8 +384,8 @@
' Status ',
],
};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('all columns except repo visible', () => {
@@ -417,7 +410,7 @@
/* This would only exist if somebody manually updated the config
file. */
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.account = {_account_id: 1001 as AccountId};
element.preferences = {
@@ -425,7 +418,7 @@
time_format: TimeFormat.HHMM_12,
change_table: ['Bad'],
};
- flush();
+ await element.updateComplete;
});
test('bad column does not exist', () => {
@@ -446,43 +439,43 @@
test('query without age and limit unchanged', () => {
const query = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), query);
+ assert.deepEqual(element.processQuery(query), query);
});
test('query with age and limit', () => {
const query = 'status:closed age:1week limit:10 owner:me';
const expectedQuery = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with age', () => {
const query = 'status:closed age:1week owner:me';
const expectedQuery = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with limit', () => {
const query = 'status:closed limit:10 owner:me';
const expectedQuery = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with age as value and not key', () => {
const query = 'status:closed random:age';
const expectedQuery = 'status:closed random:age';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with limit as value and not key', () => {
const query = 'status:closed random:limit';
const expectedQuery = 'status:closed random:limit';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with -age key', () => {
const query = 'status:closed -age:1week';
const expectedQuery = 'status:closed';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
});
@@ -518,12 +511,7 @@
],
},
];
- await flush();
- const promise = mockPromise();
- afterNextRender(element, () => {
- promise.resolve();
- });
- await promise;
+ await element.updateComplete;
const elementItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -565,23 +553,23 @@
);
});
- test('_computeItemAbsoluteIndex', () => {
- sinon.stub(element, '_computeLabelNames');
+ test('computeItemAbsoluteIndex', () => {
+ sinon.stub(element, 'computeLabelNames');
element.sections = [
{results: new Array(1)},
{results: new Array(2)},
{results: new Array(3)},
];
- assert.equal(element._computeItemAbsoluteIndex(0, 0), 0);
+ assert.equal(element.computeItemAbsoluteIndex(0, 0), 0);
// Out of range but no matter.
- assert.equal(element._computeItemAbsoluteIndex(0, 1), 1);
+ assert.equal(element.computeItemAbsoluteIndex(0, 1), 1);
- assert.equal(element._computeItemAbsoluteIndex(1, 0), 1);
- assert.equal(element._computeItemAbsoluteIndex(1, 1), 2);
- assert.equal(element._computeItemAbsoluteIndex(1, 2), 3);
- assert.equal(element._computeItemAbsoluteIndex(2, 0), 3);
- assert.equal(element._computeItemAbsoluteIndex(3, 0), 6);
+ assert.equal(element.computeItemAbsoluteIndex(1, 0), 1);
+ assert.equal(element.computeItemAbsoluteIndex(1, 1), 2);
+ assert.equal(element.computeItemAbsoluteIndex(1, 2), 3);
+ assert.equal(element.computeItemAbsoluteIndex(2, 0), 3);
+ assert.equal(element.computeItemAbsoluteIndex(3, 0), 6);
});
});
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 9beaa58..1b04268 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -450,12 +450,8 @@
this.$.commandsDialog.open();
}
- /**
- * Returns `this` as the visibility observer target for the keyboard shortcut
- * mixin to decide whether shortcuts should be enabled or not.
- */
- _computeObserverTarget() {
- return this;
+ _handleSelectedIndexChanged(e: CustomEvent) {
+ this._selectedChangeIndex = Number(e.detail.value);
}
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
index a55befb..84cf6d9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
@@ -78,10 +78,10 @@
show-star=""
account="[[account]]"
preferences="[[preferences]]"
- selected-index="{{_selectedChangeIndex}}"
+ selected-index="[[_selectedChangeIndex]]"
sections="[[_results]]"
+ on-selected-index-changed="_handleSelectedIndexChanged"
on-toggle-star="_handleToggleStar"
- observer-target="[[_computeObserverTarget()]]"
>
<div id="emptyOutgoing" slot="empty-outgoing">
<template is="dom-if" if="[[_showNewUserHelp]]">