blob: 886894e9a200dd8d19f572030f1e89ee1d070941 [file] [log] [blame]
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Subscription} from 'rxjs';
import '@polymer/paper-tabs/paper-tab';
import '@polymer/paper-tabs/paper-tabs';
import '../gr-shell-command/gr-shell-command';
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.js';
import {fire} from '../../../utils/event-util';
import {BindValueChangeEvent} from '../../../types/events';
import {resolve} from '../../../models/dependency';
import {userModelToken} from '../../../models/user/user-model';
declare global {
interface HTMLElementEventMap {
'selected-changed': CustomEvent<{value: number}>;
'selected-scheme-changed': BindValueChangeEvent;
}
interface HTMLElementTagNameMap {
'gr-download-commands': GrDownloadCommands;
}
}
export interface Command {
title: string;
command: string;
}
@customElement('gr-download-commands')
export class GrDownloadCommands extends LitElement {
// TODO(TS): maybe default to [] as only used in dom-repeat
@property({type: Array})
commands?: Command[];
// private but used in test
@state() loggedIn = false;
@property({type: Array})
schemes: string[] = [];
@property({type: String})
selectedScheme?: string;
@property({type: Boolean, attribute: 'show-keyboard-shortcut-tooltips'})
showKeyboardShortcutTooltips = false;
private readonly restApiService = getAppContext().restApiService;
// Private but used in tests.
readonly getUserModel = resolve(this, userModelToken);
private subscriptions: Subscription[] = [];
override connectedCallback() {
super.connectedCallback();
this.restApiService.getLoggedIn().then(loggedIn => {
this.loggedIn = loggedIn;
});
this.subscriptions.push(
this.getUserModel().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});
}
})
);
}
override disconnectedCallback() {
for (const s of this.subscriptions) {
s.unsubscribe();
}
this.subscriptions = [];
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);
--paper-font-common-base_-_font-family: var(--header-font-family);
--paper-font-common-base_-_-webkit-font-smoothing: initial;
--paper-tab-content_-_margin-bottom: var(--spacing-s);
/* paper-tabs uses 700 here, which can look awkward */
--paper-tab-content-focused_-_font-weight: var(--font-weight-h3);
--paper-tab-content-focused_-_background: var(
--gray-background-focus
);
--paper-tab-content-unselected_-_opacity: 1;
--paper-tab-content-unselected_-_color: var(
--deemphasized-text-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() {
const selectedIndex =
this.schemes.findIndex(scheme => scheme === this.selectedScheme) || 0;
return html`
<paper-tabs
id="downloadTabs"
class=${this.computeShowTabs()}
.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() {
return html`
<div class="commands" ?hidden=${!this.schemes.length}></div>
${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>
`;
}
async focusOnCopy() {
await this.updateComplete;
await queryAndAssert<GrShellCommand>(
this,
'gr-shell-command'
).focusOnCopy();
}
private handleTabChange = (e: CustomEvent<{value: number}>) => {
const scheme = this.schemes[e.detail.value];
if (scheme && scheme !== this.selectedScheme) {
this.selectedScheme = scheme;
fire(this, 'selected-scheme-changed', {value: scheme});
if (this.loggedIn) {
this.getUserModel().updatePreferences({
download_scheme: this.selectedScheme,
});
}
}
};
private computeTooltip(index: number) {
return index <= 4 && this.showKeyboardShortcutTooltips
? `Keyboard shortcut: ${index + 1}`
: '';
}
private computeShowTabs() {
return this.schemes.length > 1 ? '' : 'hidden';
}
// TODO: maybe unify with strToClassName from dom-util
private computeClass(title: string) {
// Only retain [a-z] chars, so "Cherry Pick" becomes "cherrypick".
return '_label_' + title.replace(/[^a-z]+/gi, '').toLowerCase();
}
}