| /** |
| * @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. |
| */ |
| import '@polymer/iron-input/iron-input'; |
| import '../../../styles/gr-form-styles'; |
| import '../../shared/gr-button/gr-button'; |
| import {ServerInfo, AccountDetailInfo} from '../../../types/common'; |
| import {EditableAccountField} from '../../../constants/constants'; |
| import {getAppContext} from '../../../services/app-context'; |
| import {fireEvent} from '../../../utils/event-util'; |
| import {LitElement, css, html, PropertyValues} from 'lit'; |
| import {customElement, property, query, state} from 'lit/decorators'; |
| import {sharedStyles} from '../../../styles/shared-styles'; |
| import {formStyles} from '../../../styles/gr-form-styles'; |
| import {when} from 'lit/directives/when'; |
| import {ifDefined} from 'lit/directives/if-defined'; |
| import {BindValueChangeEvent} from '../../../types/events'; |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-registration-dialog': GrRegistrationDialog; |
| } |
| } |
| |
| @customElement('gr-registration-dialog') |
| export class GrRegistrationDialog extends LitElement { |
| /** |
| * Fired when account details are changed. |
| * |
| * @event account-detail-update |
| */ |
| |
| /** |
| * Fired when the close button is pressed. |
| * |
| * @event close |
| */ |
| @query('#name') nameInput?: HTMLInputElement; |
| |
| @query('#username') usernameInput?: HTMLInputElement; |
| |
| @query('#displayName') displayName?: HTMLInputElement; |
| |
| @property() settingsUrl?: string; |
| |
| @state() account: Partial<AccountDetailInfo> = {}; |
| |
| @state() loading = true; |
| |
| @state() saving = false; |
| |
| @state() serverConfig?: ServerInfo; |
| |
| @state() usernameMutable = false; |
| |
| @state() hasUsernameChange?: boolean; |
| |
| @state() username?: string; |
| |
| @state() nameMutable?: boolean; |
| |
| @state() hasNameChange?: boolean; |
| |
| @state() hasDisplayNameChange?: boolean; |
| |
| private readonly restApiService = getAppContext().restApiService; |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| if (!this.getAttribute('role')) { |
| this.setAttribute('role', 'dialog'); |
| } |
| } |
| |
| static override styles = [ |
| sharedStyles, |
| formStyles, |
| css` |
| :host { |
| display: block; |
| } |
| main { |
| max-width: 46em; |
| } |
| :host(.loading) main { |
| display: none; |
| } |
| .loadingMessage { |
| display: none; |
| font-style: italic; |
| } |
| :host(.loading) .loadingMessage { |
| display: block; |
| } |
| hr { |
| margin-top: var(--spacing-l); |
| margin-bottom: var(--spacing-l); |
| } |
| header { |
| border-bottom: 1px solid var(--border-color); |
| font-weight: var(--font-weight-bold); |
| margin-bottom: var(--spacing-l); |
| } |
| .container { |
| padding: var(--spacing-m) var(--spacing-xl); |
| } |
| footer { |
| display: flex; |
| justify-content: flex-end; |
| } |
| footer gr-button { |
| margin-left: var(--spacing-l); |
| } |
| input { |
| width: 20em; |
| } |
| `, |
| ]; |
| |
| override render() { |
| return html`<div class="container gr-form-styles"> |
| <header>Please confirm your contact information</header> |
| <div class="loadingMessage">Loading...</div> |
| <main> |
| <p> |
| The following contact information was automatically obtained when you |
| signed in to the site. This information is used to display who you are |
| to others, and to send updates to code reviews you have either started |
| or subscribed to. |
| </p> |
| <hr /> |
| <section> |
| <span class="title">Full Name</span> |
| ${when( |
| this.nameMutable, |
| () => html`<span class="value"> |
| <iron-input |
| .bindValue=${this.account.name} |
| @bind-value-changed=${(e: BindValueChangeEvent) => { |
| const oldAccount = this.account; |
| if (!oldAccount || oldAccount.name === e.detail.value) return; |
| this.account = {...oldAccount, name: e.detail.value}; |
| this.hasNameChange = true; |
| }} |
| > |
| <input id="name" ?disabled=${this.saving} /> |
| </iron-input> |
| </span>`, |
| () => html`<span class="value">${this.account.name}</span>` |
| )} |
| </section> |
| <section> |
| <span class="title">Display Name</span> |
| <span class="value"> |
| <iron-input |
| .bindValue=${this.account.display_name} |
| @bind-value-changed=${(e: BindValueChangeEvent) => { |
| const oldAccount = this.account; |
| if (!oldAccount || oldAccount.display_name === e.detail.value) { |
| return; |
| } |
| this.account = {...oldAccount, display_name: e.detail.value}; |
| this.hasDisplayNameChange = true; |
| }} |
| > |
| <input id="displayName" ?disabled=${this.saving} /> |
| </iron-input> |
| </span> |
| </section> |
| ${when( |
| this.computeUsernameEditable(), |
| () => html`<section> |
| <span class="title">Username</span> |
| ${when( |
| this.usernameMutable, |
| () => html` <span class="value"> |
| <iron-input |
| .bindValue=${this.username} |
| @bind-value-changed=${(e: BindValueChangeEvent) => { |
| if (!this.usernameInput || this.username === e.detail.value) |
| return; |
| this.username = e.detail.value; |
| this.hasUsernameChange = true; |
| }} |
| > |
| <input id="username" ?disabled=${this.saving} /> |
| </iron-input> |
| </span>`, |
| () => html`<span class="value">${this.username}</span>` |
| )} |
| </section>` |
| )} |
| <hr /> |
| <p> |
| More configuration options for Gerrit may be found in the |
| <a @click=${this.close} href=${ifDefined(this.settingsUrl)} |
| >settings</a |
| >. |
| </p> |
| </main> |
| <footer> |
| <gr-button |
| id="closeButton" |
| link |
| ?disabled=${this.saving} |
| @click=${this.handleClose} |
| >Close</gr-button |
| > |
| <gr-button |
| id="saveButton" |
| primary |
| link |
| ?disabled=${this.computeSaveDisabled()} |
| @click=${this.handleSave} |
| >Save</gr-button |
| > |
| </footer> |
| </div>`; |
| } |
| |
| override willUpdate(changedProperties: PropertyValues) { |
| if (changedProperties.has('account')) { |
| this.usernameMutable = !this.account.username; |
| } |
| if (changedProperties.has('serverConfig')) { |
| this.nameMutable = this.computeNameMutable(); |
| } |
| if (changedProperties.has('loading')) { |
| this.classList.toggle('loading', this.loading); |
| } |
| } |
| |
| loadData() { |
| this.loading = true; |
| |
| const loadAccount = this.restApiService.getAccount().then(account => { |
| if (!account) return; |
| this.hasNameChange = false; |
| this.hasUsernameChange = false; |
| this.hasDisplayNameChange = false; |
| // Provide predefined value for username to trigger computation of |
| // username mutability. |
| account.username = account.username || ''; |
| |
| this.account = account; |
| this.username = account.username; |
| }); |
| |
| const loadConfig = this.restApiService.getConfig().then(config => { |
| this.serverConfig = config; |
| }); |
| |
| return Promise.all([loadAccount, loadConfig]).then(() => { |
| this.loading = false; |
| }); |
| } |
| |
| // private but used in test |
| computeUsernameEditable() { |
| return !!this.serverConfig?.auth.editable_account_fields.includes( |
| EditableAccountField.USER_NAME |
| ); |
| } |
| |
| private computeNameMutable() { |
| return !!this.serverConfig?.auth.editable_account_fields.includes( |
| EditableAccountField.FULL_NAME |
| ); |
| } |
| |
| // private but used in test |
| save() { |
| this.saving = true; |
| |
| const promises = []; |
| // Note that we are intentionally not acting on this._username being the |
| // empty string (which is falsy). |
| if (this.hasUsernameChange && this.usernameMutable && this.username) { |
| promises.push(this.restApiService.setAccountUsername(this.username)); |
| } |
| |
| if (this.hasNameChange && this.nameMutable && this.account?.name) { |
| promises.push(this.restApiService.setAccountName(this.account.name)); |
| } |
| |
| if (this.hasDisplayNameChange && this.account?.display_name) { |
| promises.push( |
| this.restApiService.setAccountDisplayName(this.account.display_name) |
| ); |
| } |
| |
| return Promise.all(promises).then(() => { |
| this.saving = false; |
| fireEvent(this, 'account-detail-update'); |
| }); |
| } |
| |
| private handleSave(e: Event) { |
| e.preventDefault(); |
| this.save().then(() => this.close()); |
| } |
| |
| private handleClose(e: Event) { |
| e.preventDefault(); |
| this.close(); |
| } |
| |
| private close() { |
| this.saving = true; // disable buttons indefinitely |
| fireEvent(this, 'close'); |
| } |
| |
| // private but used in test |
| computeSaveDisabled() { |
| return ( |
| this.saving || |
| (!this.account?.display_name && !this.account.name && !this.username) |
| ); |
| } |
| } |