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`
-
-
- [[localize('ui.panel.page-authorize.initializing')]]
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- 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() {