lit-element#LitElement TypeScript Examples

The following examples show how to use lit-element#LitElement. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: app.ts    From web with MIT License 6 votes vote down vote up
@customElement('my-app')
class MyApp extends LitElement {
  @property({ type: String })
  foo = 'bar';

  render() {
    return html`
      <p>Hello world</p>
    `;
  }
}
Example #2
Source File: blog-posts.ts    From litelement-website with MIT License 6 votes vote down vote up
@customElement('lit-blog-posts')
export class BlogPosts extends LitElement {
  static styles = css`
    h2 {
      margin: 20px;
    }
  `;

  @property({ type: Array }) blogPosts?: Post[];

  constructor() {
    super();
  }

  render() {
    this.loadBlogCard();

    return html`
      <h2>Blog Posts</h2>
      ${this.blogPosts?.map(
        post => html`<blog-card .post="${post}"></blog-card>`
      )}
    `;
  }

  firstUpdated() {
    this.blogPosts = POSTS;
    this.addEventListener('readMore', event => {
      const post = (event as CustomEvent).detail as Post;
      Router.go(`/blog/posts/${post.id}`);
    });
  }

  async loadBlogCard() {
    await import('./blog-card');
  }
}
Example #3
Source File: blog-post.ts    From litelement-website with MIT License 6 votes vote down vote up
@customElement('lit-blog-post')
export class BlogPost extends LitElement {
  render() {
    return html`
      <h2>Blog Post Title</h2>
      <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec felis
        est, placerat ut risus non, bibendum tincidunt nisl. Sed vitae gravida
        urna. Maecenas ut efficitur massa, sed viverra dolor. Ut euismod, nibh
        vel suscipit porttitor, augue libero dictum lacus, et pellentesque enim
        libero quis dui. Curabitur lorem sapien, tristique eget dictum non,
        lobortis ac justo. Ut ac ipsum aliquam, vehicula metus eu, vulputate
        felis. Nunc commodo viverra dolor commodo viverra. Donec et leo diam.
        Duis iaculis cursus bibendum. Vivamus a venenatis turpis. Proin ultrices
        libero vel sollicitudin condimentum. Curabitur vitae nisl id orci
        placerat imperdiet. In eget orci leo. Fusce dignissim, orci nec
        fermentum lobortis, ligula massa bibendum mauris, at imperdiet velit
        purus a dolor. Donec et tempor ante.
      </p>
    `;
  }
}
Example #4
Source File: analytics.ts    From litelement-website with MIT License 6 votes vote down vote up
@customElement('lit-analytics')
export class Analytics extends LitElement {
  static styles = css`
    .container {
      margin: 20px;
    }
  `;

  render() {
    return html`
      <div class="container">
        <h2>Analytics</h2>
        <slot></slot>
      </div>
    `;
  }
}
Example #5
Source File: analytics-period.ts    From litelement-website with MIT License 6 votes vote down vote up
@customElement('lit-analytics-period')
export class AnalyticsPeriod extends LitElement {
  @property({ type: String }) period: Period = Period.week;

  render() {
    return html` <h3>Period: Last ${this.period}</h3> `;
  }

  public onAfterEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): void {
    const period = location.params.period; // path: 'analytics/:period'
    if (period !== undefined) {
      this.period = Period[period as keyof typeof Period] || Period.week;
    }
  }
}
Example #6
Source File: analytics-home.ts    From litelement-website with MIT License 6 votes vote down vote up
@customElement('lit-analytics-home')
export class AnalyticsHome extends LitElement {
  render() {
    return html`
      <h3>Select a period</h3>
      <ul>
        <li><a href="${router.urlForPath(`/analytics/day`)}">Last Day</a></li>
        <li><a href="${router.urlForPath(`/analytics/week`)}">Last Week</a></li>
        <li>
          <a href="${router.urlForPath(`/analytics/month`)}">Last Month</a>
        </li>
        <li><a href="${router.urlForPath(`/analytics/year`)}">Last Year</a></li>
      </ul>
    `;
  }
}
Example #7
Source File: about.ts    From litelement-website with MIT License 6 votes vote down vote up
@customElement('lit-about')
export class About extends LitElement {
  render() {
    return html`
      <h2>About Me</h2>
      <p>
        Suspendisse mollis lobortis lacus, et venenatis nibh sagittis ac. Morbi
        interdum purus diam, vitae pharetra tellus auctor sagittis. Proin
        pellentesque diam a mauris euismod condimentum. Nullam eros ante,
        pretium eget euismod ut, molestie sed nunc. Nullam ut lorem tempus,
        convallis dui ac, congue dolor. Maecenas rutrum magna ac ullamcorper
        fermentum. Nunc porttitor sem at augue ornare, nec interdum ex laoreet.
        Ut vitae mattis urna. In elementum odio a diam iaculis, vel molestie
        diam gravida. Sed in urna nec nibh feugiat fermentum ac vitae dolor. Sed
        porta enim ut orci egestas, vitae gravida mauris scelerisque. Duis
        convallis tincidunt vehicula.
      </p>
    `;
  }
}
Example #8
Source File: submit.ts    From medblocks-ui with Apache License 2.0 6 votes vote down vote up
@customElement('mb-submit')


export default class MbSubmit extends LitElement {
  @event('mb-trigger-submit') submit: EventEmitter<any>;
  handleClick() {
    this.submit.emit()
  }
  connectedCallback() {
    super.connectedCallback()
    this.addEventListener('click', this.handleClick)
  }

  disconnectedCallback() {
    this.removeEventListener('click', this.handleClick)
  }
  render() {
    return html`
      <slot></slot>
    `;
  }
}
Example #9
Source File: opc-back-to-top.ts    From op-components with MIT License 6 votes vote down vote up
@customElement('opc-back-to-top')
export class BackToTop extends LitElement {
  @property({ type: Number, attribute: 'reveal-at' }) revealAt = 0;
  @property({ type: Number, reflect: true }) scrolledY = 0;
  constructor() {
    super();
    window.addEventListener("scroll", () => { this.scrolledY = window.scrollY });
  }

  static get styles() {
    return [style];
  }

  get toggleButton() {
    return this.revealAt < this.scrolledY ? 'show' : 'hide';
  }

  goToTop() {
    window.scrollTo(0, 0);
  }

  render() {
    return html`
    <slot @click="${this.goToTop}" class="${this.toggleButton}">
      <button class="rh-text" tabindex="0">
        <svg xmlns='http://www.w3.org/2000/svg' width='20px' height='20px' viewBox='0 0 512 512'>
          <title>ionicons-v5-a</title>
          <polyline points='112 244 256 100 400 244' style='fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:48px'/>
            <line x1='256' y1='120' x2='256' y2='412' style='fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:48px'/>
        </svg>
          Go to top
      </button>
   </slot>`;
  }
}
Example #10
Source File: opc-header.test.ts    From op-components with MIT License 6 votes vote down vote up
describe('opc-header', () => {

    const OPC_HEADER = 'opc-header';

    let opcHeader: LitElement;

    beforeEach(() => {
      opcHeader = window.document.createElement(OPC_HEADER) as LitElement;
      document.body.appendChild(opcHeader);
    });

    afterEach(() => {
      document.querySelector(OPC_HEADER).remove();
    });

    it('should be defined', async () => {
      expect(opcHeader).toBeDefined();
    });

    it('should render header text on setting "heading" attribute', async () => {
      opcHeader.setAttribute('heading', 'OPC Header');
      await opcHeader.updateComplete;
      expect(opcHeader.getAttribute("heading")).toEqual('OPC Header');
    });
});
Example #11
Source File: mb-form.test.ts    From medblocks-ui with Apache License 2.0 6 votes vote down vote up
class RepeateableTest2 extends LitElement {
    @property({ type: Number }) i: number = 2;
    render() {
        return html`<mb-form>
    ${[...Array(this.i)].map((_, i) => html`
    <div>
        <base-ehr path=${`path/${i}`}> </base-ehr> 
    </div>`)} </mb-form>`
    }
}
Example #12
Source File: opc-header.ts    From op-components with MIT License 6 votes vote down vote up
@customElement('opc-header')
class OPCHeader extends LitElement {

  // Property Declarations
  @property({ reflect: true })
  heading
  version
  sponsor

  @property({ reflect: true })
  theme

  constructor() {
    super();
    this.theme = "default";
  }

  static get styles() {
    return [ style ]
  }

  render() {
    return html`
      <div class="opc-header">
        <div class="opc-header__top-row">
          <h1 class="opc-header__top-row--header-name"> ${ this.heading } </h1>
        </div>
        <div class="opc-header__bottom-row">
          <slot name="breadcrumb">
          </slot>
          <slot name="links">
          </slot>
        </div>
      </div>
    `;
  }
}
Example #13
Source File: mb-form.test.ts    From medblocks-ui with Apache License 2.0 5 votes vote down vote up
class TestComponent extends LitElement {
    @property({ type: Array }) paths: string[]
    render() {
        return html`<mb-form>
    ${this.paths.map(path => html`<base-ehr path=${path}></base-ehr>`)}
</mb-form>`
    }
}
Example #14
Source File: unit.ts    From medblocks-ui with Apache License 2.0 5 votes vote down vote up
@customElement('mb-unit')
export default class MbUnit extends LitElement {
  @property({ type: String, reflect: true }) unit: string;
  @property({ type: String, reflect: true }) label: string;
  @property({ type: Number, reflect: true }) max: number | string;
  @property({ type: Number, reflect: true }) min: number | string;
}
Example #15
Source File: pwa-auth.d.ts    From pwa-auth with MIT License 5 votes vote down vote up
export declare class PwaAuthImpl extends LitElement implements PwaAuth {
    appearance: "button" | "list" | "none";
    signInButtonText: string;
    microsoftButtonText: string;
    googleButtonText: string;
    facebookButtonText: string;
    appleButtonText: string;
    appleRedirectUri: string | undefined | null;
    microsoftKey: string | undefined | null;
    googleKey: string | undefined | null;
    facebookKey: string | undefined | null;
    appleKey: string | undefined | null;
    credentialMode: "none" | "silent" | "prompt";
    menuOpened: boolean;
    menuPlacement: "start" | "end";
    disabled: boolean;
    iconLoading: "lazy" | "eager";
    requireNewAccessToken: boolean;
    readonly providers: ProviderInfo[];
    static readonly assetBaseUrl = "https://cdn.jsdelivr.net/npm/@pwabuilder/pwaauth@latest/assets";
    static readonly authTokenLocalStoragePrefix = "pwa-auth-token";
    static styles: import("lit-element").CSSResult;
    firstUpdated(): void;
    render(): void | TemplateResult;
    /**
     * Starts the sign-in process using the specified provider.
     * @param providerName The name provider to sign-in with. Must be "Microsoft", "Google", "Facebook", or "Apple"
     */
    signIn(providerName: ProviderName): Promise<SignInResult>;
    private getMicrosoftIconUrl;
    private getGoogleIconUrl;
    private getFacebookIconUrl;
    private getAppleIconUrl;
    private renderLoginButton;
    private renderListButtons;
    private renderNoKeysError;
    private dropdownFocusOut;
    private get hasAnyKey();
    private signInClicked;
    private toggleMenu;
    private signInWithProvider;
    private signInCompleted;
    private importMicrosoftProvider;
    private importGoogleProvider;
    private importFacebookProvider;
    private importAppleProvider;
    private tryStoreCredential;
    private tryAutoSignIn;
    private trySignInWithStoredCredential;
    private getStoredCredential;
    private credentialToSignInResult;
    private getProviderNameFromUrl;
    private isWebKit;
    private loadAllDependencies;
    private tryUpdateStoredTokenInfo;
    private tryReadStoredTokenInfo;
    private getAuthTokenLocalStorageKeyName;
    private rehydrateAccessToken;
}
Example #16
Source File: option.ts    From medblocks-ui with Apache License 2.0 5 votes vote down vote up
@customElement('mb-option')
export default class MbOption extends LitElement {
  @property({ type: String, reflect: true }) value: string;
  @property({ type: String, reflect: true }) label: string;
  @property({ type: Number, reflect: true }) ordinal: number;
  @property({ type: String, reflect: true }) type: string;
  
}
Example #17
Source File: mb-form.test.ts    From medblocks-ui with Apache License 2.0 5 votes vote down vote up
class RepeateableTest extends LitElement {
    @property({ type: Number }) i: number = 2;
    render() {
        return html`<mb-form>
    ${[...Array(this.i)].map((_, i) => html`
    <base-ehr path=${`path/${i}`}> </base-ehr>`)} </mb-form>`
    }
}
Example #18
Source File: filter.ts    From medblocks-ui with Apache License 2.0 5 votes vote down vote up
@customElement('mb-filter')
export default class MbFilter extends LitElement {
  @property({ type: String, reflect: true }) label: string;
  @property({ type: String, reflect: true }) value: string;
  @property({ type: Boolean, reflect: true }) disabled: boolean = false;
}
Example #19
Source File: EhrElement.ts    From medblocks-ui with Apache License 2.0 5 votes vote down vote up
/**This is an abstract base class to extend other elements from
 * @fires mb-input - Dispatched when the input changes
 * @fires mb-dependency - Dispatched if dependencies are needed from an external or parent source
 * @fires mb-connect - Dispatched when the component connects
 * @fires mb-disconnect - Dispatched when the component disconnects
 */
export default abstract class EhrElement extends LitElement {
  /**Path of the data element. Use the VSCode extension to get the appropriate paths */
  @property({ type: String, reflect: true }) path: string;
  /**Optional label for the element */
  @property({ type: String, reflect: true }) label?: string;
  
  @property({ type: String, reflect: true }) repeatsuffix?: string;
  @property({ type: String, reflect: true }) repeatprefix?: string;



  /**Data of the element. Setting this will emit an input event automatically. */
  abstract data: any;

  isMbElement: boolean = true
  /**An internal representation of type to handle serializing */
  @property({ type: String, reflect: true })
  datatype?: string;

  /**Event Emitter for mb-input */
  @event('mb-input') _mbInput: EventEmitter<any>;

  /**Function to validate the element during form submit */
  reportValidity(): boolean {
    return true;
  }

  @event('mb-dependency') _mbDependency: EventEmitter<{
    key: string;
    value: any;
  }>;

  @event('mb-path-change')
  _pathChangeHandler: EventEmitter<{ oldPath: string, newPath: string }>

  @event('mb-connect')
  _mbConnect: EventEmitter<string>

  @watch('path')
  handlePathChange(oldPath: string, newPath: string) {
    this._pathChangeHandler.emit({ detail: { oldPath, newPath } })
  }

  connectedCallback() {
    super.connectedCallback()
    this._mbConnect.emit({ detail: this.path })
    this._mbInput.emit()
  }

  @watch('data')
  _handleDataChange() {
    this._mbInput.emit();
  }
}
Example #20
Source File: app.ts    From litelement-website with MIT License 5 votes vote down vote up
@customElement('lit-app')
export class App extends LitElement {
  static styles = css`
    .header {
      padding: 20px;
      font-size: 25px;
      text-align: center;
      background: white;
    }

    .topnav {
      background-color: #4f4c4c;
      overflow: hidden;
    }

    .topnav a {
      float: left;
      color: #f2f2f2;
      text-align: center;
      padding: 14px 16px;
      text-decoration: none;
      font-size: 17px;
    }

    .topnav a:hover {
      background-color: #ddd;
      color: black;
    }

    .topnav a.active {
      background-color: #008cba;
      color: white;
    }
  `;

  render() {
    return html`
      <div class="topnav">
        <a class="active" href="/">Home</a>
        <a href="/blog">Blog</a>
        <a href="/about">About</a>
      </div>
      <div class="header">
        <h2>LitElement Website</h2>
      </div>

      <slot></slot>
    `;
  }
}
Example #21
Source File: blog.ts    From litelement-website with MIT License 5 votes vote down vote up
@customElement('lit-blog')
export class Blog extends LitElement {
  render() {
    return html` <slot></slot> `;
  }
}
Example #22
Source File: blog-card.ts    From litelement-website with MIT License 5 votes vote down vote up
@customElement('blog-card')
export class BlogCard extends LitElement {
  static styles = css`
    .blog-card {
      margin: 20px;
      display: flex;
      flex-direction: column;
      margin-bottom: 15px;
      background: white;
      border-radius: 5px;
      overflow: hidden;
      border-radius: 10px;
    }

    .blog-description {
      padding: 20px;
      background: white;
    }

    .blog-footer {
      text-align: right;
    }

    .blog-link {
      color: #008cba;
    }

    h1 {
      margin: 0;
      font-size: 1.5rem;
    }
    h2 {
      font-size: 1rem;
      font-weight: 300;
      color: #5e5e5e;
      margin-top: 5px;
    }
  `;

  // @property({ type: String }) postTitle?: string;
  // @property({ type: String }) author?: string;
  // @property({ type: String }) description?: string;

  @property({ type: Object }) post?: Post;

  render() {
    return html`
      <div class="blog-card">
        <div class="blog-description">
          <h1>${this.post?.title}</h1>
          <h2>${this.post?.author}</h2>
          <p>${this.post?.description}</p>
          <p class="blog-footer">
            <a class="blog-link" @click="${this.handleClick}">Read More</a>
          </p>
        </div>
      </div>
    `;
  }

  public handleClick() {
    this.dispatchEvent(
      new CustomEvent('readMore', { detail: this.post, composed: true })
    );
  }
}
Example #23
Source File: element.ts    From Bundler with MIT License 5 votes vote down vote up
@customElement("my-element")
export class MyElement extends LitElement {
  static styles = unsafeCSS(styles);

  render() {
    return html`<h1>Hello from LitElement!</h1>`;
  }
}
Example #24
Source File: opc-header.ts    From op-components with MIT License 5 votes vote down vote up
@customElement('opc-header-links')
class OPCHeaderLinks extends LitElement {

  // Property Declarations
  @property({ type: Array })
  _links = []

  constructor() {
    super()
  }

  static get styles() {
    return [ style ]
  }

  get opcHeaderLinks() {
    return this._links;
  }

  set opcHeaderLinks(links) {
    if (!links.length) {
      console.warn(`${this.tagName.toLowerCase()}: Array of "links" must be provided. You can do so by using opcHeaderLinks setter function`);
    } else {
      this._links = links;
    }
  }

  render() {
    return html`
        <link rel="stylesheet" href="https://unpkg.com/@patternfly/patternfly/patternfly.css" crossorigin="anonymous">
        ${this._links.map(link =>
          html`
              <a class="pf-c-button pf-m-link" href="${link.href}">
                <span class="pf-c-button__icon pf-m-start">
                  <i class="fas ${link.icon}" aria-hidden="true"></i>
                </span>${link.name}
              </a>`)}
      `;
  }
}
Example #25
Source File: opc-header.ts    From op-components with MIT License 5 votes vote down vote up
@customElement('opc-header-breadcrumb')
class OPCHeaderBreadcrumb extends LitElement {

  // Property Declarations
  @property({ type: Array })
  _breadcrumb = [];

  constructor() {
    super();
  }

  static get styles() {
    return [ style ]
  }

  get opcHeaderBreadcrumb() {
    return this._breadcrumb;
  }

  set opcHeaderBreadcrumb(breadcrumb) {
    if (!breadcrumb.length) {
      console.warn(`${this.tagName.toLowerCase()}: Array of "breadcrumb" must be provided. You can do so by using opcHeaderBreadcrumb setter function`);
    } else {
      this._breadcrumb = breadcrumb;
    }
  }

  render() {
    return html`
        <link rel="stylesheet" href="https://unpkg.com/@patternfly/patternfly/patternfly.css" crossorigin="anonymous">
        <nav class="pf-c-breadcrumb" aria-label="breadcrumb">
          <ol class="pf-c-breadcrumb__list">
            ${this._breadcrumb.map(breadcrumb =>
              html`<li class="pf-c-breadcrumb__item">
                    <span class="pf-c-breadcrumb__item-divider">
                      <i class="fas fa-angle-right" aria-hidden="true"></i>
                    </span>
                    <a href="${breadcrumb.href}" class="pf-c-breadcrumb__link">${breadcrumb.name}</a>
                  </li>`)}
          </ol>
        </nav>
      `;
  }
}
Example #26
Source File: opc-footer.ts    From op-components with MIT License 5 votes vote down vote up
@customElement('opc-footer')
export class OPCFooter extends LitElement {
  @property({ attribute: 'theme' }) theme: string = 'light';

  @property({ attribute: 'flat', type: Boolean }) flat: Boolean = false;

  @property({ type: Array, attribute: 'link-categories' })
  _linkCategories: OPCFooterLinkCategory[] | undefined = [];

  static get styles() {
    return [ style ];
  }

  get opcLinkCategories() {
    return this._linkCategories;
  }

  set opcLinkCategories(links) {
    if (!links.length) {
      console.warn(`
        ${ this.tagName.toLowerCase() }:
        opc-footer needs an array of OPCFooterLinkCategory[] type for more information
        read README.md file.`);
    } else {
      this._linkCategories = links;
    }
  }

  render() {
    return html`
      <footer class="${this.theme=== 'light' ? 'light' : 'dark'}
        ${this._linkCategories.length ? `background-image-${this.theme}`: '' }
        ${this.flat ? 'no-bg': nothing}">
        ${this._linkCategories.map(_linkCategory => html`
          <div class="link-category">
            <h4 class="link-category__name">${_linkCategory.category}</h4>
            ${_linkCategory.links.map(_link => html`
              ${_link.href
                ? html`<a href="${_link.href}"
                  target="_blank"
                  name="${_link.text}">${_link.text}</a>`
                : html`<a name="${_link.text}" tabindex="0"
                  @click="${e => this._footerLinkClicked(_link)}">${_link.text}</a>`}
            `)}
          </div>
        `)}
      </div>

      <div class="note">
        Copyright &copy; ${new Date().getFullYear() }
        <slot name="copyright">All Rights Reserved.</slot>
      </div>
      </footer>
    `;
  }

  _footerLinkClicked(link) {
    let footerLinkEvent = new CustomEvent('opc-footer-link:click', {
      detail: {
        message: 'opc-footer-link clicked.',
        data: link
      },
      bubbles: true,
      composed: true
    });
    this.dispatchEvent(footerLinkEvent);
  }

}
Example #27
Source File: ha-card-weather-conditions.ts    From ha-card-weather-conditions with MIT License 4 votes vote down vote up
@customElement("ha-card-weather-conditions")
    class HaCardWeatherConditions extends LitElement {
      @property() public hass?: HomeAssistant;
      @property() private _config?: CardConfig;

      private _iconsConfig: IconsConfig = new class implements IconsConfig {
        iconType: string;
        icons_model: string ;
        iconsDay: { [p: string]: string };
        iconsNight: { [p: string]: string };
        path: string ;
      };
      private _terms: ITerms = new class implements ITerms {
        windDirections;
        words;
      };

      private invalidConfig: boolean = false ;
      private numberElements: number = 0 ;

      private _header: boolean = true ;
      private _name: string = '' ;
      private _language: string ;

      private _hasCurrent: boolean = false ;
      private _hasForecast: boolean = false ;
      private _hasMeteogram: boolean = false ;
      private _hasAirQuality: boolean = false ;
      private _hasPollen: boolean = false ;
      private _hasUv: boolean = false ;
      private _hasAlert: boolean = false ;
      private _hasSea: boolean = false ;

      private _displayTop: boolean = true ;
      private _displayCurrent: boolean = true ;
      private _displayForecast: boolean = true ;

      private _classNameSuffix: string ;

      private _showSummary: boolean = true ;
      private _showPresent: boolean = true ;
      private _showUv: boolean = true ;
      private _showAirQuality: boolean = true ;
      private _showPollen: boolean = true ;
      private _showForecast: boolean = true ;
      private _showAlert: boolean = true ;
      private _showSea: boolean = true ;

      /**
       *
       * @param {CardConfig} config
       */
      public setConfig(config: CardConfig) {
        console.log({card_config: config});

        if (!config) {
          this.invalidConfig = true;
          throw new Error("Invalid configuration");
        }

        if (config.name && config.name.length > 0) {
          this._name = config.name;
        }
        if (config.language && config.language.length > 0) {
          this._language = config.language.toLowerCase();
        } else this._language = 'en';

        let transls ;
        try {
          transls = JSON.parse(translations[cwcLocale[this._language]]);
          this._terms.windDirections = transls.cwcLocWindDirections ;
          this._terms.words = transls.cwcTerms ;
          console.info(logo + "%c card \"" + this._name + "\", locale is '" + this._language + "'.",
            optConsoleParam1, optConsoleParam2, optConsoleParam3);
        } catch(e) {
          transls = JSON.parse(translations[cwcLocale['en']]);
          this._terms.windDirections = transls.cwcLocWindDirections ;
          this._terms.words = transls.cwcTerms ;
          console.info(logo + "%c card \"" + this._name + "\" unable to use '" + this._language + "' locale, set as default 'en'.",
            optConsoleParam1, optConsoleParam2, optConsoleParam3);
        }

        numberFormat_0dec = new Intl.NumberFormat(this._language, { maximumFractionDigits: 0 }) ;
        numberFormat_1dec = new Intl.NumberFormat(this._language, { maximumFractionDigits: 1 }) ;

        if (undefined !== config.display) {
          this._displayTop = config.display.findIndex(item => 'top' === item.toLowerCase()) >= 0;
          this._displayCurrent = config.display.findIndex(item => 'current' === item.toLowerCase()) >= 0;
          this._displayForecast = config.display.findIndex(item => 'forecast' === item.toLowerCase()) >= 0;
        }

        this._hasCurrent = (!!config.weather) && (!!config.weather.current);
        this._hasForecast = (!!config.weather) && (!!config.weather.forecast);
        this._hasMeteogram = this._hasForecast && (!!config.weather.forecast.meteogram);
        this._hasAirQuality = !!config.air_quality;
        this._hasPollen = !!config.pollen && (!!config.pollen.tree || !!config.pollen.weed || !!config.pollen.grass);
        this._hasUv = !!config.uv;
        this._hasAlert = !!config.alert;
        this._hasSea = !!config.sea;

        this._iconsConfig.path = hacsImages ? hacsImagePath : manImages ? manImagePath : null;
        // this._iconsConfig.iconType = config.animation ? "animated" : "static";
        this._iconsConfig.iconType = config.animation ? "animated" : "static";
        this._iconsConfig.iconsDay = cwcClimacellDayIcons;
        this._iconsConfig.iconsNight = cwcClimacellNightIcons;
        this._iconsConfig.icons_model = "climacell";
        if ((!!config.weather) && (!!config.weather.icons_model))
          switch (config.weather.icons_model.toLowerCase()) {
            case 'darksky':
              this._iconsConfig.iconsDay = cwcDarkskyDayIcons;
              this._iconsConfig.iconsNight = cwcDarkskyNightIcons;
              this._iconsConfig.icons_model = "darksky";
              break;
            case 'openweathermap':
              this._iconsConfig.iconsDay = cwcOpenWeatherMapDayIcons;
              this._iconsConfig.iconsNight = cwcOpenWeatherMapNightIcons;
              this._iconsConfig.icons_model = "openweathermap";
              break;
            case 'buienradar':
              this._iconsConfig.iconsDay = cwcBuienradarDayIcons;
              this._iconsConfig.iconsNight = cwcBuienradarNightIcons;
              this._iconsConfig.icons_model = "buienradar";
              break;
            case 'defaulthass':
              this._iconsConfig.iconsDay = cwcDefaultHassDayIcons;
              this._iconsConfig.iconsNight = cwcDefaultHassNightIcons;
              this._iconsConfig.icons_model = "defaulthass";
              break;
          }

        this._config = config;
      }

      /**
       * get the current size of the card
       * @return {Number}
       */
      getCardSize() {
        return 1;
      }

      /**
       *
       * @returns {CSSResult}
       */
      static get styles(): CSSResult {
        return css`${style}${styleSummary}${styleForecast}${styleMeter}${styleCamera}${styleNightAndDay}${unsafeCSS(getSeaStyle(globalImagePath))}`;
      }

      /**
       * generates the card HTML
       * @return {TemplateResult}
       */
      render() {
        if (this.invalidConfig) return html`
            <ha-card class="ha-card-weather-conditions">
                <div class='banner'>
                    <div class="header">ha-card-weather-conditions</div>
                </div>
                <div class='content'>
                    Configuration ERROR!
                </div>
            </ha-card>
        `;
        else {
          return this._render();
        }
      }

      /**
       *
       * @returns {TemplateResult}
       * @private
       */
      _render() {
        let sunrise, sunriseEnd, sunsetStart, sunset, now ;
        let dynStyle, condition, habgImage ;

        let _renderedSummary, _renderedPresent, _renderedUv, _renderedAirQuality, _renderedPollen, _renderedForecast,
          _renderedAlert, _renderedSea ;
        // let _renderSummury: boolean = false ;

        let posix:number = 0 ;
        let states = this.hass.states ;

        if( this._showSummary && this._hasCurrent ) {
          let current = this._config.weather.current ;

          if((current.current_conditions && typeof states[ current.current_conditions ] !== undefined)
            || (current.temperature && typeof states[ current.temperature ] !== undefined)) {
            _renderedSummary = renderSummary(this.hass,
              this._config.weather.current, this._config.name, this._iconsConfig, this._terms) ;
            posix++ ;
          } else _renderedSummary = "" ;
        } else _renderedSummary = "" ;

        // Test if render >Present<
        if( this._showPresent && this._hasCurrent) {
          let current = this._config.weather.current ;

          if((current.sun && typeof states[ current.sun ] !== undefined)
            || (current.humidity && typeof states[ current.humidity ] !== undefined)
            || (current.pressure && typeof states[ current.pressure ] !== undefined)
            || (current.visibility && typeof states[ current.visibility ] !== undefined)
            || (current.wind_bearing && typeof states[ current.wind_bearing ] !== undefined)
            || (current.wind_speed && typeof states[ current.wind_speed ] !== undefined)) {

            _renderedPresent = renderPresent(this.hass,
              this._config.weather.current, this._config.weather.forecast, this._language, this._terms, posix > 0) ;
            posix++ ;
          } else {
            if(current.forecast && this._hasForecast) {
              let forecast = this._config.weather.forecast ;

              if((forecast.temperature_low && forecast.temperature_low.day_1 && typeof states[ forecast.temperature_low.day_1 ] !== undefined)
                || (forecast.temperature_high && forecast.temperature_high.day_1 && typeof states[ forecast.temperature_high.day_1 ] !== undefined)
                || (forecast.precipitation_intensity && forecast.precipitation_intensity.day_1 && typeof states[ forecast.precipitation_intensity.day_1 ] !== undefined)
                || (forecast.precipitation_probability && forecast.precipitation_probability.day_1 && typeof states[ forecast.precipitation_probability.day_1 ] !== undefined)) {

                _renderedPresent = renderPresent(this.hass,
                  this._config.weather.current, this._config.weather.forecast, this._language, this._terms, posix > 0) ;
                posix++ ;
              } else _renderedPresent = "" ;
            } else _renderedPresent = "" ;
          }
        } else _renderedPresent = "" ;

        // Test AirQuality
        if(this._showAirQuality && this._hasAirQuality ) {
          let airQuality = this._config.air_quality ;

          if((airQuality.co && typeof states[ airQuality.co ] !== undefined)
            || (airQuality.epa_aqi && typeof states[ airQuality.epa_aqi ] !== undefined)
            || (airQuality.epa_health_concern && typeof states[ airQuality.epa_health_concern ] !== undefined)
            || (airQuality.no2 && typeof states[ airQuality.no2 ] !== undefined)
            || (airQuality.o3 && typeof states[ airQuality.o3 ] !== undefined)
            || (airQuality.pm10 && typeof states[ airQuality.pm10 ] !== undefined)
            || (airQuality.pm25 && typeof states[ airQuality.pm25 ] !== undefined)
            || (airQuality.so2 && typeof states[ airQuality.so2 ] !== undefined)) {

            _renderedAirQuality = renderAirQualities(this.hass, this._config.air_quality, posix > 0) ;
            posix++ ;
          } else _renderedAirQuality = "" ;
        } else _renderedAirQuality = "" ;

        // Test uv
        if(this._showUv && this._hasUv ) {
          let uv = this._config.uv ;

          if((uv.protection_window && typeof states[ uv.protection_window ] !== undefined)
            || (uv.ozone_level && typeof states[ uv.ozone_level ] !== undefined)
            || (uv.uv_index && typeof states[ uv.uv_index ] !== undefined)
            || (uv.uv_level && typeof states[ uv.uv_level ] !== undefined)
            || (uv.max_uv_index && typeof states[ uv.max_uv_index ] !== undefined)) {

            _renderedUv = renderUv(this.hass, this._config.uv, posix > 0) ;
            posix++ ;
          } else _renderedUv = "" ;
        } else _renderedUv = "" ;

        if(this._showPollen && this._hasPollen ) {
          let pollen = this._config.pollen ;

          if((pollen.grass && pollen.grass.entity &&  typeof states[ pollen.grass.entity ] !== undefined)
            || (pollen.tree && pollen.tree.entity &&  typeof states[ pollen.tree.entity ] !== undefined)
            || (pollen.weed && pollen.weed.entity &&  typeof states[ pollen.weed.entity ] !== undefined)) {

            _renderedPollen = renderPollens(this.hass, this._config.pollen, posix > 0) ;
            posix++ ;
          } else _renderedPollen = "" ;
        } else _renderedPollen = "" ;

        if( this._showForecast && this._hasForecast ) {
          let forecast = this._config.weather.forecast ;

          _renderedForecast = renderForecasts(this.hass,
            this._config.weather.current, forecast, this._iconsConfig, this._language, posix > 0) ;
          posix++ ;
        } else _renderedForecast = "" ;

        // Test Alert
        if( this._showAlert && this._hasAlert ) {
          let alert = this._config.alert ;

          _renderedAlert = renderAlert(this.hass, alert, posix > 0) ;
          posix++ ;
        } else _renderedAlert = "" ;

        // Test Sea
        if( this._showSea && this._hasSea ) {
          let sea = this._config.sea ;
          _renderedSea = renderSeaForecast(this.hass, sea, this._iconsConfig, this._language, posix > 0) ;
          posix++ ;
        } else _renderedSea = "" ;

        return html`
      ${dynStyle ? html`
      <style>${dynStyle}</style>` : "" }
      
      <ha-card class="ha-card-weather-conditions ">
        <div class="nd-container ${habgImage ? habgImage : ''}">
        ${this._header ? html`
            ${_renderedSummary}
            ${_renderedAlert}
            ${_renderedPresent}
            ${_renderedUv}
            ${_renderedAirQuality}
            ${_renderedPollen}
            ${_renderedForecast}
            ${_renderedSea}
            ${this._hasMeteogram ? this.renderCamera(this.hass, this._config.weather.forecast.meteogram) : ""}
            ${this._config.camera ? this.renderCamera(this.hass, this._config.camera) : ""}
        ` : html``}
        </div>
      </ha-card>
    `;
      }

      /**
       *
       * @param hass
       * @param camId
       */
      renderCamera(hass: HomeAssistant, camId: string) {
        let camera = hass.states[camId];
        let entity_picture: string = camera ? camera.attributes.entity_picture : undefined ;

        return entity_picture ? html`
        <div @click=${e => this.handlePopup(e, camId)} class="camera-container">
          <div class="camera-image">
            <img src="${entity_picture}" alt="${camera.attributes.friendly_name}"/>
          </div>
        </div>
      ` : html``;
      }

      /**
       *
       * @param e
       * @param entityId
       */
      handlePopup(e, entityId: string) {
        e.stopPropagation();

        let ne = new Event('hass-more-info', {composed: true});
        // @ts-ignore
        ne.detail = {entityId};
        this.dispatchEvent(ne);
      }

    }
Example #28
Source File: pwa-auth.ts    From pwa-auth with MIT License 4 votes vote down vote up
@customElement('pwa-auth')
export class PwaAuthImpl extends LitElement implements PwaAuth {

    @property({ type: String, reflect: true }) appearance: "button" | "list" | "none" = "button";
    @property({ type: String }) signInButtonText = "Sign in";
    @property({ type: String }) microsoftButtonText = "Sign in with Microsoft";
    @property({ type: String }) googleButtonText = "Sign in with Google";
    @property({ type: String }) facebookButtonText = "Sign in with Facebook";
    @property({ type: String }) appleButtonText = "Sign in with Apple";
    @property({ type: String }) appleRedirectUri: string | undefined | null;
    @property({ type: String }) microsoftKey: string | undefined | null;
    @property({ type: String }) googleKey: string | undefined | null;
    @property({ type: String }) facebookKey: string | undefined | null;
    @property({ type: String }) appleKey: string | undefined | null;
    @property({ type: String }) credentialMode: "none" | "silent" | "prompt" = "silent";
    @property({ type: Boolean }) menuOpened = false;
    @property({ type: String, reflect: true }) menuPlacement: "start" | "end" = "start";
    @property({ type: Boolean }) disabled = false;
    @property({ type: String }) iconLoading: "lazy" | "eager" = "lazy";
    @property({ type: Boolean }) requireNewAccessToken = false; // If true, user always goes through OAuth flow to acquire a new access token. If false, user can sign-in using a stored credential with possibly stale access token.

    readonly providers: ProviderInfo[] = [
        {
            name: "Microsoft",
            url: "https://graph.microsoft.com",
            getKey: () => this.microsoftKey,
            getButtonText: () => this.microsoftButtonText,
            getIconUrl: () => this.getMicrosoftIconUrl(),
            import: (key: string) => this.importMicrosoftProvider(key),
            btnClass: "microsoft-btn",
            buttonPartName: "microsoftButton",
            containerPartName: "microsoftContainer",
            iconPartName: "microsoftIcon",
            signIn: () => this.signIn("Microsoft")
        },
        {
            name: "Google",
            url: "https://account.google.com",
            getKey: () => this.googleKey,
            getButtonText: () => this.googleButtonText,
            getIconUrl: () => this.getGoogleIconUrl(),
            import: (key: string) => this.importGoogleProvider(key),
            btnClass: "google-btn",
            buttonPartName: "googleButton",
            containerPartName: "googleContainer",
            iconPartName: "googleIcon",
            signIn: () => this.signIn("Google")
        },
        {
            name: "Facebook",
            url: "https://www.facebook.com",
            getKey: () => this.facebookKey,
            getButtonText: () => this.facebookButtonText,
            getIconUrl: () => this.getFacebookIconUrl(),
            import: (key: string) => this.importFacebookProvider(key),
            btnClass: "facebook-btn",
            buttonPartName: "facebookButton",
            containerPartName: "facebookContainer",
            iconPartName: "facebookIcon",
            signIn: () => this.signIn("Facebook")
        },
        {
            name: "Apple",
            url: "https://appleid.apple.com",
            getKey: () => this.appleKey,
            getButtonText: () => this.appleButtonText,
            getIconUrl: () => this.getAppleIconUrl(),
            import: (key: string) => this.importAppleProvider(key),
            btnClass: "apple-btn",
            buttonPartName: "appleButton",
            containerPartName: "appleContainer",
            iconPartName: "appleIcon",
            signIn: () => this.signIn("Apple")
        },
    ];

    static readonly assetBaseUrl = "https://cdn.jsdelivr.net/npm/@pwabuilder/pwaauth@latest/assets";
    static readonly authTokenLocalStoragePrefix = "pwa-auth-token";

	static styles = css`

		:host {
			display: inline-block;
		}

        button {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        }

        :host([appearance="list"]) .provider {
            width: 200px;
		}
		:host([appearance="list"]) .provider + .provider {
			margin-top: 10px;
		}

        :host([appearance="list"]) .provider button {
			display: block;
            width: 100%;
            padding: 10px;
            cursor: pointer;
            border-radius: 2px;
            border-width: 0;
            text-align: left;
        }

        :host([appearance="list"]) .provider button img {
            vertical-align: middle;
            margin-right: 10px;
            margin-left: 5px;
        }

        :host([appearance="list"]) .google-btn {
            background-color: white;
            border: 1px solid rgb(192, 192, 192);
        }

        :host([appearance="list"]) .google-btn:hover {
            background-color: rgb(245, 245, 246);
        }

        :host([appearance="list"]) .microsoft-btn {
            color: white;
            background-color: rgb(84, 84, 84);
        }

        :host([appearance="list"]) .microsoft-btn:hover {
            background-color: rgb(47, 51, 55);
        }

        :host([appearance="list"]) .facebook-btn {
            color: white;
            background-color: #385499;
        }

        :host([appearance="list"]) .facebook-btn:hover {
            background-color: #314a86;
        }

        :host([appearance="list"]) .apple-btn {
            background-color: black;
            color: white;
        }

        .signin-btn {
            background-color: rgb(225, 230, 234);
            border: 1px solid rgb(220, 224, 229);
            color: rgb(33, 37, 41);
            border-radius: 4px;
            padding: 12px;
            transition: all 0.15s ease-in-out;
            outline: none;
            cursor: pointer;
        }

            .signin-btn:hover:not(:disabled) {
                background-color: rgb(220, 224, 228);
                border-color: rgb(212, 218, 223);
            }

            .signin-btn:focus {
                background-color: rgb(219, 225, 230);
                border-color: rgb(212, 218, 224);
                box-shadow: rgba(216, 217, 219, 0.1) 0 0 0 3.2px;
            }

            .signin-btn:active {
                background-color: rgb(210, 214, 218);
                border-color: rgb(202, 208, 213);
            }

            .signin-btn:disabled {
                color: rgba(16, 16, 16, 0.3);
            }

        .dropdown {
            position: relative;
            display: inline-block;
        }

        .dropdown .menu {
            position: absolute;
            top: 100%;
            left: 0;
            z-index: 1000;
            display: none;
            float: left;
            min-width: 10rem;
            padding: .5rem 0;
            margin: .125rem 0 0;
            font-size: 1rem;
            background-color: white;
            background-clip: padding-box;
            border: 1px solid rgba(0,0,0,.15);
            border-radius: .25rem;
            cursor: pointer;
        }

        .dropdown .menu.open {
            display: block;
            transform: translate3d(0px, 38px, 0px);
            top: 0;
            left: 0;

            animation-name: dropdown-animation;
            animation-duration: 300ms;
        }

        .dropdown .menu.open.align-end {
            left: auto;
            right: 0;
        }

        .dropdown .menu button {
            background-color: transparent;
            white-space: nowrap;
            border: none;
            outline: none;
            padding: 8px 24px 8px 24px;
            cursor: pointer;
            width: 100%;
            text-align: left;
        }

            .dropdown .menu button:hover {
                background-color: rgb(245, 246, 247);
            }

            .dropdown .menu button:active {
                background-color: rgb(240, 241, 242);
            }

        .dropdown .menu button img {
            vertical-align: middle;
            margin-right: 10px;
        }

        .provider-error {
            background-color: rgb(220, 53, 69);
            color: white;
            padding: 20px;
        }

        @keyframes dropdown-animation {
          from {
            opacity: 0;
          }
          to {
            opacity: 1;
          }
        }

        @media(prefers-reduced-motion: reduce) {
            .dropdown .menu.open {
                animation: none;
            }
        }
    `;

    firstUpdated() {
        // If we're on Safari, we need to load dependencies up front to avoid Safari
        // blocking the first OAuth popup. See https://github.com/pwa-builder/pwa-auth/issues/3
        if (this.isWebKit()) {
            this.disabled = true;
            this.loadAllDependencies()
                .finally(() => this.disabled = false);
        }
    }

    render() {
        if (!this.hasAnyKey) {
            return this.renderNoKeysError();
        }

        if (this.appearance === "list") {
            return this.renderListButtons();
        }

        if (this.appearance === "button") {
            return this.renderLoginButton();
        }

        return super.render();
    }

    /**
     * Starts the sign-in process using the specified provider.
     * @param providerName The name provider to sign-in with. Must be "Microsoft", "Google", "Facebook", or "Apple"
     */
    public signIn(providerName: ProviderName): Promise<SignInResult> {
        const provider = this.providers.find(p => p.name === providerName);
        if (!provider) {
            const errorMessage = "Unable to sign-in because of unsupported provider";
            console.error(errorMessage, providerName);
            return Promise.reject(errorMessage + " " + providerName);
        } 
        
        return this.signInWithProvider(provider)
            .then(result => this.signInCompleted(result));
    }

    private getMicrosoftIconUrl(): string {
        if (this.appearance === "button") {
            return `${PwaAuthImpl.assetBaseUrl}/microsoft-icon-button.svg`;
        }

        return `${PwaAuthImpl.assetBaseUrl}/microsoft-icon-list.svg`;
    }

	private getGoogleIconUrl(): string {
        return `${PwaAuthImpl.assetBaseUrl}/google-icon.svg`;
    }
    
    private getFacebookIconUrl(): string {
        if (this.appearance === "button") {
            return `${PwaAuthImpl.assetBaseUrl}/facebook-icon-button.svg`;
        }

        return `${PwaAuthImpl.assetBaseUrl}/facebook-icon-list.svg`;
    }

	private getAppleIconUrl(): string {
        if (this.appearance === "button") {
            return `${PwaAuthImpl.assetBaseUrl}/apple-icon-button.svg`;
        }

        return `${PwaAuthImpl.assetBaseUrl}/apple-icon-list.svg`;
    }

    private renderLoginButton(): TemplateResult {
        return html`
            <div class="dropdown" @focusout="${this.dropdownFocusOut}">
                <button class="signin-btn" part="signInButton" ?disabled=${this.disabled} @click="${this.signInClicked}">
                    ${this.signInButtonText}
                </button>
                <div class="menu ${this.menuOpened ? "open" : ""} ${this.menuPlacement === "end" ? "align-end" : ""}" part="dropdownMenu">
					${this.renderListButtons()}
                </div>
            </div>
        `;
    }

    private renderListButtons(): TemplateResult {
        return html`
            ${this.providers
                .filter(provider => !!provider.getKey())
                .map(provider => html`
                <div class="provider" part="${provider.containerPartName}">
                    <button class="${provider.btnClass}" ?disabled=${this.disabled} part="${provider.buttonPartName}" @click="${provider.signIn}">
                        <img part="${provider.iconPartName}" loading="${this.iconLoading}" width="20px" height="20px" src="${provider.getIconUrl()}" />
                        ${provider.getButtonText()}
                    </button>
                </div>
            `)}
        `;
    }

    private renderNoKeysError(): TemplateResult {
        return html`<div class="provider-error"><strong>❌ No available sign-ins</strong><br><em>To enable sign-in, pass a Microsoft key, Google key, Facebook, or Apple key to the &lt;pwa-auth&gt; component.</em><br><pre>&lt;pwa-auth microsoftkey="..."&gt;&lt;/pwa-auth&gt;</pre></div>`;
    }

    private dropdownFocusOut(e: FocusEvent) {
        // Close the dropdown if the focus is no longer within it.
        if (this.menuOpened) {
            const dropdown = this.shadowRoot?.querySelector(".dropdown");
            const dropdownContainsFocus = dropdown?.matches(":focus-within");
            if (!dropdownContainsFocus) {
                this.menuOpened = false;
            }
        }
    }

    private get hasAnyKey(): boolean {
        return this.providers.some(p => !!p.getKey());
    }

    private async signInClicked() {
        // Are we configured to use browser credentials (the new CredentialStore API)?
        // If so, go ahead and sign in with whatever stored credential we have.
        if (this.credentialMode === "none") {
            this.toggleMenu();
        } else {
            const signedInCreds = await this.tryAutoSignIn();
            if (!signedInCreds) {
                // There was no stored credential to sign in with. Just show the menu.
                this.toggleMenu();
            }
        }
    }

    private toggleMenu() {
        this.menuOpened = !this.menuOpened;
    }

    private signInWithProvider(provider: ProviderInfo) {
        const key = provider.getKey();
        if (!key) {
            return Promise.reject("No key specified");
        }
        if (this.disabled) {
            return Promise.reject("Sign-in already in progress, rejecting new sign-in attempt");
        }

        this.disabled = true;
        this.menuOpened = false;
        return this.trySignInWithStoredCredential(provider.url)
            .then(storedCredSignInResult => {
                // Did we sign in with a stored credential? Good, we're done.
                if (storedCredSignInResult) {
                    return storedCredSignInResult;
                }

                // Couldn't sign in with stored credential.
                // Kick off the provider-specified OAuth flow.
                return provider.import(key)
                    .then(p => p.signIn())
                    .catch(error => {
                        // If the provider sends back an error, consider that a SignInResult
                        const providerError: SignInResult = {
                            error: error,
                            provider: provider.name
                        };
                        return providerError;
                    })
            })
            .finally(() => this.disabled = false);
    }

    private signInCompleted(signIn: SignInResult): SignInResult {
        this.rehydrateAccessToken(signIn);
        this.dispatchEvent(new CustomEvent("signin-completed", { detail: signIn }));
        this.tryStoreCredential(signIn);
        return signIn;
    }

    private importMicrosoftProvider(key: string): Promise<SignInProvider> {
        return import("./microsoft-provider")
            .then(module => new module.MicrosoftProvider(key));
    }

    private importGoogleProvider(key: string): Promise<SignInProvider> {
        return import("./google-provider")
            .then(module => new module.GoogleProvider(key));
    }

    private importFacebookProvider(key: string): Promise<SignInProvider> {
        return import ("./facebook-provider")
            .then(module => new module.FacebookProvider(key));
    }

    private importAppleProvider(key: string): Promise<SignInProvider> {
        return import ("./apple-provider")
            .then(module => new module.AppleProvider(key, this.appleRedirectUri));
    }

    private tryStoreCredential(signIn: SignInResult) {
        // Use the new Credential Management API to store the credential, allowing for automatic sign-in next time the user visits the page.
        // https://developers.google.com/web/fundamentals/security/credential-management/
        const federatedCredentialCtor = window["FederatedCredential"];
        if (signIn.email && federatedCredentialCtor) {
            try {
                const cred = new federatedCredentialCtor({
                    id: signIn.email,
                    provider: this.providers.find(p => p.name === signIn.provider)?.url || signIn.provider,
                    name: signIn.name || "",
                    iconURL: signIn.imageUrl || ""
                });
                navigator.credentials.store(cred);
            } catch (error) {
                console.error("Unable to store federated credential", error);
            }
        }
    }

    private async tryAutoSignIn(): Promise<FederatedCredential | null> {
        // Use the new Credential Management API to login the user automatically.
        // https://developers.google.com/web/fundamentals/security/credential-management/

        // Bail if we don't support Credential Management
        if (!window["FederatedCredential"]) {
            return null;
        }

        // Bail if we're forcing OAuth flow.
        if (this.requireNewAccessToken) {
            return null;
        }

        let credential: FederatedCredential | null = null;
        if (this.credentialMode === "prompt") {
            // Let the user choose.
            // The browser brings up the native "choose your sign in" dialog.
            credential = await this.getStoredCredential("required", this.providers.map(p => p.url));
        } else if (this.credentialMode === "silent") {
            // Go through the available providers and find one that the user has logged in with.
            for (let i = 0; i < this.providers.length; i++) {
                const provider = this.providers[i];
                credential = await this.getStoredCredential("silent", [provider.url]);
                if (credential) {
                    break;
                }
            }
        }

        if (credential) {
            const loginResult = this.credentialToSignInResult(credential);
            this.signInCompleted(loginResult);
        }

        return credential;
    }

    private trySignInWithStoredCredential(providerUrl: string): Promise<SignInResult | null> {
        return this.getStoredCredential("silent", [providerUrl])
            .catch(error => console.warn("Error attempting to sign-in with stored credential", error))
            .then(credential => credential ? this.credentialToSignInResult(credential) : null);
    }

    private getStoredCredential(mediation: string, providerUrls: string[]): Promise<FederatedCredential | null> {
        // Bail if we don't support Credential Management
        if (!window["FederatedCredential"]) {
            return Promise.resolve(null);
        }

        // Bail if we're not allowed to use stored credential.
        if (this.requireNewAccessToken) {
            return Promise.resolve(null);
        }

        const credOptions: any = {
            mediation: mediation,
            federated: {
                providers: providerUrls
            }
        };

        return navigator.credentials.get(credOptions);
    }

    private credentialToSignInResult(cred: FederatedCredential): SignInResult {
        return {
            name: cred.name,
            email: cred.id,
            providerData: null,
            imageUrl: cred.iconURL,
            error: null,
            provider: this.getProviderNameFromUrl(cred.provider!) as ProviderName
        };
    }

    private getProviderNameFromUrl(url: string): ProviderName {
        const provider = this.providers.find(p => p.url === url);
        if (!provider) {
            console.warn("Unable to find provider matching URL", url);
            return "Microsoft";
        }
        
        return provider.name;
    }

    private isWebKit(): boolean {
        // As of April 2020, Webkit-based browsers wrongfully blocks
        // the OAuth popup due to lazy-loading the auth library(s).
        const isIOS = !!navigator.userAgent.match(/ipad|iphone/i);  // everything is WebKit on iOS
        const isSafari = !!navigator.vendor && navigator.vendor.includes("Apple");
        return isIOS || isSafari;
    }

    private loadAllDependencies(): Promise<any> {
        const dependencyLoadTasks = this.providers
            .filter(p => !!p.getKey())
            .map(p => p.import(p.getKey()!).then(p => p.loadDependencies()));

        return Promise.all(dependencyLoadTasks)
            .catch(error => console.error("Error loading dependencies", error));
    }

    private tryUpdateStoredTokenInfo(signIn: SignInResult) {
        const localStorageKey = this.getAuthTokenLocalStorageKeyName(signIn.provider);
        const storedToken: StoredAccessToken = {
            token: signIn.accessToken || null,
            expiration: signIn.accessTokenExpiration || null,
            providerData: signIn.providerData
        };
        try {
            localStorage.setItem(localStorageKey, JSON.stringify(storedToken));
        } catch (error) {
            console.warn("Unable to store auth token in local storage", localStorageKey, signIn, error);
        }
    }

    private tryReadStoredTokenInfo(providerName: ProviderName): StoredAccessToken | null {
        const localStorageKey = this.getAuthTokenLocalStorageKeyName(providerName);
        try {
            const tokenJson = localStorage.getItem(localStorageKey);
            return tokenJson ? JSON.parse(tokenJson) : null;
        } catch (error) {
            console.warn("Unable to read auth token from local storage", localStorageKey, error);
            return null;
        }
    }

    private getAuthTokenLocalStorageKeyName(providerName: string): string {
        return `${PwaAuthImpl.authTokenLocalStoragePrefix}-${providerName}`;
    }

    private rehydrateAccessToken(signIn: SignInResult) {
        if (signIn.accessToken) {
            // If the user signed in with OAuth flow just now, we already have the auth token.
            // Store it for later.
            this.tryUpdateStoredTokenInfo(signIn);
        } else {
            // We don't have an access token, meaning we signed-in with a stored credential.
            // Thus, we'll fetch it from local storage.
            const tokenInfo = this.tryReadStoredTokenInfo(signIn.provider);
            if (tokenInfo) {
                signIn.accessToken = tokenInfo.token;
                signIn.accessTokenExpiration = tokenInfo.expiration;
                signIn.providerData = tokenInfo.providerData;
            }
        }
    }
}
Example #29
Source File: hui-grid-card-options.ts    From Custom-Grid-View with MIT License 4 votes vote down vote up
@customElement('hui-grid-card-options')
export class HuiGridCardOptions extends LitElement {
  @property({ attribute: false }) public hass?: HomeAssistant;

  @property({ attribute: false }) public lovelace?;

  @property({ type: Array }) public path?: [number, number];

  @queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;

  public getCardSize(): number | Promise<number> {
    return this._assignedNodes ? computeCardSize(this._assignedNodes[0]) : 1;
  }

  protected render(): TemplateResult {
    return html`
      <slot></slot>
      <div class="parent-card-actions">
        <div class="overlay"></div>
        <div class="card-actions">
          <mwc-icon-button
            .title=${this.hass!.localize('ui.panel.lovelace.editor.edit_card.edit')}
            @click=${this._editCard}
          >
            <ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
          </mwc-icon-button>
          <mwc-icon-button
            .title=${this.hass!.localize('ui.panel.lovelace.editor.edit_card.delete')}
            @click=${this._deleteCard}
          >
            <ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
          </mwc-icon-button>
        </div>
      </div>
    `;
  }

  private _editCard(): void {
    fireEvent(this, 'll-edit-card' as any, { path: this.path });
  }

  private _deleteCard(): void {
    fireEvent(this, 'll-delete-card' as any, { path: this.path });
  }

  static get styles(): CSSResult {
    return css`
      slot {
        pointer-events: none;
        z-index: 0;
      }

      .overlay {
        transition: all 0.25s;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1;
        opacity: 0;
        cursor: move;
      }

      .parent-card-actions:hover .overlay {
        outline: 2px solid var(--primary-color);
        background: rgba(0, 0, 0, 0.3);
        /* background-color: grey; */
        opacity: 1;
      }

      .parent-card-actions {
        transition: all 0.25s;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        opacity: 0;
      }

      .parent-card-actions:hover {
        opacity: 1;
      }

      .card-actions {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        align-items: center;
        z-index: 2;
        position: absolute;
        left: 0;
        right: 0;
        bottom: 24px;
        color: white;
      }

      .card-actions > * {
        margin: 0 4px;
        border-radius: 24px;
        background: rgba(0, 0, 0, 0.7);
      }

      mwc-list-item {
        cursor: pointer;
        white-space: nowrap;
      }

      mwc-list-item.delete-item {
        color: var(--error-color);
      }

      .drag-handle {
        cursor: move;
      }
    `;
  }
}