Convert authorize page to lit (#2115)

* Convert authorize page to lit

* Don't use ha-markdown

* Simplify CSS
This commit is contained in:
Paulus Schoutsen 2018-11-26 14:10:01 +01:00 committed by GitHub
parent 07b65f37db
commit 5ab419534c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 312 additions and 231 deletions

View File

@ -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`
<style>

View File

@ -1,154 +0,0 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/ha-markdown";
import LocalizeLiteMixin from "../mixins/localize-lite-mixin";
import "./ha-auth-flow";
class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
static get template() {
return html`
<style>
ha-markdown {
display: block;
margin-bottom: 16px;
}
ha-markdown a {
color: var(--primary-color);
}
ha-markdown p:last-child {
margin-bottom: 0;
}
ha-pick-auth-provider {
display: block;
margin-top: 48px;
}
</style>
<template is="dom-if" if="[[!_authProviders]]">
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
</template>
<template is="dom-if" if="[[_authProviders]]">
<ha-markdown
content="[[_computeIntro(localize, clientId, _authProvider)]]"
></ha-markdown>
<ha-auth-flow
resources="[[resources]]"
client-id="[[clientId]]"
redirect-uri="[[redirectUri]]"
oauth2-state="[[oauth2State]]"
auth-provider="[[_authProvider]]"
step="{{step}}"
></ha-auth-flow>
<template is="dom-if" if="[[_computeMultiple(_authProviders)]]">
<ha-pick-auth-provider
resources="[[resources]]"
client-id="[[clientId]]"
auth-providers="[[_computeInactiveProvders(_authProvider, _authProviders)]]"
on-pick="_handleAuthProviderPick"
></ha-pick-auth-provider>
</template>
</template>
`;
}
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);

158
src/auth/ha-authorize.ts Normal file
View File

@ -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`
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
`;
}
// 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**",
`<b>${this._authProvider!.name}</b>`
);
const inactiveProviders = this._authProviders.filter(
(prv) => prv !== this._authProvider
);
return html`
${this.renderStyle()}
<p>
${
this.localize(
"ui.panel.page-authorize.authorizing_client",
"clientId",
this.clientId
)
}
</p>
${loggingInWith}
<ha-auth-flow
.resources="${this.resources}"
.clientId="${this.clientId}"
.redirectUri="${this.redirectUri}"
.oauth2State="${this.oauth2State}"
.authProvider="${this._authProvider}"
.step="{{step}}"
></ha-auth-flow>
${
inactiveProviders.length > 0
? html`
<ha-pick-auth-provider
.resources="${this.resources}"
.clientId="${this.clientId}"
.authProviders="${inactiveProviders}"
@pick="${this._handleAuthProviderPick}"
></ha-pick-auth-provider>
`
: ""
}
`;
}
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`
<style>
ha-pick-auth-provider {
display: block;
margin-top: 48px;
}
</style>
`;
}
private async _handleAuthProviderPick(ev) {
this._authProvider = ev.detail;
}
}
customElements.define("ha-authorize", HaAuthorize);

View File

@ -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`

5
src/data/auth.ts Normal file
View File

@ -0,0 +1,5 @@
export interface AuthProvider {
name: string;
id: string;
type: string;
}

View File

@ -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 = <T extends LitElement>(
superClass: Constructor<T>
): Constructor<T & LocalizeMixin & LitLocalizeLiteMixin> =>
// @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);
}
}
};

View File

@ -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,
};
}
};

View File

@ -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,
};
}
}
);

View File

@ -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();
}
}
);

View File

@ -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() {