| /** |
| * @license |
| * Copyright 2022 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| import '@polymer/iron-dropdown/iron-dropdown'; |
| import '../../shared/gr-copy-clipboard/gr-copy-clipboard'; |
| import {LitElement, html, css, nothing} from 'lit'; |
| import {customElement, property, query, state} from 'lit/decorators.js'; |
| import {strToClassName} from '../../../utils/dom-util'; |
| import {IronDropdownElement} from '@polymer/iron-dropdown/iron-dropdown'; |
| import {copyToClipbard, queryAndAssert} from '../../../utils/common-util'; |
| import {ValueChangedEvent} from '../../../types/events'; |
| |
| export interface CopyLink { |
| label: string; |
| shortcut: string; |
| value: string; |
| } |
| |
| const AWAIT_MAX_ITERS = 10; |
| const AWAIT_STEP = 5; |
| |
| @customElement('gr-copy-links') |
| export class GrCopyLinks extends LitElement { |
| @property({type: Array}) |
| copyLinks: CopyLink[] = []; |
| |
| @state() isDropdownOpen = false; |
| |
| @query('iron-dropdown') private dropdown?: IronDropdownElement; |
| |
| static override get styles() { |
| return [ |
| css` |
| iron-dropdown { |
| box-shadow: var(--elevation-level-2); |
| width: min(90vw, 640px); |
| background-color: var(--dialog-background-color); |
| border-radius: var(--border-radius); |
| } |
| [slot='dropdown-content'] { |
| padding: var(--spacing-m) var(--spacing-l) var(--spacing-m); |
| } |
| .copy-link-row { |
| margin-bottom: var(--spacing-m); |
| display: flex; |
| align-items: center; |
| } |
| .copy-link-row label { |
| flex: 0 0 120px; |
| color: var(--deemphasized-text-color); |
| } |
| .copy-link-row input { |
| flex: 1 1 420px; |
| } |
| .copy-link-row .shortcut { |
| width: 27px; |
| margin: 0 var(--spacing-m); |
| color: var(--deemphasized-text-color); |
| } |
| .copy-link-row gr-copy-clipboard { |
| flex: 0 0 20px; |
| } |
| /* TODO(milutin): It's from shared styles, move it to input styles */ |
| input { |
| background-color: var(--background-color-primary); |
| border: 1px solid var(--border-color); |
| border-radius: var(--border-radius); |
| box-sizing: border-box; |
| color: var(--primary-text-color); |
| margin: 0; |
| padding: var(--spacing-s); |
| font: inherit; |
| } |
| `, |
| ]; |
| } |
| |
| override render() { |
| if (!this.copyLinks) return nothing; |
| return html`<iron-dropdown |
| .horizontalAlign=${'left'} |
| .verticalAlign=${'top'} |
| .verticalOffset=${20} |
| @keydown=${this.handleKeydown} |
| @opened-changed=${(e: ValueChangedEvent<boolean>) => |
| (this.isDropdownOpen = e.detail.value)} |
| > |
| ${this.renderCopyLinks()} |
| </iron-dropdown>`; |
| } |
| |
| private renderCopyLinks() { |
| return html`<div slot="dropdown-content"> |
| ${this.copyLinks?.map(link => this.renderCopyLinkRow(link))} |
| </div>`; |
| } |
| |
| private renderCopyLinkRow(copyLink: CopyLink) { |
| const {label, shortcut, value} = copyLink; |
| const id = `${strToClassName(label, '')}-field`; |
| // TODO(milutin): Use input in gr-copy-clipboard instead of creating new |
| // one. Move shorcut to gr-copy-clipboard. |
| return html`<div class="copy-link-row"> |
| <label for=${id}>${label}</label |
| ><input type="text" readonly="" id=${id} class="input" .value=${value} /> |
| <span class="shortcut">${`l - ${shortcut}`}</span> |
| <gr-copy-clipboard |
| hideInput="" |
| text=${value} |
| id=${`${id}-copy-clipboard`} |
| ></gr-copy-clipboard> |
| </div>`; |
| } |
| |
| private async handleKeydown(e: KeyboardEvent) { |
| const copyLink = this.copyLinks?.find(link => link.shortcut === e.key); |
| if (!copyLink) return; |
| await copyToClipbard(copyLink.value, copyLink.label); |
| this.closeDropdown(); |
| } |
| |
| toggleDropdown() { |
| this.isDropdownOpen ? this.closeDropdown() : this.openDropdown(); |
| } |
| |
| private closeDropdown() { |
| this.dropdown?.close(); |
| } |
| |
| openDropdown() { |
| this.dropdown?.open(); |
| this.awaitOpen(() => { |
| queryAndAssert<HTMLInputElement>(this.dropdown, 'input')?.select(); |
| }); |
| } |
| |
| /** |
| * NOTE: (milutin) Slightly hacky way to listen to the overlay actually |
| * opening. It's from gr-editable-label. It will be removed when we |
| * migrate out of iron-* components. |
| */ |
| private awaitOpen(fn: () => void) { |
| let iters = 0; |
| const step = () => { |
| setTimeout(() => { |
| if (this.dropdown?.style.display !== 'none') { |
| fn.call(this); |
| } else if (iters++ < AWAIT_MAX_ITERS) { |
| step.call(this); |
| } |
| }, AWAIT_STEP); |
| }; |
| step.call(this); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-copy-links': GrCopyLinks; |
| } |
| } |