import {LitElement, html, css, nothing} from 'lit'; import {showToastMessage, IS_MOBILE} from './utils'; import {SHARED_STYLES} from '../css/shared-css.js'; import {customElement, property, state} from 'lit/decorators.js'; import {User} from '../js-src/cs-client'; @customElement('chromedash-header') export class ChromedashHeader extends LitElement { static get styles() { return [ ...SHARED_STYLES, css` :host { --md-gray-100-alpha: hsla(0, 0%, 0%, 0.12); --md-gray-700-alpha: hsla(0, 0%, 0%, 0.62); --md-blue-900: #01579b; --nav-link-color: var(--md-gray-700-alpha); --nav-link-font-size: 16px; --nav-link-hover-background: var(--md-gray-100-alpha); --nav-link-border: 2px solid transparent; --nav-link-active-color: var(--md-blue-900); --nav-link-active-border: 2px solid var(--nav-link-active-color); } header { display: flex; align-items: center; user-select: none; background: var(--card-background); border-bottom: var(--card-border); box-shadow: var(--card-box-shadow); } header a { text-decoration: none !important; } header nav { display: flex; flex: 1; align-items: center; margin: 0 var(--content-padding); -webkit-font-smoothing: initial; } header nav a { cursor: pointer; font-size: var(--nav-link-font-size); text-align: center; padding: var(--content-padding-half) var(--content-padding); color: var(--nav-link-color); white-space: nowrap; border-bottom: var(--nav-link-border); } header nav a:hover { color: black; background: var(--nav-link-hover-background); } header nav a.disabled { opacity: 0.5; pointer-events: none; } header nav [active] { color: var(--nav-link-active-color); border-bottom: var(--nav-link-active-border); } header nav [active] a { color: var(--nav-link-active-color); } header nav .nav-dropdown-container { position: relative; } header nav .nav-dropdown-container ul { display: none; position: absolute; top: 80%; left: 0; list-style: none; z-index: 1; background: var(--card-background); border-bottom: var(--card-border); box-shadow: var(--card-box-shadow); } header nav .nav-dropdown-container a { display: block; } header nav .nav-dropdown-container .nav-dropdown-trigger:hover + ul, header nav .nav-dropdown-container ul:hover { display: block; } header aside a { color: var(--logo-color); } header aside h1 { line-height: 1; } header aside img { height: 24px; width: 24px; } .flex-container { display: flex; justify-content: flex-end; flex-wrap: wrap; align-items: center; width: 100%; } .menu { margin-left: 15px; margin-right: 7px; align-items: center; } .menu:hover { color: black; background: var(--nav-link-hover-background); } .menu [active] { color: var(--nav-link-active-color); border-bottom: var(--nav-link-active-border); } @media only screen and (max-width: 700px) { header { --logoSize: 24px; margin: 0; display: flex; } header aside { display: flex; padding: var(--content-padding-half); border-radius: 0; background: inherit; } } `, ]; } @property({type: String}) appTitle = ''; @property({type: String}) googleSignInClientId = ''; @property({type: String}) devMode = ''; @property({type: String}) currentPage = ''; @property({type: Object}) user = {} as User; @state() loading = false; connectedCallback() { super.connectedCallback(); if (IS_MOBILE) { // Login UI will be handled by chromedash-drawer instead. return; } // user is passed in from chromedash-app if (this.user && this.user.email) return; // user is passed in from chromedash-app, but the user is not logged in if (!this.user) { if (!window['isPlaywright']) { // Insert the google signin button first. // Only insert if not running playwright. this.initializeGoogleSignIn(); } if (this.devMode == 'True') { // Insert the testing signin second, so it appears to the left // of the google signin button, with a large margin on the right. this.initializeTestingSignIn(); } return; } // user is not passed in from anywhere, i.e. this.user is still {} // this is for MPA pages where this component is initialized in _base.html this.loading = true; window.csClient .getPermissions() .then(user => { this.user = user; if (!this.user) { if (!window['isPlaywright']) { this.initializeGoogleSignIn(); } if (this.devMode == 'True') { this.initializeTestingSignIn(); } } }) .catch(() => { showToastMessage( 'Some errors occurred. Please refresh the page or try again later.' ); }) .finally(() => { this.loading = false; }); } initializeGoogleSignIn() { google.accounts.id.initialize({ client_id: this.googleSignInClientId, callback: this.handleCredentialResponse, use_fedcm_for_prompt: true, }); google.accounts.id.prompt(); // Google Identity Services Library cannot find elements in a shadow DOM, // so we create signInButton element at the document level and insert it // in the light DOM of the header, which will be rendered in the below const signInButton = document.createElement('div'); google.accounts.id.renderButton(signInButton, {type: 'standard'}); const appComponent = document.querySelector('chromedash-app'); if (appComponent) { appComponent.insertAdjacentElement('afterbegin', signInButton); // for SPA } else { this.insertAdjacentElement('afterbegin', signInButton); // for MPA } } initializeTestingSignIn() { // Create DEV_MODE login button for testing const signInTestingButton = document.createElement('button'); signInTestingButton.innerText = 'Sign in as example@chromium.org'; signInTestingButton.setAttribute('type', 'button'); signInTestingButton.setAttribute('data-testid', 'dev-mode-sign-in-button'); signInTestingButton.setAttribute( 'style', 'margin-right: 300px; z-index:1000; background: lightblue; border: 1px solid blue;' ); signInTestingButton.addEventListener('click', () => { // POST to '/dev/mock_login' to login as example@chromium. fetch('/dev/mock_login', {method: 'POST'}) .then(response => { if (!response.ok) { throw new Error(`Sign in failed! Response: ${response}`); } }) .then(() => { setTimeout(() => { const url = window.location.href.split('?')[0]; window.location.href = url; }, 1000); }) .catch(error => { console.error('Sign in failed.', error); }); }); const signInButtonContainer = document.querySelector('chromedash-app'); if (signInButtonContainer) { signInButtonContainer.insertAdjacentElement( 'afterbegin', signInTestingButton ); // for SPA } else { this.insertAdjacentElement('afterbegin', signInTestingButton); // for MPA } } handleCredentialResponse(credentialResponse) { window.csClient .signIn(credentialResponse) .then(() => { setTimeout(() => { const url = window.location.href.split('?')[0]; window.location = url as string & Location; }, 1000); }) .catch(() => { console.error('Sign in failed, so signing out to allow retry'); this.signOut(); }); } handleSignOutClick(e) { e.preventDefault(); this.signOut(); } signOut() { window.csClient.signOut().then(() => { window.location.reload(); }); } isCurrentPage(href) { return this.currentPage.startsWith(href); } _fireEvent(eventName, detail) { const event = new CustomEvent(eventName, { bubbles: true, composed: true, detail, }); this.dispatchEvent(event); } handleDrawer() { this._fireEvent('drawer-clicked', {}); } renderAccountMenu() { return html` ${this.user ? html` ${this.user.can_create_feature && !this.isCurrentPage('/guide/new') ? html` Create feature ` : nothing} ` : html` `} `; } render() { let accountMenu = html``; if (!IS_MOBILE && !this.loading) { accountMenu = html`
${this.renderAccountMenu()}
`; } return html`
`; } }