blob: fcfe209b9c22478d3a37303e440fce87241237af [file] [log] [blame]
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '@polymer/iron-input/iron-input';
import '../../shared/gr-button/gr-button';
import {IncludedInInfo, NumericChangeId} from '../../../types/common';
import {getAppContext} from '../../../services/app-context';
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.js';
import {BindValueChangeEvent} from '../../../types/events';
interface DisplayGroup {
title: string;
items: string[];
}
@customElement('gr-included-in-dialog')
export class GrIncludedInDialog extends LitElement {
/**
* Fired when the user presses the close button.
*
* @event close
*/
@property({type: Object})
changeNum?: NumericChangeId;
// private but used in test
@state() includedIn?: IncludedInInfo;
@state() private loaded = false;
// private but used in test
@state() filterText = '';
private readonly restApiService = getAppContext().restApiService;
static override get styles() {
return [
fontStyles,
sharedStyles,
css`
:host {
background-color: var(--dialog-background-color);
display: block;
max-height: 80vh;
overflow-y: auto;
padding: 4.5em var(--spacing-l) var(--spacing-l) var(--spacing-l);
}
header {
background-color: var(--dialog-background-color);
border-bottom: 1px solid var(--border-color);
left: 0;
padding: var(--spacing-l);
position: absolute;
right: 0;
top: 0;
}
#title {
display: inline-block;
font-family: var(--header-font-family);
font-size: var(--font-size-h3);
font-weight: var(--font-weight-h3);
line-height: var(--line-height-h3);
margin-top: var(--spacing-xs);
}
#filterInput {
display: block;
float: right;
margin: 0 var(--spacing-l);
padding: var(--spacing-xs);
}
.closeButtonContainer {
float: right;
}
ul {
margin-bottom: var(--spacing-l);
}
ul li {
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
background: var(--chip-background-color);
display: inline-block;
margin: 0 var(--spacing-xs) var(--spacing-s) var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-s);
}
`,
];
}
override render() {
return html`
<header>
<h1 id="title" class="heading-1">Included In:</h1>
<span class="closeButtonContainer">
<gr-button
id="closeButton"
link
@click=${(e: Event) => {
this.handleCloseTap(e);
}}
>Close</gr-button
>
</span>
<iron-input
id="filterInput"
.bindValue=${this.filterText}
@bind-value-changed=${(e: BindValueChangeEvent) => {
this.filterText = e.detail.value ?? '';
}}
>
<input placeholder="Filter" />
</iron-input>
</header>
${this.renderLoading()}
${this.computeGroups().map(group => this.renderGroup(group))}
`;
}
private renderLoading() {
if (this.loaded) return;
return html`<div>Loading...</div>`;
}
private renderGroup(group: DisplayGroup) {
return html`
<div>
<span>${group.title}:</span>
<ul>
${group.items.map(item => this.renderGroupItem(item))}
</ul>
</div>
`;
}
private renderGroupItem(item: string) {
return html`<li>${item}</li>`;
}
override willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('changeNum')) {
this.resetData();
}
}
loadData() {
if (!this.changeNum) {
return Promise.reject(new Error('missing required property changeNum'));
}
this.filterText = '';
return this.restApiService
.getChangeIncludedIn(this.changeNum)
.then(configs => {
if (!configs) {
return;
}
this.includedIn = configs;
this.loaded = true;
});
}
private resetData() {
this.includedIn = undefined;
this.loaded = false;
}
// private but used in test
computeGroups() {
if (!this.includedIn || this.filterText === undefined) {
return [];
}
const filter = (item: string) =>
!this.filterText.length ||
item.toLowerCase().indexOf(this.filterText.toLowerCase()) !== -1;
const groups: DisplayGroup[] = [
{title: 'Branches', items: this.includedIn.branches.filter(filter)},
{title: 'Tags', items: this.includedIn.tags.filter(filter)},
];
if (this.includedIn.external) {
for (const externalKey of Object.keys(this.includedIn.external)) {
groups.push({
title: externalKey,
items: this.includedIn.external[externalKey].filter(filter),
});
}
}
return groups.filter(g => g.items.length);
}
private handleCloseTap(e: Event) {
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(
new CustomEvent('close', {
composed: true,
bubbles: false,
})
);
}
}
declare global {
interface HTMLElementTagNameMap {
'gr-included-in-dialog': GrIncludedInDialog;
}
}