| /** |
| * @license |
| * Copyright (C) 2019 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 '../styles/themes/app-theme'; |
| import '../styles/themes/dark-theme'; |
| import {applyTheme as applyDarkTheme} from '../styles/themes/dark-theme'; |
| import './admin/gr-admin-view/gr-admin-view'; |
| import './documentation/gr-documentation-search/gr-documentation-search'; |
| import './change-list/gr-change-list-view/gr-change-list-view'; |
| import './change-list/gr-dashboard-view/gr-dashboard-view'; |
| import './change/gr-change-view/gr-change-view'; |
| import './core/gr-error-manager/gr-error-manager'; |
| import './core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog'; |
| import './core/gr-main-header/gr-main-header'; |
| import './core/gr-smart-search/gr-smart-search'; |
| import './diff/gr-diff-view/gr-diff-view'; |
| import './edit/gr-editor-view/gr-editor-view'; |
| import './plugins/gr-endpoint-decorator/gr-endpoint-decorator'; |
| import './plugins/gr-endpoint-param/gr-endpoint-param'; |
| import './plugins/gr-endpoint-slot/gr-endpoint-slot'; |
| import './plugins/gr-external-style/gr-external-style'; |
| import './plugins/gr-plugin-host/gr-plugin-host'; |
| import './settings/gr-cla-view/gr-cla-view'; |
| import './settings/gr-registration-dialog/gr-registration-dialog'; |
| import './settings/gr-settings-view/gr-settings-view'; |
| import {loginUrl} from '../utils/url-util'; |
| import {Shortcut} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin'; |
| import {GerritNav} from './core/gr-navigation/gr-navigation'; |
| import {getAppContext} from '../services/app-context'; |
| import {GrRouter} from './core/gr-router/gr-router'; |
| import {AccountDetailInfo, ServerInfo} from '../types/common'; |
| import { |
| constructServerErrorMsg, |
| GrErrorManager, |
| } from './core/gr-error-manager/gr-error-manager'; |
| import {GrOverlay} from './shared/gr-overlay/gr-overlay'; |
| import {GrRegistrationDialog} from './settings/gr-registration-dialog/gr-registration-dialog'; |
| import { |
| AppElementJustRegisteredParams, |
| AppElementParams, |
| AppElementPluginScreenParams, |
| AppElementSearchParam, |
| isAppElementJustRegisteredParams, |
| } from './gr-app-types'; |
| import {GrMainHeader} from './core/gr-main-header/gr-main-header'; |
| import {GrSettingsView} from './settings/gr-settings-view/gr-settings-view'; |
| import { |
| DialogChangeEventDetail, |
| EventType, |
| LocationChangeEvent, |
| PageErrorEventDetail, |
| RpcLogEvent, |
| TitleChangeEventDetail, |
| ValueChangedEvent, |
| } from '../types/events'; |
| import {ChangeListViewState, ChangeViewState, ViewState} from '../types/types'; |
| import {GerritView} from '../services/router/router-model'; |
| import {LifeCycle} from '../constants/reporting'; |
| import {fireIronAnnounce} from '../utils/event-util'; |
| import {resolve} from '../models/dependency'; |
| import {browserModelToken} from '../models/browser/browser-model'; |
| import {sharedStyles} from '../styles/shared-styles'; |
| import {LitElement, PropertyValues, html, css, nothing} from 'lit'; |
| import {customElement, property, query, state} from 'lit/decorators'; |
| import {ShortcutController} from './lit/shortcut-controller'; |
| import {cache} from 'lit/directives/cache'; |
| import {assertIsDefined} from '../utils/common-util'; |
| import './gr-css-mixins'; |
| |
| interface ErrorInfo { |
| text: string; |
| emoji?: string; |
| moreInfo?: string; |
| } |
| |
| // TODO(TS): implement AppElement interface from gr-app-types.ts |
| @customElement('gr-app-element') |
| export class GrAppElement extends LitElement { |
| /** |
| * Fired when the URL location changes. |
| * |
| * @event location-change |
| */ |
| |
| @query('#errorManager') errorManager?: GrErrorManager; |
| |
| @query('#errorView') errorView?: HTMLDivElement; |
| |
| @query('#mainHeader') mainHeader?: GrMainHeader; |
| |
| @query('#registrationOverlay') registrationOverlay?: GrOverlay; |
| |
| @query('#registrationDialog') registrationDialog?: GrRegistrationDialog; |
| |
| @query('#keyboardShortcuts') keyboardShortcuts?: GrOverlay; |
| |
| @query('gr-settings-view') settingdView?: GrSettingsView; |
| |
| @property({type: Object}) |
| params?: AppElementParams; |
| |
| @state() private account?: AccountDetailInfo; |
| |
| @state() private serverConfig?: ServerInfo; |
| |
| @state() private version?: string; |
| |
| @state() private showChangeListView?: boolean; |
| |
| @state() private showDashboardView?: boolean; |
| |
| @state() private showChangeView?: boolean; |
| |
| @state() private showDiffView?: boolean; |
| |
| @state() private showSettingsView?: boolean; |
| |
| @state() private showAdminView?: boolean; |
| |
| @state() private showCLAView?: boolean; |
| |
| @state() private showEditorView?: boolean; |
| |
| @state() private showPluginScreen?: boolean; |
| |
| @state() private showDocumentationSearch?: boolean; |
| |
| @state() private viewState?: ViewState; |
| |
| @state() private lastError?: ErrorInfo; |
| |
| // private but used in test |
| @state() lastSearchPage?: string; |
| |
| @state() private path?: string; |
| |
| @state() private settingsUrl?: string; |
| |
| @state() private mobileSearch = false; |
| |
| @state() private loadRegistrationDialog = false; |
| |
| @state() private loadKeyboardShortcutsDialog = false; |
| |
| // TODO(milutin) - remove once new gr-dialog will do it out of the box |
| // This removes footer, header from a11y tree, when a dialog on view |
| // (e.g. reply dialog) is open |
| @state() private footerHeaderAriaHidden = false; |
| |
| // TODO(milutin) - remove once new gr-dialog will do it out of the box |
| // This removes main page from a11y tree, when a dialog on gr-app-element |
| // (e.g. shortcut dialog) is open |
| @state() private mainAriaHidden = false; |
| |
| // Triggers dom-if unsetting/setting restamp behaviour in lit |
| @state() private invalidateChangeViewCache = false; |
| |
| // Triggers dom-if unsetting/setting restamp behaviour in lit |
| @state() private invalidateDiffViewCache = false; |
| |
| readonly router = new GrRouter(); |
| |
| private reporting = getAppContext().reportingService; |
| |
| private readonly restApiService = getAppContext().restApiService; |
| |
| private readonly getBrowserModel = resolve(this, browserModelToken); |
| |
| private readonly shortcuts = new ShortcutController(this); |
| |
| constructor() { |
| super(); |
| document.addEventListener(EventType.PAGE_ERROR, e => { |
| this.handlePageError(e); |
| }); |
| this.addEventListener(EventType.TITLE_CHANGE, e => { |
| this.handleTitleChange(e); |
| }); |
| this.addEventListener(EventType.DIALOG_CHANGE, e => { |
| this.handleDialogChange(e as CustomEvent<DialogChangeEventDetail>); |
| }); |
| document.addEventListener(EventType.LOCATION_CHANGE, e => |
| this.handleLocationChange(e) |
| ); |
| this.addEventListener(EventType.RECREATE_CHANGE_VIEW, () => |
| this.handleRecreateView() |
| ); |
| this.addEventListener(EventType.RECREATE_DIFF_VIEW, () => |
| this.handleRecreateView() |
| ); |
| document.addEventListener(EventType.GR_RPC_LOG, e => this.handleRpcLog(e)); |
| this.shortcuts.addAbstract(Shortcut.OPEN_SHORTCUT_HELP_DIALOG, () => |
| this.showKeyboardShortcuts() |
| ); |
| this.shortcuts.addAbstract(Shortcut.GO_TO_USER_DASHBOARD, () => |
| this.goToUserDashboard() |
| ); |
| this.shortcuts.addAbstract(Shortcut.GO_TO_OPENED_CHANGES, () => |
| this.goToOpenedChanges() |
| ); |
| this.shortcuts.addAbstract(Shortcut.GO_TO_MERGED_CHANGES, () => |
| this.goToMergedChanges() |
| ); |
| this.shortcuts.addAbstract(Shortcut.GO_TO_ABANDONED_CHANGES, () => |
| this.goToAbandonedChanges() |
| ); |
| this.shortcuts.addAbstract(Shortcut.GO_TO_WATCHED_CHANGES, () => |
| this.goToWatchedChanges() |
| ); |
| } |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| const resizeObserver = this.getBrowserModel().observeWidth(); |
| resizeObserver.observe(this); |
| |
| this.reporting.appStarted(); |
| this.router.start(); |
| |
| this.restApiService.getAccount().then(account => { |
| this.account = account; |
| if (account) { |
| this.reporting.reportLifeCycle(LifeCycle.STARTED_AS_USER); |
| } else { |
| this.reporting.reportLifeCycle(LifeCycle.STARTED_AS_GUEST); |
| } |
| }); |
| this.restApiService.getConfig().then(config => { |
| this.serverConfig = config; |
| }); |
| this.restApiService.getVersion().then(version => { |
| this.version = version; |
| this.logWelcome(); |
| }); |
| |
| const isDarkTheme = !!window.localStorage.getItem('dark-theme'); |
| document.documentElement.classList.toggle('darkTheme', isDarkTheme); |
| document.documentElement.classList.toggle('lightTheme', !isDarkTheme); |
| if (isDarkTheme) applyDarkTheme(); |
| |
| // Note: this is evaluated here to ensure that it only happens after the |
| // router has been initialized. @see Issue 7837 |
| this.settingsUrl = GerritNav.getUrlForSettings(); |
| |
| this.viewState = { |
| changeView: { |
| changeNum: null, |
| patchRange: null, |
| selectedFileIndex: 0, |
| showReplyDialog: false, |
| diffMode: null, |
| numFilesShown: null, |
| }, |
| changeListView: { |
| query: null, |
| offset: 0, |
| selectedChangeIndex: 0, |
| }, |
| dashboardView: {}, |
| }; |
| } |
| |
| static override get styles() { |
| return [ |
| sharedStyles, |
| css` |
| :host { |
| background-color: var(--background-color-tertiary); |
| display: flex; |
| flex-direction: column; |
| min-height: 100%; |
| } |
| gr-main-header, |
| footer { |
| color: var(--primary-text-color); |
| } |
| gr-main-header { |
| background: var( |
| --header-background, |
| var(--header-background-color, #eee) |
| ); |
| padding: var(--header-padding); |
| border-bottom: var(--header-border-bottom); |
| border-image: var(--header-border-image); |
| border-right: 0; |
| border-left: 0; |
| border-top: 0; |
| box-shadow: var(--header-box-shadow); |
| /* Make sure the header is above the main content, to preserve box-shadow |
| visibility. We need 2 here instead of 1, because dropdowns in the |
| header should be shown on top of the sticky diff header, which has a |
| z-index of 1. */ |
| z-index: 2; |
| } |
| footer { |
| background: var( |
| --footer-background, |
| var(--footer-background-color, #eee) |
| ); |
| border-top: var(--footer-border-top); |
| display: flex; |
| justify-content: space-between; |
| padding: var(--spacing-m) var(--spacing-l); |
| z-index: 100; |
| } |
| main { |
| flex: 1; |
| padding-bottom: var(--spacing-xxl); |
| position: relative; |
| } |
| .errorView { |
| align-items: center; |
| display: none; |
| flex-direction: column; |
| justify-content: center; |
| position: absolute; |
| top: 0; |
| right: 0; |
| bottom: 0; |
| left: 0; |
| } |
| .errorView.show { |
| display: flex; |
| } |
| .errorEmoji { |
| font-size: 2.6rem; |
| } |
| .errorText, |
| .errorMoreInfo { |
| margin-top: var(--spacing-m); |
| } |
| .errorText { |
| font-family: var(--header-font-family); |
| font-size: var(--font-size-h3); |
| font-weight: var(--font-weight-h3); |
| line-height: var(--line-height-h3); |
| } |
| .errorMoreInfo { |
| color: var(--deemphasized-text-color); |
| } |
| `, |
| ]; |
| } |
| |
| override render() { |
| return html` |
| <gr-css-mixins></gr-css-mixins> |
| <gr-endpoint-decorator name="banner"></gr-endpoint-decorator> |
| <gr-main-header |
| id="mainHeader" |
| .searchQuery=${(this.params as AppElementSearchParam)?.query} |
| @mobile-search=${this.mobileSearchToggle} |
| @show-keyboard-shortcuts=${this.handleShowKeyboardShortcuts} |
| .mobileSearchHidden=${!this.mobileSearch} |
| .loginUrl=${loginUrl(this.serverConfig?.auth)} |
| .loginText=${this.serverConfig?.auth.login_text ?? 'Sign in'} |
| ?aria-hidden=${this.footerHeaderAriaHidden} |
| > |
| </gr-main-header> |
| <main ?aria-hidden=${this.mainAriaHidden}> |
| ${this.renderMobileSearch()} ${this.renderChangeListView()} |
| ${this.renderDashboardView()} ${this.renderChangeView()} |
| ${this.renderEditorView()} ${this.renderDiffView()} |
| ${this.renderSettingsView()} ${this.renderAdminView()} |
| ${this.renderPluginScreen()} ${this.renderCLAView()} |
| ${this.renderDocumentationSearch()} |
| <div id="errorView" class="errorView"> |
| <div class="errorEmoji">${this.lastError?.emoji}</div> |
| <div class="errorText">${this.lastError?.text}</div> |
| <div class="errorMoreInfo">${this.lastError?.moreInfo}</div> |
| </div> |
| </main> |
| <footer ?aria-hidden=${this.footerHeaderAriaHidden}> |
| <div> |
| Powered by |
| <a |
| href="https://www.gerritcodereview.com/" |
| rel="noopener" |
| target="_blank" |
| >Gerrit Code Review</a |
| > |
| (${this.version}) |
| <gr-endpoint-decorator name="footer-left"></gr-endpoint-decorator> |
| </div> |
| <div> |
| Press “?” for keyboard shortcuts |
| <gr-endpoint-decorator name="footer-right"></gr-endpoint-decorator> |
| </div> |
| </footer> |
| ${this.renderKeyboardShortcutsDialog()} ${this.renderRegistrationDialog()} |
| <gr-endpoint-decorator name="plugin-overlay"></gr-endpoint-decorator> |
| <gr-error-manager |
| id="errorManager" |
| .loginUrl=${loginUrl(this.serverConfig?.auth)} |
| .loginText=${this.serverConfig?.auth.login_text ?? 'Sign in'} |
| ></gr-error-manager> |
| <gr-plugin-host id="plugins" .config=${this.serverConfig}> |
| </gr-plugin-host> |
| <gr-external-style |
| id="externalStyleForAll" |
| name="app-theme" |
| ></gr-external-style> |
| <gr-external-style |
| id="externalStyleForTheme" |
| .name=${this.getThemeEndpoint()} |
| ></gr-external-style> |
| `; |
| } |
| |
| private renderMobileSearch() { |
| if (!this.mobileSearch) return nothing; |
| return html` |
| <gr-smart-search |
| id="search" |
| label="Search for changes" |
| .searchQuery=${(this.params as AppElementSearchParam)?.query} |
| .serverConfig=${this.serverConfig} |
| > |
| </gr-smart-search> |
| `; |
| } |
| |
| private renderChangeListView() { |
| if (!this.showChangeListView) return nothing; |
| return html` |
| <gr-change-list-view |
| .params=${this.params} |
| .account=${this.account} |
| .viewState=${this.viewState?.changeListView} |
| @view-state-change-list-view-changed=${this.handleViewStateChanged} |
| ></gr-change-list-view> |
| `; |
| } |
| |
| private renderDashboardView() { |
| if (!this.showDashboardView) return nothing; |
| return html` |
| <gr-dashboard-view |
| .account=${this.account} |
| .params=${this.params} |
| .viewState=${this.viewState?.dashboardView} |
| ></gr-dashboard-view> |
| `; |
| } |
| |
| private renderChangeView() { |
| if (this.invalidateChangeViewCache) { |
| this.updateComplete.then(() => (this.invalidateChangeViewCache = false)); |
| return nothing; |
| } |
| return cache(this.showChangeView ? this.changeViewTemplate() : nothing); |
| } |
| |
| // Template as not to create duplicates, for renderChangeView() only. |
| private changeViewTemplate() { |
| return html` |
| <gr-change-view |
| .params=${this.params} |
| .viewState=${this.viewState?.changeView} |
| .backPage=${this.lastSearchPage} |
| @view-state-change-view-changed=${this.handleViewStateChangeViewChanged} |
| ></gr-change-view> |
| `; |
| } |
| |
| private renderEditorView() { |
| if (!this.showEditorView) return nothing; |
| return html`<gr-editor-view .params=${this.params}></gr-editor-view>`; |
| } |
| |
| private renderDiffView() { |
| if (this.invalidateDiffViewCache) { |
| this.updateComplete.then(() => (this.invalidateDiffViewCache = false)); |
| return nothing; |
| } |
| return cache(this.showDiffView ? this.diffViewTemplate() : nothing); |
| } |
| |
| private diffViewTemplate() { |
| return html` |
| <gr-diff-view |
| .params=${this.params} |
| .changeViewState=${this.viewState?.changeView} |
| @view-state-change-view-changed=${this.handleViewStateChangeViewChanged} |
| ></gr-diff-view> |
| `; |
| } |
| |
| private renderSettingsView() { |
| if (!this.showSettingsView) return nothing; |
| return html` |
| <gr-settings-view |
| .params=${this.params} |
| @account-detail-update=${this.handleAccountDetailUpdate} |
| > |
| </gr-settings-view> |
| `; |
| } |
| |
| private renderAdminView() { |
| if (!this.showAdminView) return nothing; |
| return html`<gr-admin-view |
| .path=${this.path} |
| .params=${this.params} |
| ></gr-admin-view>`; |
| } |
| |
| private renderPluginScreen() { |
| if (!this.showPluginScreen) return nothing; |
| return html` |
| <gr-endpoint-decorator .name=${this.computePluginScreenName()}> |
| <gr-endpoint-param |
| name="token" |
| .value=${(this.params as AppElementPluginScreenParams).screen} |
| ></gr-endpoint-param> |
| </gr-endpoint-decorator> |
| `; |
| } |
| |
| private renderCLAView() { |
| if (!this.showCLAView) return nothing; |
| return html`<gr-cla-view></gr-cla-view>`; |
| } |
| |
| private renderDocumentationSearch() { |
| if (!this.showDocumentationSearch) return nothing; |
| return html` |
| <gr-documentation-search .params=${this.params}></gr-documentation-search> |
| `; |
| } |
| |
| private renderKeyboardShortcutsDialog() { |
| if (!this.loadKeyboardShortcutsDialog) return nothing; |
| return html` |
| <gr-overlay |
| id="keyboardShortcuts" |
| with-backdrop="" |
| @iron-overlay-canceled=${this.onOverlayCanceled} |
| > |
| <gr-keyboard-shortcuts-dialog |
| @close=${this.handleKeyboardShortcutDialogClose} |
| ></gr-keyboard-shortcuts-dialog> |
| </gr-overlay> |
| `; |
| } |
| |
| private renderRegistrationDialog() { |
| if (!this.loadRegistrationDialog) return nothing; |
| return html` |
| <gr-overlay id="registrationOverlay" with-backdrop=""> |
| <gr-registration-dialog |
| id="registrationDialog" |
| .settingsUrl=${this.settingsUrl} |
| @account-detail-update=${this.handleAccountDetailUpdate} |
| @close=${this.handleRegistrationDialogClose} |
| > |
| </gr-registration-dialog> |
| </gr-overlay> |
| `; |
| } |
| |
| override willUpdate(changedProperties: PropertyValues) { |
| if (changedProperties.has('account')) { |
| this.accountChanged(); |
| } |
| |
| if (changedProperties.has('params')) { |
| this.viewChanged(); |
| |
| this.paramsChanged(); |
| } |
| } |
| |
| private accountChanged() { |
| if (!this.account) return; |
| |
| // Preferences are cached when a user is logged in; warm them. |
| this.restApiService.getPreferences(); |
| this.restApiService.getDiffPreferences(); |
| this.restApiService.getEditPreferences(); |
| if (this.errorManager) |
| this.errorManager.knownAccountId = |
| (this.account && this.account._account_id) || null; |
| } |
| |
| /** |
| * Throws away the view and re-creates it. The view itself fires an event, if |
| * it wants to be re-created. |
| */ |
| private handleRecreateView() { |
| this.invalidateChangeViewCache = true; |
| this.invalidateDiffViewCache = true; |
| } |
| |
| private async viewChanged() { |
| const view = this.params?.view; |
| this.errorView?.classList.remove('show'); |
| this.showChangeListView = view === GerritView.SEARCH; |
| this.showDashboardView = view === GerritView.DASHBOARD; |
| this.showChangeView = view === GerritView.CHANGE; |
| this.showDiffView = view === GerritView.DIFF; |
| this.showSettingsView = view === GerritView.SETTINGS; |
| // showAdminView must be in sync with the gr-admin-view AdminViewParams type |
| this.showAdminView = |
| view === GerritView.ADMIN || |
| view === GerritView.GROUP || |
| view === GerritView.REPO; |
| this.showCLAView = view === GerritView.AGREEMENTS; |
| this.showEditorView = view === GerritView.EDIT; |
| const isPluginScreen = view === GerritView.PLUGIN_SCREEN; |
| this.showPluginScreen = false; |
| // Navigation within plugin screens does not restamp gr-endpoint-decorator |
| // because showPluginScreen value does not change. To force restamp, |
| // change showPluginScreen value between true and false. |
| if (isPluginScreen) { |
| setTimeout(() => (this.showPluginScreen = true), 1); |
| } |
| this.showDocumentationSearch = view === GerritView.DOCUMENTATION_SEARCH; |
| if ( |
| this.params && |
| isAppElementJustRegisteredParams(this.params) && |
| this.params.justRegistered |
| ) { |
| this.loadRegistrationDialog = true; |
| await this.updateComplete; |
| assertIsDefined(this.registrationOverlay, 'registrationOverlay'); |
| assertIsDefined(this.registrationDialog, 'registrationDialog'); |
| this.registrationOverlay.open(); |
| this.registrationDialog.loadData().then(() => { |
| this.registrationOverlay!.refit(); |
| }); |
| } |
| // To fix bug announce read after each new view, we reset announce with |
| // empty space |
| fireIronAnnounce(this, ' '); |
| } |
| |
| private handlePageError(e: CustomEvent<PageErrorEventDetail>) { |
| const props = [ |
| 'showChangeListView', |
| 'showDashboardView', |
| 'showChangeView', |
| 'showDiffView', |
| 'showSettingsView', |
| 'showAdminView', |
| ]; |
| for (const showProp of props) { |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| (this as any)[showProp as any] = false; |
| } |
| |
| this.errorView?.classList.add('show'); |
| const response = e.detail.response; |
| const err: ErrorInfo = { |
| text: [response?.status, response?.statusText].join(' '), |
| }; |
| if (response?.status === 404) { |
| err.emoji = '¯\\_(ツ)_/¯'; |
| this.lastError = err; |
| } else { |
| err.emoji = 'o_O'; |
| if (response) { |
| response.text().then(text => { |
| const trace = |
| response.headers && response.headers.get('X-Gerrit-Trace'); |
| const {status, statusText} = response; |
| err.moreInfo = constructServerErrorMsg({ |
| status, |
| statusText, |
| errorText: text, |
| trace, |
| }); |
| this.lastError = err; |
| }); |
| } |
| } |
| } |
| |
| private handleLocationChange(e: LocationChangeEvent) { |
| this.requestUpdate(); |
| const hash = e.detail.hash.substring(1); |
| let pathname = e.detail.pathname; |
| if (pathname.startsWith('/c/') && Number(hash) > 0) { |
| pathname += '@' + hash; |
| } |
| this.path = pathname; |
| } |
| |
| // private but used in test |
| paramsChanged() { |
| const viewsToCheck = [GerritView.SEARCH, GerritView.DASHBOARD]; |
| if (this.params?.view && viewsToCheck.includes(this.params.view)) { |
| this.lastSearchPage = location.pathname; |
| } |
| } |
| |
| private handleTitleChange(e: CustomEvent<TitleChangeEventDetail>) { |
| if (e.detail.title) { |
| document.title = e.detail.title + ' · Gerrit Code Review'; |
| } else { |
| document.title = ''; |
| } |
| } |
| |
| private handleDialogChange(e: CustomEvent<DialogChangeEventDetail>) { |
| if (e.detail.canceled) { |
| this.footerHeaderAriaHidden = false; |
| } else if (e.detail.opened) { |
| this.footerHeaderAriaHidden = true; |
| } |
| } |
| |
| private async handleShowKeyboardShortcuts() { |
| this.loadKeyboardShortcutsDialog = true; |
| await this.updateComplete; |
| assertIsDefined(this.keyboardShortcuts, 'keyboardShortcuts'); |
| this.keyboardShortcuts.open(); |
| } |
| |
| private async showKeyboardShortcuts() { |
| // same shortcut should close the dialog if pressed again |
| // when dialog is open |
| this.loadKeyboardShortcutsDialog = true; |
| await this.updateComplete; |
| if (!this.keyboardShortcuts) return; |
| if (this.keyboardShortcuts.opened) { |
| this.keyboardShortcuts.cancel(); |
| return; |
| } |
| this.keyboardShortcuts.open(); |
| this.footerHeaderAriaHidden = true; |
| this.mainAriaHidden = true; |
| } |
| |
| private handleKeyboardShortcutDialogClose() { |
| assertIsDefined(this.keyboardShortcuts, 'keyboardShortcuts'); |
| this.keyboardShortcuts.close(); |
| } |
| |
| onOverlayCanceled() { |
| this.footerHeaderAriaHidden = false; |
| this.mainAriaHidden = false; |
| } |
| |
| private handleAccountDetailUpdate() { |
| this.mainHeader?.reload(); |
| if (this.params?.view === GerritView.SETTINGS) { |
| assertIsDefined(this.settingdView, 'settingdView'); |
| this.settingdView.reloadAccountDetail(); |
| } |
| } |
| |
| private handleRegistrationDialogClose() { |
| // The registration dialog is visible only if this.params is |
| // instanceof AppElementJustRegisteredParams |
| (this.params as AppElementJustRegisteredParams).justRegistered = false; |
| assertIsDefined(this.registrationOverlay, 'registrationOverlay'); |
| this.registrationOverlay.close(); |
| } |
| |
| private goToOpenedChanges() { |
| GerritNav.navigateToStatusSearch('open'); |
| } |
| |
| private goToUserDashboard() { |
| GerritNav.navigateToUserDashboard(); |
| } |
| |
| private goToMergedChanges() { |
| GerritNav.navigateToStatusSearch('merged'); |
| } |
| |
| private goToAbandonedChanges() { |
| GerritNav.navigateToStatusSearch('abandoned'); |
| } |
| |
| private goToWatchedChanges() { |
| // The query is hardcoded, and doesn't respect custom menu entries |
| GerritNav.navigateToSearchQuery('is:watched is:open'); |
| } |
| |
| private computePluginScreenName() { |
| if (this.params?.view !== GerritView.PLUGIN_SCREEN) return ''; |
| if (!this.params.plugin || !this.params.screen) return ''; |
| return `${this.params.plugin}-screen-${this.params.screen}`; |
| } |
| |
| private logWelcome() { |
| console.group('Runtime Info'); |
| console.info('Gerrit UI (PolyGerrit)'); |
| console.info(`Gerrit Server Version: ${this.version}`); |
| if (window.VERSION_INFO) { |
| console.info(`UI Version Info: ${window.VERSION_INFO}`); |
| } |
| console.groupEnd(); |
| } |
| |
| /** |
| * Intercept RPC log events emitted by REST API interfaces. |
| * Note: the REST API interface cannot use gr-reporting directly because |
| * that would create a cyclic dependency. |
| */ |
| private handleRpcLog(e: RpcLogEvent) { |
| this.reporting.reportRpcTiming(e.detail.anonymizedUrl, e.detail.elapsed); |
| } |
| |
| private mobileSearchToggle() { |
| this.mobileSearch = !this.mobileSearch; |
| } |
| |
| getThemeEndpoint() { |
| // For now, we only have dark mode and light mode |
| return window.localStorage.getItem('dark-theme') |
| ? 'app-theme-dark' |
| : 'app-theme-light'; |
| } |
| |
| private handleViewStateChanged(e: ValueChangedEvent<ChangeListViewState>) { |
| if (!this.viewState) return; |
| this.viewState.changeListView = { |
| ...this.viewState.changeListView, |
| ...e.detail.value, |
| }; |
| } |
| |
| private handleViewStateChangeViewChanged( |
| e: ValueChangedEvent<ChangeViewState> |
| ) { |
| if (!this.viewState) return; |
| this.viewState.changeView = { |
| ...this.viewState.changeView, |
| ...e.detail.value, |
| }; |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-app-element': GrAppElement; |
| } |
| } |