Merge changes I27a6d786,I59d2b5e7 * changes: Migrate gr-download-commands to lit Convert gr-download-dialog to lit
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts index 5fc7bc8..a3f7374 100644 --- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts +++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -246,7 +246,10 @@ )} .schemes=${this.schemes} .selectedScheme=${this.selectedScheme} - @selected-scheme-changed=${this.handleSelectedSchemeValueChanged} + @selected-scheme-changed=${(e: BindValueChangeEvent) => { + if (this.loading) return; + this.selectedScheme = e.detail.value; + }} ></gr-download-commands> </fieldset> </div> @@ -1121,12 +1124,7 @@ } } - private handleSelectedSchemeValueChanged(e: CustomEvent) { - if (this.loading) return; - this.selectedScheme = e.detail.value; - } - - private handleDescriptionTextChanged(e: CustomEvent) { + private handleDescriptionTextChanged(e: BindValueChangeEvent) { if (!this.repoConfig || this.loading) return; this.repoConfig = { ...this.repoConfig,
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts index 92f4a87..a72749b 100644 --- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts +++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -14,46 +14,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../../styles/gr-font-styles'; -import '../../../styles/shared-styles'; import '../../shared/gr-download-commands/gr-download-commands'; -import {PolymerElement} from '@polymer/polymer/polymer-element'; -import {htmlTemplate} from './gr-download-dialog_html'; import {changeBaseURL, getRevisionKey} from '../../../utils/change-util'; -import {customElement, property, computed, observe} from '@polymer/decorators'; -import { - ChangeInfo, - DownloadInfo, - PatchSetNum, - RevisionInfo, -} from '../../../types/common'; +import {ChangeInfo, DownloadInfo, PatchSetNum} from '../../../types/common'; import {GrDownloadCommands} from '../../shared/gr-download-commands/gr-download-commands'; import {GrButton} from '../../shared/gr-button/gr-button'; -import {hasOwnProperty} from '../../../utils/common-util'; +import {hasOwnProperty, queryAndAssert} from '../../../utils/common-util'; import {GrOverlayStops} from '../../shared/gr-overlay/gr-overlay'; import {fireAlert, fireEvent} from '../../../utils/event-util'; import {addShortcut} from '../../../utils/dom-util'; - -export interface GrDownloadDialog { - $: { - download: HTMLAnchorElement; - downloadCommands: GrDownloadCommands; - closeButton: GrButton; - }; -} +import {fontStyles} from '../../../styles/gr-font-styles'; +import {sharedStyles} from '../../../styles/shared-styles'; +import {LitElement, PropertyValues, html, css} from 'lit'; +import {customElement, property, state, query} from 'lit/decorators'; +import {assertIsDefined} from '../../../utils/common-util'; +import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs'; +import {BindValueChangeEvent} from '../../../types/events'; @customElement('gr-download-dialog') -export class GrDownloadDialog extends PolymerElement { - static get template() { - return htmlTemplate; - } - +export class GrDownloadDialog extends LitElement { /** * Fired when the user presses the close button. * * @event close */ + @query('#download') protected download?: HTMLAnchorElement; + + @query('#downloadCommands') protected downloadCommands?: GrDownloadCommands; + + @query('#closeButton') protected closeButton?: GrButton; + @property({type: Object}) change: ChangeInfo | undefined; @@ -63,8 +54,7 @@ @property({type: String}) patchNum: PatchSetNum | undefined; - @property({type: String}) - _selectedScheme?: string; + @state() private selectedScheme?: string; /** Called in disconnectedCallback. */ private cleanups: (() => void)[] = []; @@ -79,14 +69,159 @@ super.connectedCallback(); for (const key of ['1', '2', '3', '4', '5']) { this.cleanups.push( - addShortcut(this, {key}, e => this._handleNumberKey(e)) + addShortcut(this, {key}, e => this.handleNumberKey(e)) ); } } - @computed('change', 'patchNum') - get _schemes() { - // Polymer 2: check for undefined + static override get styles() { + return [ + fontStyles, + sharedStyles, + css` + :host { + display: block; + padding: var(--spacing-m) 0; + } + section { + display: flex; + padding: var(--spacing-m) var(--spacing-xl); + } + .flexContainer { + display: flex; + justify-content: space-between; + padding-top: var(--spacing-m); + } + .footer { + justify-content: flex-end; + } + .closeButtonContainer { + align-items: flex-end; + display: flex; + flex: 0; + justify-content: flex-end; + } + .patchFiles, + .archivesContainer { + padding-bottom: var(--spacing-m); + } + .patchFiles { + margin-right: var(--spacing-xxl); + } + .patchFiles a, + .archives a { + display: inline-block; + margin-right: var(--spacing-l); + } + .patchFiles a:last-of-type, + .archives a:last-of-type { + margin-right: 0; + } + gr-download-commands { + width: min(80vw, 1200px); + } + `, + ]; + } + + override render() { + const revisions = this.change?.revisions; + return html` + <section> + <h3 class="heading-3"> + Patch set ${this.patchNum} of + ${revisions ? Object.keys(revisions).length : 0} + </h3> + </section> + ${this.renderDownloadCommands()} + <section class="flexContainer"> + ${this.renderPatchFiles()} ${this.renderArchives()} + </section> + <section class="footer"> + <span class="closeButtonContainer"> + <gr-button + id="closeButton" + link + @click=${(e: Event) => { + this.handleCloseTap(e); + }} + >Close</gr-button + > + </span> + </section> + `; + } + + private renderDownloadCommands() { + if (!this.schemes.length) return; + + return html` + <section> + <gr-download-commands + id="downloadCommands" + .commands=${this.computeDownloadCommands()} + .schemes=${this.schemes} + .selectedScheme=${this.selectedScheme} + show-keyboard-shortcut-tooltips + @selected-scheme-changed=${(e: BindValueChangeEvent) => { + this.selectedScheme = e.detail.value; + }} + ></gr-download-commands> + </section> + `; + } + + private renderPatchFiles() { + if (this.computeHidePatchFile()) return; + + return html` + <div class="patchFiles"> + <label>Patch file</label> + <div> + <a id="download" .href="${this.computeDownloadLink()}" download> + ${this.computeDownloadFilename()} + </a> + <a .href="${this.computeDownloadLink(true)}" download> + ${this.computeDownloadFilename(true)} + </a> + </div> + </div> + `; + } + + private renderArchives() { + if (!this.config?.archives.length) return; + + return html` + <div class="archivesContainer"> + <label>Archive</label> + <div id="archives" class="archives"> + ${this.config.archives.map(format => this.renderArchivesLink(format))} + </div> + </div> + `; + } + + private renderArchivesLink(format: string) { + return html` + <a .href=${this.computeArchiveDownloadLink(format)} download> + ${format} + </a> + `; + } + + override firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + if (!this.getAttribute('role')) this.setAttribute('role', 'dialog'); + } + + override willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has('schemes')) { + this.schemesChanged(); + } + } + + get schemes() { if (this.change === undefined || this.patchNum === undefined) { return []; } @@ -103,13 +238,9 @@ return []; } - _handleNumberKey(e: KeyboardEvent) { + private handleNumberKey(e: KeyboardEvent) { const index = Number(e.key) - 1; - const commands = this._computeDownloadCommands( - this.change, - this.patchNum, - this._selectedScheme - ); + const commands = this.computeDownloadCommands(); if (index > commands.length) return; navigator.clipboard.writeText(commands[index].command).then(() => { fireAlert(this, `${commands[index].title} command copied to clipboard`); @@ -117,41 +248,40 @@ }); } - override ready() { - super.ready(); - this._ensureAttribute('role', 'dialog'); - } - override focus() { - if (this._schemes.length) { - this.$.downloadCommands.focusOnCopy(); + if (this.schemes.length) { + assertIsDefined(this.downloadCommands, 'downloadCommands'); + this.downloadCommands.focusOnCopy(); } else { - this.$.download.focus(); + assertIsDefined(this.download, 'download'); + this.download.focus(); } } getFocusStops(): GrOverlayStops { + assertIsDefined(this.downloadCommands, 'downloadCommands'); + assertIsDefined(this.closeButton, 'closeButton'); + const downloadTabs = queryAndAssert<PaperTabsElement>( + this.downloadCommands, + '#downloadTabs' + ); return { - start: this.$.downloadCommands.$.downloadTabs, - end: this.$.closeButton, + start: downloadTabs, + end: this.closeButton, }; } - _computeDownloadCommands( - change?: ChangeInfo, - patchNum?: PatchSetNum, - selectedScheme?: string - ) { + private computeDownloadCommands() { let commandObj; - if (!change || !selectedScheme) return []; - for (const rev of Object.values(change.revisions || {})) { + if (!this.change || !this.selectedScheme) return []; + for (const rev of Object.values(this.change.revisions || {})) { if ( - rev._number === patchNum && + rev._number === this.patchNum && rev && rev.fetch && - hasOwnProperty(rev.fetch, selectedScheme) + hasOwnProperty(rev.fetch, this.selectedScheme) ) { - commandObj = rev.fetch[selectedScheme].commands; + commandObj = rev.fetch[this.selectedScheme].commands; break; } } @@ -162,53 +292,35 @@ return commands; } - _computeZipDownloadLink(change?: ChangeInfo, patchNum?: PatchSetNum) { - return this._computeDownloadLink(change, patchNum, true); - } - - _computeZipDownloadFilename(change?: ChangeInfo, patchNum?: PatchSetNum) { - return this._computeDownloadFilename(change, patchNum, true); - } - - _computeDownloadLink( - change?: ChangeInfo, - patchNum?: PatchSetNum, - zip?: boolean - ) { - // Polymer 2: check for undefined - if (change === undefined || patchNum === undefined) { + private computeDownloadLink(zip?: boolean) { + if (this.change === undefined || this.patchNum === undefined) { return ''; } return ( - changeBaseURL(change.project, change._number, patchNum) + + changeBaseURL(this.change.project, this.change._number, this.patchNum) + '/patch?' + (zip ? 'zip' : 'download') ); } - _computeDownloadFilename( - change?: ChangeInfo, - patchNum?: PatchSetNum, - zip?: boolean - ) { - // Polymer 2: check for undefined - if (change === undefined || patchNum === undefined) { + private computeDownloadFilename(zip?: boolean) { + if (this.change === undefined || this.patchNum === undefined) { return ''; } - const rev = getRevisionKey(change, patchNum) ?? ''; + const rev = getRevisionKey(this.change, this.patchNum) ?? ''; const shortRev = rev.substr(0, 7); return shortRev + '.diff.' + (zip ? 'zip' : 'base64'); } - _computeHidePatchFile(change?: ChangeInfo, patchNum?: PatchSetNum) { - // Polymer 2: check for undefined - if (change === undefined || patchNum === undefined) { + // private but used in test + computeHidePatchFile() { + if (this.change === undefined || this.patchNum === undefined) { return false; } - for (const rev of Object.values(change.revisions || {})) { - if (rev._number === patchNum) { + for (const rev of Object.values(this.change.revisions || {})) { + if (rev._number === this.patchNum) { const parentLength = rev.commit && rev.commit.parents ? rev.commit.parents.length : 0; return parentLength === 0 || parentLength > 1; @@ -217,52 +329,36 @@ return false; } - _computeArchiveDownloadLink( - change?: ChangeInfo, - patchNum?: PatchSetNum, - format?: string - ) { - // Polymer 2: check for undefined + // private but used in test + computeArchiveDownloadLink(format?: string) { if ( - change === undefined || - patchNum === undefined || + this.change === undefined || + this.patchNum === undefined || format === undefined ) { return ''; } return ( - changeBaseURL(change.project, change._number, patchNum) + + changeBaseURL(this.change.project, this.change._number, this.patchNum) + '/archive?format=' + format ); } - _computePatchSetQuantity(revisions?: {[revisionId: string]: RevisionInfo}) { - if (!revisions) { - return 0; - } - return Object.keys(revisions).length; - } - - _handleCloseTap(e: Event) { + private handleCloseTap(e: Event) { e.preventDefault(); e.stopPropagation(); fireEvent(this, 'close'); } - @observe('_schemes') - _schemesChanged(schemes: string[]) { - if (schemes.length === 0) { + private schemesChanged() { + if (this.schemes.length === 0) { return; } - if (!this._selectedScheme || !schemes.includes(this._selectedScheme)) { - this._selectedScheme = schemes.sort()[0]; + if (!this.selectedScheme || !this.schemes.includes(this.selectedScheme)) { + this.selectedScheme = this.schemes.sort()[0]; } } - - _computeShowDownloadCommands(schemes: string[]) { - return schemes.length ? '' : 'hidden'; - } } declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts deleted file mode 100644 index 097fb0e..0000000 --- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts +++ /dev/null
@@ -1,127 +0,0 @@ -/** - * @license - * Copyright (C) 2020 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 {html} from '@polymer/polymer/lib/utils/html-tag'; - -export const htmlTemplate = html` - <style include="gr-font-styles"> - /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */ - </style> - <style include="shared-styles"> - :host { - display: block; - padding: var(--spacing-m) 0; - } - section { - display: flex; - padding: var(--spacing-m) var(--spacing-xl); - } - .flexContainer { - display: flex; - justify-content: space-between; - padding-top: var(--spacing-m); - } - .footer { - justify-content: flex-end; - } - .closeButtonContainer { - align-items: flex-end; - display: flex; - flex: 0; - justify-content: flex-end; - } - .patchFiles, - .archivesContainer { - padding-bottom: var(--spacing-m); - } - .patchFiles { - margin-right: var(--spacing-xxl); - } - .patchFiles a, - .archives a { - display: inline-block; - margin-right: var(--spacing-l); - } - .patchFiles a:last-of-type, - .archives a:last-of-type { - margin-right: 0; - } - .hidden { - display: none; - } - gr-download-commands { - width: min(80vw, 1200px); - } - </style> - <section> - <h3 class="heading-3"> - Patch set [[patchNum]] of [[_computePatchSetQuantity(change.revisions)]] - </h3> - </section> - <section class$="[[_computeShowDownloadCommands(_schemes)]]"> - <gr-download-commands - id="downloadCommands" - commands="[[_computeDownloadCommands(change, patchNum, _selectedScheme)]]" - schemes="[[_schemes]]" - selected-scheme="{{_selectedScheme}}" - show-keyboard-shortcut-tooltips - ></gr-download-commands> - </section> - <section class="flexContainer"> - <div - class="patchFiles" - hidden="[[_computeHidePatchFile(change, patchNum)]]" - > - <label>Patch file</label> - <div> - <a - id="download" - href$="[[_computeDownloadLink(change, patchNum)]]" - download="" - > - [[_computeDownloadFilename(change, patchNum)]] - </a> - <a href$="[[_computeZipDownloadLink(change, patchNum)]]" download=""> - [[_computeZipDownloadFilename(change, patchNum)]] - </a> - </div> - </div> - <div - class="archivesContainer" - hidden$="[[!config.archives.length]]" - hidden="" - > - <label>Archive</label> - <div id="archives" class="archives"> - <template is="dom-repeat" items="[[config.archives]]" as="format"> - <a - href$="[[_computeArchiveDownloadLink(change, patchNum, format)]]" - download="" - > - [[format]] - </a> - </template> - </div> - </div> - </section> - <section class="footer"> - <span class="closeButtonContainer"> - <gr-button id="closeButton" link="" on-click="_handleCloseTap" - >Close</gr-button - > - </span> - </section> -`;
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts index f61bb68..4c5a3ee 100644 --- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts +++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
@@ -22,7 +22,6 @@ createCommit, createDownloadInfo, createRevision, - createRevisions, } from '../../../test/test-data-generators'; import { CommitId, @@ -30,8 +29,10 @@ PatchSetNum, RepoName, } from '../../../types/common'; +import './gr-download-dialog'; import {GrDownloadDialog} from './gr-download-dialog'; -import {mockPromise} from '../../../test/test-utils'; +import {mockPromise, queryAll, queryAndAssert} from '../../../test/test-utils'; +import {GrDownloadCommands} from '../../shared/gr-download-commands/gr-download-commands'; const basicFixture = fixtureFromElement('gr-download-dialog'); @@ -103,37 +104,43 @@ }; } -function getChangeObjectNoFetch() { - return { - ...createChange(), - current_revision: '34685798fe548b6d17d1e8e5edc43a26d055cc72' as CommitId, - revisions: createRevisions(1), - }; -} - suite('gr-download-dialog', () => { let element: GrDownloadDialog; - setup(() => { + setup(async () => { element = basicFixture.instantiate(); element.patchNum = 1 as PatchSetNum; element.config = createDownloadInfo(); - flush(); + await element.updateComplete; }); test('anchors use download attribute', () => { - const anchors = Array.from(element.root!.querySelectorAll('a')); + const anchors = Array.from(queryAll(element, 'a')); assert.isTrue(!anchors.some(a => !a.hasAttribute('download'))); }); suite('gr-download-dialog tests with no fetch options', () => { - setup(() => { - element.change = getChangeObjectNoFetch(); - flush(); + setup(async () => { + element.change = { + ...createChange(), + revisions: { + r1: { + ...createRevision(), + commit: { + ...createCommit(), + parents: [{commit: 'p1' as CommitId, subject: 'subject1'}], + }, + }, + }, + }; + await element.updateComplete; }); test('focuses on first download link if no copy links', () => { - const focusStub = sinon.stub(element.$.download, 'focus'); + const focusStub = sinon.stub( + queryAndAssert<HTMLAnchorElement>(element, '#download'), + 'focus' + ); element.focus(); assert.isTrue(focusStub.called); focusStub.restore(); @@ -141,30 +148,31 @@ }); suite('gr-download-dialog with fetch options', () => { - setup(() => { + setup(async () => { element.change = getChangeObject(); - flush(); + await element.updateComplete; }); - test('focuses on first copy link', () => { - const focusStub = sinon.stub(element.$.downloadCommands, 'focusOnCopy'); + test('focuses on first copy link', async () => { + const focusStub = sinon.stub( + queryAndAssert<GrDownloadCommands>(element, '#downloadCommands'), + 'focusOnCopy' + ); element.focus(); - flush(); + await element.updateComplete; assert.isTrue(focusStub.called); focusStub.restore(); }); test('computed fields', () => { + element.change = { + ...createChange(), + project: 'test/project' as RepoName, + _number: 123 as NumericChangeId, + }; + element.patchNum = 2 as PatchSetNum; assert.equal( - element._computeArchiveDownloadLink( - { - ...createChange(), - project: 'test/project' as RepoName, - _number: 123 as NumericChangeId, - }, - 2 as PatchSetNum, - 'tgz' - ), + element.computeArchiveDownloadLink('tgz'), '/changes/test%2Fproject~123/revisions/2/archive?format=tgz' ); }); @@ -174,7 +182,8 @@ element.addEventListener('close', () => { closeCalled.resolve(); }); - const closeButton = element.shadowRoot!.querySelector( + const closeButton = queryAndAssert( + element, '.closeButtonContainer gr-button' ); tap(closeButton!); @@ -182,23 +191,18 @@ }); }); - test('_computeShowDownloadCommands', () => { - assert.equal(element._computeShowDownloadCommands([]), 'hidden'); - assert.equal(element._computeShowDownloadCommands(['test']), ''); - }); + test('computeHidePatchFile', () => { + element.patchNum = 1 as PatchSetNum; - test('_computeHidePatchFile', () => { - const patchNum = 1 as PatchSetNum; - - const changeWithNoParent = { + element.change = { ...createChange(), revisions: { r1: {...createRevision(), commit: createCommit()}, }, }; - assert.isTrue(element._computeHidePatchFile(changeWithNoParent, patchNum)); + assert.isTrue(element.computeHidePatchFile()); - const changeWithOneParent = { + element.change = { ...createChange(), revisions: { r1: { @@ -210,11 +214,9 @@ }, }, }; - assert.isFalse( - element._computeHidePatchFile(changeWithOneParent, patchNum) - ); + assert.isFalse(element.computeHidePatchFile()); - const changeWithMultipleParents = { + element.change = { ...createChange(), revisions: { r1: { @@ -229,8 +231,6 @@ }, }, }; - assert.isTrue( - element._computeHidePatchFile(changeWithMultipleParents, patchNum) - ); + assert.isTrue(element.computeHidePatchFile()); }); });
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts index 6eb19da..64f97ca 100644 --- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts +++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -18,53 +18,44 @@ import '@polymer/paper-tabs/paper-tab'; import '@polymer/paper-tabs/paper-tabs'; import '../gr-shell-command/gr-shell-command'; -import '../../../styles/gr-paper-styles'; -import '../../../styles/shared-styles'; -import {PolymerElement} from '@polymer/polymer/polymer-element'; -import {htmlTemplate} from './gr-download-commands_html'; -import {customElement, property} from '@polymer/decorators'; -import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs'; import {getAppContext} from '../../../services/app-context'; import {queryAndAssert} from '../../../utils/common-util'; import {GrShellCommand} from '../gr-shell-command/gr-shell-command'; +import {paperStyles} from '../../../styles/gr-paper-styles'; +import {sharedStyles} from '../../../styles/shared-styles'; +import {LitElement, html, css} from 'lit'; +import {customElement, property, state} from 'lit/decorators'; +import {fire} from '../../../utils/event-util'; +import {BindValueChangeEvent} from '../../../types/events'; declare global { interface HTMLElementEventMap { 'selected-changed': CustomEvent<{value: number}>; + 'selected-scheme-changed': BindValueChangeEvent; } interface HTMLElementTagNameMap { 'gr-download-commands': GrDownloadCommands; } } -export interface GrDownloadCommands { - $: { - downloadTabs: PaperTabsElement; - }; -} - export interface Command { title: string; command: string; } @customElement('gr-download-commands') -export class GrDownloadCommands extends PolymerElement { - static get template() { - return htmlTemplate; - } - +export class GrDownloadCommands extends LitElement { // TODO(TS): maybe default to [] as only used in dom-repeat @property({type: Array}) commands?: Command[]; - @property({type: Boolean}) - _loggedIn = false; + // private but used in test + @state() loggedIn = false; @property({type: Array}) schemes: string[] = []; - @property({type: String, notify: true}) + @property({type: String}) selectedScheme?: string; @property({type: Boolean}) @@ -79,14 +70,15 @@ override connectedCallback() { super.connectedCallback(); - this._getLoggedIn().then(loggedIn => { - this._loggedIn = loggedIn; + this.restApiService.getLoggedIn().then(loggedIn => { + this.loggedIn = loggedIn; }); this.subscriptions.push( this.userModel.preferences$.subscribe(prefs => { if (prefs?.download_scheme) { // Note (issue 5180): normalize the download scheme with lower-case. this.selectedScheme = prefs.download_scheme.toLowerCase(); + fire(this, 'selected-scheme-changed', {value: this.selectedScheme}); } }) ); @@ -100,42 +92,121 @@ super.disconnectedCallback(); } + static override get styles() { + return [ + paperStyles, + sharedStyles, + css` + paper-tabs { + height: 3rem; + margin-bottom: var(--spacing-m); + --paper-tabs-selection-bar-color: var(--link-color); + } + paper-tab { + max-width: 15rem; + text-transform: uppercase; + --paper-tab-ink: var(--link-color); + } + label, + input { + display: block; + } + label { + font-weight: var(--font-weight-bold); + } + .schemes { + display: flex; + justify-content: space-between; + } + .commands { + display: flex; + flex-direction: column; + } + gr-shell-command { + margin-bottom: var(--spacing-m); + } + .hidden { + display: none; + } + `, + ]; + } + + override render() { + return html` + <div class="schemes">${this.renderDownloadTabs()}</div> + ${this.renderCommands()} + `; + } + + private renderDownloadTabs() { + if (this.schemes.length <= 1) return; + + const selectedIndex = + this.schemes.findIndex(scheme => scheme === this.selectedScheme) || 0; + return html` + <paper-tabs + id="downloadTabs" + .selected=${selectedIndex} + @selected-changed=${this.handleTabChange} + > + ${this.schemes.map(scheme => this.renderPaperTab(scheme))} + </paper-tabs> + `; + } + + private renderPaperTab(scheme: string) { + return html` <paper-tab data-scheme=${scheme}>${scheme}</paper-tab> `; + } + + private renderCommands() { + if (!this.schemes.length) return; + + return html` + <div class="commands"> + ${this.commands?.map((command, index) => + this.renderShellCommand(command, index) + )} + </div> + `; + } + + private renderShellCommand(command: Command, index: number) { + return html` + <gr-shell-command + class="${this.computeClass(command.title)}" + .label=${command.title} + .command=${command.command} + .tooltip=${this.computeTooltip(index)} + ></gr-shell-command> + `; + } + focusOnCopy() { queryAndAssert<GrShellCommand>(this, 'gr-shell-command').focusOnCopy(); } - _getLoggedIn() { - return this.restApiService.getLoggedIn(); - } - - _handleTabChange(e: CustomEvent<{value: number}>) { + private handleTabChange = (e: CustomEvent<{value: number}>) => { const scheme = this.schemes[e.detail.value]; if (scheme && scheme !== this.selectedScheme) { - this.set('selectedScheme', scheme); - if (this._loggedIn) { + this.selectedScheme = scheme; + fire(this, 'selected-scheme-changed', {value: scheme}); + if (this.loggedIn) { this.userModel.updatePreferences({ download_scheme: this.selectedScheme, }); } } - } + }; - _computeSelected(schemes: string[], selectedScheme?: string) { - return `${schemes.findIndex(scheme => scheme === selectedScheme) || 0}`; - } - - _computeShowTabs(schemes: string[]) { - return schemes.length > 1 ? '' : 'hidden'; - } - - _computeTooltip(showKeyboardShortcutTooltips: boolean, index: number) { - return index <= 4 && showKeyboardShortcutTooltips + private computeTooltip(index: number) { + return index <= 4 && this.showKeyboardShortcutTooltips ? `Keyboard shortcut: ${index + 1}` : ''; } // TODO: maybe unify with strToClassName from dom-util - _computeClass(title: string) { + private computeClass(title: string) { // Only retain [a-z] chars, so "Cherry Pick" becomes "cherrypick". return '_label_' + title.replace(/[^a-z]+/gi, '').toLowerCase(); }
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts deleted file mode 100644 index f9c08ba..0000000 --- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts +++ /dev/null
@@ -1,78 +0,0 @@ -/** - * @license - * Copyright (C) 2020 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 {html} from '@polymer/polymer/lib/utils/html-tag'; - -export const htmlTemplate = html` - <style include="gr-paper-styles"> - /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */ - </style> - <style include="shared-styles"> - paper-tabs { - height: 3rem; - margin-bottom: var(--spacing-m); - --paper-tabs-selection-bar-color: var(--link-color); - } - paper-tab { - max-width: 15rem; - text-transform: uppercase; - --paper-tab-ink: var(--link-color); - } - label, - input { - display: block; - } - label { - font-weight: var(--font-weight-bold); - } - .schemes { - display: flex; - justify-content: space-between; - } - .commands { - display: flex; - flex-direction: column; - } - gr-shell-command { - margin-bottom: var(--spacing-m); - } - .hidden { - display: none; - } - </style> - <div class="schemes"> - <paper-tabs - id="downloadTabs" - class$="[[_computeShowTabs(schemes)]]" - selected="[[_computeSelected(schemes, selectedScheme)]]" - on-selected-changed="_handleTabChange" - > - <template is="dom-repeat" items="[[schemes]]" as="scheme"> - <paper-tab data-scheme$="[[scheme]]">[[scheme]]</paper-tab> - </template> - </paper-tabs> - </div> - <div class="commands" hidden$="[[!schemes.length]]" hidden=""> - <template is="dom-repeat" items="[[commands]]" as="command" indexAs="index"> - <gr-shell-command - class$="[[_computeClass(command.title)]]" - label="[[command.title]]" - command="[[command.command]]" - tooltip="[[_computeTooltip(showKeyboardShortcutTooltips, index)]]" - ></gr-shell-command> - </template> - </div> -`;
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts index bd0ca70..eb9490f 100644 --- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
@@ -18,11 +18,12 @@ import '../../../test/common-test-setup-karma'; import './gr-download-commands'; import {GrDownloadCommands} from './gr-download-commands'; -import {isHidden, queryAndAssert, stubRestApi} from '../../../test/test-utils'; +import {query, queryAndAssert, stubRestApi} from '../../../test/test-utils'; import {createPreferences} from '../../../test/test-data-generators'; import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions'; import {GrShellCommand} from '../gr-shell-command/gr-shell-command'; import {createDefaultPreferences} from '../../../constants/constants'; +import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs'; const basicFixture = fixtureFromElement('gr-download-commands'); @@ -63,7 +64,7 @@ element.schemes = SCHEMES; element.commands = COMMANDS; element.selectedScheme = SELECTED_SCHEME; - await flush(); + await element.updateComplete; }); test('focusOnCopy', () => { @@ -75,30 +76,37 @@ assert.isTrue(focusStub.called); }); - test('element visibility', () => { - assert.isFalse(isHidden(queryAndAssert(element, 'paper-tabs'))); - assert.isFalse(isHidden(queryAndAssert(element, '.commands'))); + test('element visibility', async () => { + assert.isTrue(Boolean(query(element, 'paper-tabs'))); + assert.isTrue(Boolean(query(element, '.commands'))); element.schemes = []; - assert.isTrue(isHidden(queryAndAssert(element, 'paper-tabs'))); - assert.isTrue(isHidden(queryAndAssert(element, '.commands'))); + await element.updateComplete; + assert.isFalse(Boolean(query(element, 'paper-tabs'))); + assert.isFalse(Boolean(query(element, '.commands'))); }); - test('tab selection', () => { - assert.equal(element.$.downloadTabs.selected, '0'); + test('tab selection', async () => { + assert.equal( + queryAndAssert<PaperTabsElement>(element, '#downloadTabs').selected, + '0' + ); MockInteractions.tap(queryAndAssert(element, '[data-scheme="ssh"]')); - flush(); + await element.updateComplete; assert.equal(element.selectedScheme, 'ssh'); - assert.equal(element.$.downloadTabs.selected, '2'); + assert.equal( + queryAndAssert<PaperTabsElement>(element, '#downloadTabs').selected, + '2' + ); }); - test('saves scheme to preferences', () => { - element._loggedIn = true; + test('saves scheme to preferences', async () => { + element.loggedIn = true; const savePrefsStub = stubRestApi('savePreferences').returns( Promise.resolve(createDefaultPreferences()) ); - flush(); + await element.updateComplete; const repoTab = queryAndAssert(element, 'paper-tab[data-scheme="repo"]'); @@ -114,7 +122,7 @@ suite('authenticated', () => { test('loads scheme from preferences', async () => { const element = basicFixture.instantiate(); - await flush(); + await element.updateComplete; element.userModel.setPreferences({ ...createPreferences(), download_scheme: 'repo', @@ -124,7 +132,7 @@ test('normalize scheme from preferences', async () => { const element = basicFixture.instantiate(); - await flush(); + await element.updateComplete; element.userModel.setPreferences({ ...createPreferences(), download_scheme: 'REPO',