diff --git a/src/auth/ha-auth-flow.js b/src/auth/ha-auth-flow.js index e872c04e97..469ff0df73 100644 --- a/src/auth/ha-auth-flow.js +++ b/src/auth/ha-auth-flow.js @@ -2,9 +2,9 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "@polymer/paper-button/paper-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import "../components/ha-form"; -import LocalizeLiteMixin from "../mixins/localize-lite-mixin"; +import { localizeLiteMixin } from "../mixins/localize-lite-mixin"; -class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) { +class HaAuthFlow extends localizeLiteMixin(PolymerElement) { static get template() { return html` - - - - - `; - } - - static get properties() { - return { - _authProvider: String, - _authProviders: Array, - clientId: String, - redirectUri: String, - oauth2State: String, - translationFragment: { - type: String, - value: "page-authorize", - }, - }; - } - - async ready() { - super.ready(); - const query = {}; - const values = location.search.substr(1).split("&"); - for (let i = 0; i < values.length; i++) { - const value = values[i].split("="); - if (value.length > 1) { - query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]); - } - } - const props = {}; - if (query.client_id) props.clientId = query.client_id; - if (query.redirect_uri) props.redirectUri = query.redirect_uri; - if (query.state) props.oauth2State = query.state; - this.setProperties(props); - - import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider"); - - // Fetch auth providers - try { - const response = await window.providersPromise; - const authProviders = await response.json(); - - // Forward to main screen which will redirect to right onboarding page. - if ( - response.status === 400 && - authProviders.code === "onboarding_required" - ) { - location.href = "/"; - return; - } - - if (authProviders.length === 0) { - alert("No auth providers returned. Unable to finish login."); - return; - } - - this.setProperties({ - _authProviders: authProviders, - _authProvider: authProviders[0], - }); - } catch (err) { - // eslint-disable-next-line - console.error("Error loading auth providers", err); - this._state = "error-loading"; - } - } - - _computeMultiple(array) { - return array && array.length > 1; - } - - async _handleAuthProviderPick(ev) { - this._authProvider = ev.detail; - } - - _computeInactiveProvders(curProvider, providers) { - return providers.filter( - (prv) => prv.type !== curProvider.type || prv.id !== curProvider.id - ); - } - - _computeIntro(localize, clientId, authProvider) { - return ( - localize( - "ui.panel.page-authorize.authorizing_client", - "clientId", - clientId - ) + - "\n\n" + - localize( - "ui.panel.page-authorize.logging_in_with", - "authProviderName", - authProvider.name - ) - ); - } -} -customElements.define("ha-authorize", HaAuthorize); diff --git a/src/auth/ha-authorize.ts b/src/auth/ha-authorize.ts new file mode 100644 index 0000000000..e93a992959 --- /dev/null +++ b/src/auth/ha-authorize.ts @@ -0,0 +1,158 @@ +import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin"; +import { LitElement, html, PropertyDeclarations } from "@polymer/lit-element"; +import "./ha-auth-flow"; +import { AuthProvider } from "../data/auth"; + +import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider"); + +interface QueryParams { + client_id?: string; + redirect_uri?: string; + state?: string; +} + +class HaAuthorize extends litLocalizeLiteMixin(LitElement) { + public clientId?: string; + public redirectUri?: string; + public oauth2State?: string; + private _authProvider?: AuthProvider; + private _authProviders?: AuthProvider[]; + + constructor() { + super(); + this.translationFragment = "page-authorize"; + const query: QueryParams = {}; + const values = location.search.substr(1).split("&"); + for (const item of values) { + const value = item.split("="); + if (value.length > 1) { + query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]); + } + } + if (query.client_id) { + this.clientId = query.client_id; + } + if (query.redirect_uri) { + this.redirectUri = query.redirect_uri; + } + if (query.state) { + this.oauth2State = query.state; + } + } + + static get properties(): PropertyDeclarations { + return { + _authProvider: {}, + _authProviders: {}, + clientId: {}, + redirectUri: {}, + oauth2State: {}, + }; + } + + public render() { + if (!this._authProviders) { + return html` +

[[localize('ui.panel.page-authorize.initializing')]]

+ `; + } + + // We don't have a good approach yet to map text markup in localization. + // So we sanitize the translation with innerText and then inject + // the name with a bold tag. + const loggingInWith = document.createElement("div"); + loggingInWith.innerText = this.localize( + "ui.panel.page-authorize.logging_in_with", + "authProviderName", + "NAME" + ); + loggingInWith.innerHTML = loggingInWith.innerHTML.replace( + "**NAME**", + `${this._authProvider!.name}` + ); + + const inactiveProviders = this._authProviders.filter( + (prv) => prv !== this._authProvider + ); + + return html` + ${this.renderStyle()} +

+ ${ + this.localize( + "ui.panel.page-authorize.authorizing_client", + "clientId", + this.clientId + ) + } +

+ ${loggingInWith} + + + + ${ + inactiveProviders.length > 0 + ? html` + + ` + : "" + } + `; + } + + public async firstUpdated() { + // Fetch auth providers + try { + const response = await (window as any).providersPromise; + const authProviders = await response.json(); + + // Forward to main screen which will redirect to right onboarding page. + if ( + response.status === 400 && + authProviders.code === "onboarding_required" + ) { + location.href = "/?"; + return; + } + + if (authProviders.length === 0) { + alert("No auth providers returned. Unable to finish login."); + return; + } + + this._authProviders = authProviders; + this._authProvider = authProviders[0]; + } catch (err) { + // tslint:disable-next-line + console.error("Error loading auth providers", err); + } + } + + protected renderStyle() { + return html` + + `; + } + + private async _handleAuthProviderPick(ev) { + this._authProvider = ev.detail; + } +} +customElements.define("ha-authorize", HaAuthorize); diff --git a/src/auth/ha-pick-auth-provider.js b/src/auth/ha-pick-auth-provider.js index 3e31129069..6e63f3a1d5 100644 --- a/src/auth/ha-pick-auth-provider.js +++ b/src/auth/ha-pick-auth-provider.js @@ -4,13 +4,13 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import EventsMixin from "../mixins/events-mixin"; -import LocalizeLiteMixin from "../mixins/localize-lite-mixin"; +import { localizeLiteMixin } from "../mixins/localize-lite-mixin"; /* * @appliesMixin EventsMixin */ class HaPickAuthProvider extends EventsMixin( - LocalizeLiteMixin(PolymerElement) + localizeLiteMixin(PolymerElement) ) { static get template() { return html` diff --git a/src/data/auth.ts b/src/data/auth.ts new file mode 100644 index 0000000000..52c3af9099 --- /dev/null +++ b/src/data/auth.ts @@ -0,0 +1,5 @@ +export interface AuthProvider { + name: string; + id: string; + type: string; +} diff --git a/src/mixins/lit-localize-lite-mixin.ts b/src/mixins/lit-localize-lite-mixin.ts new file mode 100644 index 0000000000..4c6f4a25e5 --- /dev/null +++ b/src/mixins/lit-localize-lite-mixin.ts @@ -0,0 +1,60 @@ +import { + Constructor, + LitElement, + PropertyDeclarations, + PropertyValues, +} from "@polymer/lit-element"; +import { HomeAssistant } from "../types"; +import { getActiveTranslation } from "../util/hass-translation"; +import { LocalizeFunc, LocalizeMixin } from "./localize-base-mixin"; +import { localizeLiteBaseMixin } from "./localize-lite-base-mixin"; + +const empty = () => ""; + +interface LitLocalizeLiteMixin { + language: string; + resources: {}; + translationFragment: string; +} + +export const litLocalizeLiteMixin = ( + superClass: Constructor +): Constructor => + // @ts-ignore + class extends localizeLiteBaseMixin(superClass) { + protected hass?: HomeAssistant; + protected localize!: LocalizeFunc; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + localize: {}, + language: {}, + resources: {}, + translationFragment: {}, + }; + } + + constructor() { + super(); + // This will prevent undefined errors if called before connected to DOM. + this.localize = empty; + this.language = getActiveTranslation(); + } + + public connectedCallback(): void { + super.connectedCallback(); + this._initializeLocalizeLite(); + this.localize = this.__computeLocalize(this.language, this.resources); + } + + public updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + if ( + changedProperties.has("language") || + changedProperties.has("resources") + ) { + this.localize = this.__computeLocalize(this.language, this.resources); + } + } + }; diff --git a/src/mixins/localize-lite-base-mixin.ts b/src/mixins/localize-lite-base-mixin.ts new file mode 100644 index 0000000000..d9fe1e2a58 --- /dev/null +++ b/src/mixins/localize-lite-base-mixin.ts @@ -0,0 +1,44 @@ +/** + * Lite base mixin to add localization without depending on the Hass object. + */ +import { localizeBaseMixin } from "./localize-base-mixin"; +import { getTranslation } from "../util/hass-translation"; + +/** + * @polymerMixin + */ +export const localizeLiteBaseMixin = (superClass) => + class extends localizeBaseMixin(superClass) { + protected _initializeLocalizeLite() { + if (this.resources) { + return; + } + + if (!this.translationFragment) { + // In dev mode, we will issue a warning if after a second we are still + // not configured correctly. + if (__DEV__) { + setTimeout( + () => + !this.resources && + // tslint:disable-next-line + console.error( + "Forgot to pass in resources or set translationFragment for", + this.nodeName + ), + 1000 + ); + } + return; + } + + this._updateResources(); + } + + private async _updateResources() { + const { language, data } = await getTranslation(this.translationFragment); + this.resources = { + [language]: data, + }; + } + }; diff --git a/src/mixins/localize-lite-mixin.js b/src/mixins/localize-lite-mixin.js deleted file mode 100644 index 8df031b522..0000000000 --- a/src/mixins/localize-lite-mixin.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Lite mixin to add localization without depending on the Hass object. - */ -import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin"; -import { localizeBaseMixin } from "./localize-base-mixin"; -import { getActiveTranslation, getTranslation } from "../util/hass-translation"; - -/** - * @polymerMixin - */ -export default dedupingMixin( - (superClass) => - class extends localizeBaseMixin(superClass) { - static get properties() { - return { - language: { - type: String, - value: getActiveTranslation(), - }, - resources: Object, - // The fragment to load. - translationFragment: String, - /** - * Translates a string to the current `language`. Any parameters to the - * string should be passed in order, as follows: - * `localize(stringKey, param1Name, param1Value, param2Name, param2Value)` - */ - localize: { - type: Function, - computed: "__computeLocalize(language, resources, formats)", - }, - }; - } - - ready() { - super.ready(); - - if (this.resources) { - return; - } - - if (!this.translationFragment) { - // In dev mode, we will issue a warning if after a second we are still - // not configured correctly. - if (__DEV__) { - /* eslint-disable no-console */ - setTimeout( - () => - !this.resources && - console.error( - "Forgot to pass in resources or set translationFragment for", - this.nodeName - ), - 1000 - ); - } - return; - } - - this._updateResources(); - } - - async _updateResources() { - const { language, data } = await getTranslation( - this.translationFragment - ); - this.resources = { - [language]: data, - }; - } - } -); diff --git a/src/mixins/localize-lite-mixin.ts b/src/mixins/localize-lite-mixin.ts new file mode 100644 index 0000000000..16ffa615a1 --- /dev/null +++ b/src/mixins/localize-lite-mixin.ts @@ -0,0 +1,40 @@ +/** + * Lite mixin to add localization without depending on the Hass object. + */ +import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin"; +import { getActiveTranslation } from "../util/hass-translation"; +import { localizeLiteBaseMixin } from "./localize-lite-base-mixin"; + +/** + * @polymerMixin + */ +export const localizeLiteMixin = dedupingMixin( + (superClass) => + class extends localizeLiteBaseMixin(superClass) { + static get properties() { + return { + language: { + type: String, + value: getActiveTranslation(), + }, + resources: Object, + // The fragment to load. + translationFragment: String, + /** + * Translates a string to the current `language`. Any parameters to the + * string should be passed in order, as follows: + * `localize(stringKey, param1Name, param1Value, param2Name, param2Value)` + */ + localize: { + type: Function, + computed: "__computeLocalize(language, resources, formats)", + }, + }; + } + + public ready() { + super.ready(); + this._initializeLocalizeLite(); + } + } +); diff --git a/src/onboarding/ha-onboarding.js b/src/onboarding/ha-onboarding.js index 3a30c3c8c5..ec9d8b2149 100644 --- a/src/onboarding/ha-onboarding.js +++ b/src/onboarding/ha-onboarding.js @@ -4,7 +4,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-button/paper-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import localizeLiteMixin from "../mixins/localize-lite-mixin"; +import { localizeLiteMixin } from "../mixins/localize-lite-mixin"; class HaOnboarding extends localizeLiteMixin(PolymerElement) { static get template() {