| /** |
| * @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, state} from 'lit/decorators.js'; |
| import {BindValueChangeEvent} from '../../../types/events'; |
| import {fireNoBubble} from '../../../utils/event-util'; |
| import {resolve} from '../../../models/dependency'; |
| import {changeModelToken} from '../../../models/change/change-model'; |
| import {subscribe} from '../../lit/subscription-controller'; |
| import {formStyles} from '../../../styles/form-styles'; |
| |
| 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 |
| */ |
| |
| @state() changeNum?: NumericChangeId; |
| |
| @state() includedIn?: IncludedInInfo; |
| |
| @state() private loaded = false; |
| |
| @state() filterText = ''; |
| |
| private readonly restApiService = getAppContext().restApiService; |
| |
| private readonly getChangeModel = resolve(this, changeModelToken); |
| |
| static override get styles() { |
| return [ |
| formStyles, |
| 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); |
| } |
| `, |
| ]; |
| } |
| |
| constructor() { |
| super(); |
| subscribe( |
| this, |
| () => this.getChangeModel().changeNum$, |
| changeNum => (this.changeNum = changeNum) |
| ); |
| } |
| |
| 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(); |
| fireNoBubble(this, 'close', {}); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-included-in-dialog': GrIncludedInDialog; |
| } |
| } |