
After change 290410, we have converted gr-rest-api into a service and no longer require this import. Change-Id: I7990253fc5c25bab8d9d28ee594754a2eb78947d
395 lines
9.9 KiB
TypeScript
395 lines
9.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright (C) 2016 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 '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
|
|
import '../../shared/gr-dropdown/gr-dropdown';
|
|
import '../../shared/gr-icons/gr-icons';
|
|
import '../../shared/gr-js-api-interface/gr-js-api-interface';
|
|
import '../gr-account-dropdown/gr-account-dropdown';
|
|
import '../gr-smart-search/gr-smart-search';
|
|
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
|
|
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
|
|
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
|
import {htmlTemplate} from './gr-main-header_html';
|
|
import {getBaseUrl, getDocsBaseUrl} from '../../../utils/url-util';
|
|
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
|
|
import {getAdminLinks, NavLink} from '../../../utils/admin-nav-util';
|
|
import {customElement, property, observe} from '@polymer/decorators';
|
|
import {
|
|
AccountDetailInfo,
|
|
RequireProperties,
|
|
ServerInfo,
|
|
TopMenuEntryInfo,
|
|
TopMenuItemInfo,
|
|
} from '../../../types/common';
|
|
import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types';
|
|
import {AuthType} from '../../../constants/constants';
|
|
import {DropdownLink} from '../../shared/gr-dropdown/gr-dropdown';
|
|
import {appContext} from '../../../services/app-context';
|
|
|
|
type MainHeaderLink = RequireProperties<DropdownLink, 'url' | 'name'>;
|
|
|
|
interface MainHeaderLinkGroup {
|
|
title: string;
|
|
links: MainHeaderLink[];
|
|
class?: string;
|
|
}
|
|
|
|
const DEFAULT_LINKS: MainHeaderLinkGroup[] = [
|
|
{
|
|
title: 'Changes',
|
|
links: [
|
|
{
|
|
url: '/q/status:open+-is:wip',
|
|
name: 'Open',
|
|
},
|
|
{
|
|
url: '/q/status:merged',
|
|
name: 'Merged',
|
|
},
|
|
{
|
|
url: '/q/status:abandoned',
|
|
name: 'Abandoned',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const DOCUMENTATION_LINKS: MainHeaderLink[] = [
|
|
{
|
|
url: '/index.html',
|
|
name: 'Table of Contents',
|
|
},
|
|
{
|
|
url: '/user-search.html',
|
|
name: 'Searching',
|
|
},
|
|
{
|
|
url: '/user-upload.html',
|
|
name: 'Uploading',
|
|
},
|
|
{
|
|
url: '/access-control.html',
|
|
name: 'Access Control',
|
|
},
|
|
{
|
|
url: '/rest-api.html',
|
|
name: 'REST API',
|
|
},
|
|
{
|
|
url: '/intro-project-owner.html',
|
|
name: 'Project Owner Guide',
|
|
},
|
|
];
|
|
|
|
// Set of authentication methods that can provide custom registration page.
|
|
const AUTH_TYPES_WITH_REGISTER_URL: Set<AuthType> = new Set([
|
|
AuthType.LDAP,
|
|
AuthType.LDAP_BIND,
|
|
AuthType.CUSTOM_EXTENSION,
|
|
]);
|
|
|
|
export interface GrMainHeader {
|
|
$: {
|
|
jsAPI: JsApiService & Element;
|
|
};
|
|
}
|
|
|
|
@customElement('gr-main-header')
|
|
export class GrMainHeader extends GestureEventListeners(
|
|
LegacyElementMixin(PolymerElement)
|
|
) {
|
|
static get template() {
|
|
return htmlTemplate;
|
|
}
|
|
|
|
@property({type: String, notify: true})
|
|
searchQuery?: string;
|
|
|
|
@property({type: Boolean, reflectToAttribute: true})
|
|
loggedIn?: boolean;
|
|
|
|
@property({type: Boolean, reflectToAttribute: true})
|
|
loading?: boolean;
|
|
|
|
@property({type: Object})
|
|
_account?: AccountDetailInfo;
|
|
|
|
@property({type: Array})
|
|
_adminLinks: NavLink[] = [];
|
|
|
|
@property({type: String})
|
|
_docBaseUrl: string | null = null;
|
|
|
|
@property({
|
|
type: Array,
|
|
computed: '_computeLinks(_userLinks, _adminLinks, _topMenus, _docBaseUrl)',
|
|
})
|
|
_links?: MainHeaderLinkGroup[];
|
|
|
|
@property({type: String})
|
|
loginUrl = '/login';
|
|
|
|
@property({type: Array})
|
|
_userLinks: MainHeaderLink[] = [];
|
|
|
|
@property({type: Array})
|
|
_topMenus?: TopMenuEntryInfo[] = [];
|
|
|
|
@property({type: String})
|
|
_registerText = 'Sign up';
|
|
|
|
// Empty string means that the register <div> will be hidden.
|
|
@property({type: String})
|
|
_registerURL = '';
|
|
|
|
@property({type: Boolean})
|
|
mobileSearchHidden = false;
|
|
|
|
private readonly restApiService = appContext.restApiService;
|
|
|
|
/** @override */
|
|
ready() {
|
|
super.ready();
|
|
this._ensureAttribute('role', 'banner');
|
|
}
|
|
|
|
/** @override */
|
|
attached() {
|
|
super.attached();
|
|
this._loadAccount();
|
|
this._loadConfig();
|
|
}
|
|
|
|
/** @override */
|
|
detached() {
|
|
super.detached();
|
|
}
|
|
|
|
reload() {
|
|
this._loadAccount();
|
|
}
|
|
|
|
_computeRelativeURL(path: string) {
|
|
return '//' + window.location.host + getBaseUrl() + path;
|
|
}
|
|
|
|
_computeLinks(
|
|
userLinks?: TopMenuItemInfo[],
|
|
adminLinks?: NavLink[],
|
|
topMenus?: TopMenuEntryInfo[],
|
|
docBaseUrl?: string | null,
|
|
// defaultLinks parameter is used in tests only
|
|
defaultLinks = DEFAULT_LINKS
|
|
) {
|
|
// Polymer 2: check for undefined
|
|
if (
|
|
userLinks === undefined ||
|
|
adminLinks === undefined ||
|
|
topMenus === undefined ||
|
|
docBaseUrl === undefined
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const links: MainHeaderLinkGroup[] = defaultLinks.map(menu => {
|
|
return {
|
|
title: menu.title,
|
|
links: menu.links.slice(),
|
|
};
|
|
});
|
|
if (userLinks && userLinks.length > 0) {
|
|
links.push({
|
|
title: 'Your',
|
|
links: userLinks.slice(),
|
|
});
|
|
}
|
|
const docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
|
|
if (docLinks.length) {
|
|
links.push({
|
|
title: 'Documentation',
|
|
links: docLinks,
|
|
class: 'hideOnMobile',
|
|
});
|
|
}
|
|
links.push({
|
|
title: 'Browse',
|
|
links: adminLinks.slice(),
|
|
});
|
|
const topMenuLinks: {[name: string]: MainHeaderLink[]} = {};
|
|
links.forEach(link => {
|
|
topMenuLinks[link.title] = link.links;
|
|
});
|
|
for (const m of topMenus) {
|
|
const items = m.items.map(this._createHeaderLink).filter(
|
|
link =>
|
|
// Ignore GWT project links
|
|
!link.url.includes('${projectName}')
|
|
);
|
|
if (m.name in topMenuLinks) {
|
|
items.forEach(link => {
|
|
topMenuLinks[m.name].push(link);
|
|
});
|
|
} else {
|
|
links.push({
|
|
title: m.name,
|
|
links: topMenuLinks[m.name] = items,
|
|
});
|
|
}
|
|
}
|
|
return links;
|
|
}
|
|
|
|
_getDocLinks(docBaseUrl: string | null, docLinks: MainHeaderLink[]) {
|
|
if (!docBaseUrl) {
|
|
return [];
|
|
}
|
|
return docLinks.map(link => {
|
|
let url = docBaseUrl;
|
|
if (url && url[url.length - 1] === '/') {
|
|
url = url.substring(0, url.length - 1);
|
|
}
|
|
return {
|
|
url: url + link.url,
|
|
name: link.name,
|
|
target: '_blank',
|
|
};
|
|
});
|
|
}
|
|
|
|
_loadAccount() {
|
|
this.loading = true;
|
|
|
|
return Promise.all([
|
|
this.restApiService.getAccount(),
|
|
this.restApiService.getTopMenus(),
|
|
getPluginLoader().awaitPluginsLoaded(),
|
|
]).then(result => {
|
|
const account = result[0];
|
|
this._account = account;
|
|
this.loggedIn = !!account;
|
|
this.loading = false;
|
|
this._topMenus = result[1];
|
|
|
|
return getAdminLinks(
|
|
account,
|
|
() =>
|
|
this.restApiService.getAccountCapabilities().then(capabilities => {
|
|
if (!capabilities) {
|
|
throw new Error('getAccountCapabilities returns undefined');
|
|
}
|
|
return capabilities;
|
|
}),
|
|
() => this.$.jsAPI.getAdminMenuLinks()
|
|
).then(res => {
|
|
this._adminLinks = res.links;
|
|
});
|
|
});
|
|
}
|
|
|
|
_loadConfig() {
|
|
this.restApiService
|
|
.getConfig()
|
|
.then(config => {
|
|
if (!config) {
|
|
throw new Error('getConfig returned undefined');
|
|
}
|
|
this._retrieveRegisterURL(config);
|
|
return getDocsBaseUrl(config, this.restApiService);
|
|
})
|
|
.then(docBaseUrl => {
|
|
this._docBaseUrl = docBaseUrl;
|
|
});
|
|
}
|
|
|
|
@observe('_account')
|
|
_accountLoaded(account?: AccountDetailInfo) {
|
|
if (!account) {
|
|
return;
|
|
}
|
|
|
|
this.restApiService.getPreferences().then(prefs => {
|
|
this._userLinks =
|
|
prefs && prefs.my ? prefs.my.map(this._createHeaderLink) : [];
|
|
});
|
|
}
|
|
|
|
_retrieveRegisterURL(config: ServerInfo) {
|
|
if (AUTH_TYPES_WITH_REGISTER_URL.has(config.auth.auth_type)) {
|
|
this._registerURL = config.auth.register_url ?? '';
|
|
if (config.auth.register_text) {
|
|
this._registerText = config.auth.register_text;
|
|
}
|
|
}
|
|
}
|
|
|
|
_computeRegisterHidden(registerURL: string) {
|
|
return !registerURL;
|
|
}
|
|
|
|
_createHeaderLink(linkObj: TopMenuItemInfo): MainHeaderLink {
|
|
// Delete target property due to complications of
|
|
// https://bugs.chromium.org/p/gerrit/issues/detail?id=5888
|
|
//
|
|
// The server tries to guess whether URL is a view within the UI.
|
|
// If not, it sets target='_blank' on the menu item. The server
|
|
// makes assumptions that work for the GWT UI, but not PolyGerrit,
|
|
// so we'll just disable it altogether for now.
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const {target, ...headerLink} = {...linkObj};
|
|
|
|
// Normalize all urls to PolyGerrit style.
|
|
if (headerLink.url.startsWith('#')) {
|
|
headerLink.url = linkObj.url.slice(1);
|
|
}
|
|
|
|
return headerLink;
|
|
}
|
|
|
|
_generateSettingsLink() {
|
|
return getBaseUrl() + '/settings/';
|
|
}
|
|
|
|
_onMobileSearchTap(e: Event) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.dispatchEvent(
|
|
new CustomEvent('mobile-search', {
|
|
composed: true,
|
|
bubbles: false,
|
|
})
|
|
);
|
|
}
|
|
|
|
_computeLinkGroupClass(linkGroup: MainHeaderLinkGroup) {
|
|
return linkGroup.class ?? '';
|
|
}
|
|
|
|
_computeShowHideAriaLabel(mobileSearchHidden: boolean) {
|
|
if (mobileSearchHidden) {
|
|
return 'Show Searchbar';
|
|
} else {
|
|
return 'Hide Searchbar';
|
|
}
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'gr-main-header': GrMainHeader;
|
|
}
|
|
}
|