| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import debounce from 'debounce'; |
| import {LitElement, html, css} from 'lit-element'; |
| |
| import {userV3ToRef} from 'shared/convertersV0.js'; |
| |
| import {store, connectStore} from 'reducers/base.js'; |
| import {hotlists} from 'reducers/hotlists.js'; |
| import * as sitewide from 'reducers/sitewide.js'; |
| import * as users from 'reducers/users.js'; |
| |
| import 'elements/framework/links/mr-user-link/mr-user-link.js'; |
| import 'elements/hotlist/mr-hotlist-header/mr-hotlist-header.js'; |
| |
| /** Hotlist People page */ |
| class _MrHotlistPeoplePage extends LitElement { |
| /** @override */ |
| static get styles() { |
| return css` |
| :host { |
| display: block; |
| } |
| section { |
| margin: 16px 24px; |
| } |
| h2 { |
| font-weight: normal; |
| } |
| |
| ul { |
| padding: 0; |
| } |
| li { |
| list-style-type: none; |
| } |
| p, li, form { |
| display: flex; |
| } |
| p, ul, li, form { |
| margin: 12px 0; |
| } |
| |
| input { |
| margin-left: -6px; |
| padding: 4px; |
| width: 320px; |
| } |
| |
| button { |
| align-items: center; |
| background-color: transparent; |
| border: 0; |
| cursor: pointer; |
| display: inline-flex; |
| margin: 0 4px; |
| padding: 0; |
| } |
| .material-icons { |
| font-size: 18px; |
| } |
| |
| .placeholder::before { |
| animation: pulse 1s infinite ease-in-out; |
| border-radius: 3px; |
| content: " "; |
| height: 10px; |
| margin: 4px 0; |
| width: 200px; |
| } |
| @keyframes pulse { |
| 0% {background-color: var(--chops-blue-50);} |
| 50% {background-color: var(--chops-blue-75);} |
| 100% {background-color: var(--chops-blue-50);} |
| } |
| `; |
| } |
| |
| /** @override */ |
| render() { |
| return html` |
| <mr-hotlist-header selected=1></mr-hotlist-header> |
| ${this._renderPage()} |
| `; |
| } |
| |
| /** |
| * @return {TemplateResult} |
| */ |
| _renderPage() { |
| if (this._fetchError) { |
| return html`<section>${this._fetchError.description}</section>`; |
| } |
| |
| return html` |
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> |
| |
| <section> |
| <h2>Owner</h2> |
| ${this._renderOwner(this._owner)} |
| </section> |
| |
| <section> |
| <h2>Editors</h2> |
| ${this._renderEditors(this._editors)} |
| |
| ${this._permissions.includes(hotlists.ADMINISTER) ? html` |
| <form @submit=${this._onAddEditors}> |
| <input id="add" placeholder="List of email addresses"></input> |
| <button><i class="material-icons">add</i></button> |
| </form> |
| ` : html``} |
| </section> |
| `; |
| } |
| |
| /** |
| * @param {?User} owner |
| * @return {TemplateResult} |
| */ |
| _renderOwner(owner) { |
| if (!owner) return html`<p class="placeholder"></p>`; |
| return html` |
| <p><mr-user-link .userRef=${userV3ToRef(owner)}></mr-user-link></p> |
| `; |
| } |
| |
| /** |
| * @param {?Array<User>} editors |
| * @return {TemplateResult} |
| */ |
| _renderEditors(editors) { |
| if (!editors) return html`<p class="placeholder"></p>`; |
| if (!editors.length) return html`<p>No editors.</p>`; |
| |
| return html` |
| <ul>${editors.map((editor) => this._renderEditor(editor))}</ul> |
| `; |
| } |
| |
| /** |
| * @param {?User} editor |
| * @return {TemplateResult} |
| */ |
| _renderEditor(editor) { |
| if (!editor) return html`<li class="placeholder"></li>`; |
| |
| const canRemove = this._permissions.includes(hotlists.ADMINISTER) || |
| editor.name === this._currentUserName; |
| |
| return html` |
| <li> |
| <mr-user-link .userRef=${userV3ToRef(editor)}></mr-user-link> |
| ${canRemove ? html` |
| <button @click=${this._removeEditor.bind(this, editor.name)}> |
| <i class="material-icons">clear</i> |
| </button> |
| ` : html``} |
| </li> |
| `; |
| } |
| |
| /** @override */ |
| static get properties() { |
| return { |
| // Populated from Redux. |
| _hotlist: {type: Object}, |
| _owner: {type: Object}, |
| _editors: {type: Array}, |
| _permissions: {type: Array}, |
| _currentUserName: {type: String}, |
| _fetchError: {type: Object}, |
| }; |
| } |
| |
| /** @override */ |
| constructor() { |
| super(); |
| |
| // Populated from Redux. |
| /** @type {?Hotlist} */ this._hotlist = null; |
| /** @type {?User} */ this._owner = null; |
| /** @type {Array<User>} */ this._editors = null; |
| /** @type {Array<Permission>} */ this._permissions = []; |
| /** @type {?String} */ this._currentUserName = null; |
| /** @type {?Error} */ this._fetchError = null; |
| |
| this._debouncedAddEditors = debounce(this._addEditors, 400, true); |
| } |
| |
| /** Adds hotlist editors. |
| * @param {Event} event |
| */ |
| async _onAddEditors(event) { |
| event.preventDefault(); |
| |
| const input = |
| /** @type {HTMLInputElement} */ (this.shadowRoot.getElementById('add')); |
| const emails = input.value.split(/[\s,;]/).filter((e) => e); |
| if (!emails.length) return; |
| const editors = emails.map((email) => 'users/' + email); |
| try { |
| await this._debouncedAddEditors(editors); |
| input.value = ''; |
| } catch (error) { |
| // The `hotlists.update()` call shows a snackbar on errors. |
| } |
| } |
| |
| /** Adds hotlist editors. |
| * @param {Array<string>} editors An Array of User resource names. |
| */ |
| async _addEditors(editors) {} |
| |
| /** |
| * Removes a hotlist editor. |
| * @param {string} name A User resource name. |
| */ |
| async _removeEditor(name) {} |
| }; |
| |
| /** Redux-connected version of _MrHotlistPeoplePage. */ |
| export class MrHotlistPeoplePage extends connectStore(_MrHotlistPeoplePage) { |
| /** @override */ |
| stateChanged(state) { |
| this._hotlist = hotlists.viewedHotlist(state); |
| this._owner = hotlists.viewedHotlistOwner(state); |
| this._editors = hotlists.viewedHotlistEditors(state); |
| this._permissions = hotlists.viewedHotlistPermissions(state); |
| this._currentUserName = users.currentUserName(state); |
| this._fetchError = hotlists.requests(state).fetch.error; |
| } |
| |
| /** @override */ |
| updated(changedProperties) { |
| super.updated(changedProperties); |
| |
| if (changedProperties.has('_hotlist') && this._hotlist) { |
| const pageTitle = 'People - ' + this._hotlist.displayName; |
| store.dispatch(sitewide.setPageTitle(pageTitle)); |
| const headerTitle = 'Hotlist ' + this._hotlist.displayName; |
| store.dispatch(sitewide.setHeaderTitle(headerTitle)); |
| } |
| } |
| |
| /** @override */ |
| async _addEditors(editors) { |
| await store.dispatch(hotlists.update(this._hotlist.name, {editors})); |
| } |
| |
| /** @override */ |
| async _removeEditor(name) { |
| await store.dispatch(hotlists.removeEditors(this._hotlist.name, [name])); |
| } |
| } |
| |
| customElements.define('mr-hotlist-people-page-base', _MrHotlistPeoplePage); |
| customElements.define('mr-hotlist-people-page', MrHotlistPeoplePage); |