From d24bc3c07c0dc2ed192e61036c7a4c1830489475 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 22 Nov 2018 04:15:22 +0100 Subject: [PATCH 1/4] Dont change config on init (#2044) * dont change config on init * set default title empty * used firstUpdated instead of updated * prevent double events * check if val changed * typing * clean * lint * clean * prettier is having a fight --- .../lovelace/components/hui-entity-editor.ts | 4 +- .../components/hui-theme-select-editor.ts | 7 ++- .../hui-entities-card-editor.ts | 43 ++++++++------ .../config-elements/hui-glance-card-editor.ts | 56 +++++++++++-------- src/panels/lovelace/editor/hui-edit-card.ts | 9 ++- 5 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/panels/lovelace/components/hui-entity-editor.ts b/src/panels/lovelace/components/hui-entity-editor.ts index 886dc206a8..7c9212e3a1 100644 --- a/src/panels/lovelace/components/hui-entity-editor.ts +++ b/src/panels/lovelace/components/hui-entity-editor.ts @@ -52,7 +52,7 @@ export class HuiEntityEditor extends LitElement { private _addEntity() { const newConfigEntities = this.entities!.concat({ entity: "" }); - fireEvent(this, "change", { entities: newConfigEntities }); + fireEvent(this, "entities-changed", { entities: newConfigEntities }); } private _valueChanged(ev: Event): void { @@ -68,7 +68,7 @@ export class HuiEntityEditor extends LitElement { }; } - fireEvent(this, "change", { entities: newConfigEntities }); + fireEvent(this, "entities-changed", { entities: newConfigEntities }); } private renderStyle(): TemplateResult { diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 8c8a4b4de1..71c9017183 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -31,7 +31,7 @@ export class HuiThemeSelectionEditor extends hassLocalizeLitMixin(LitElement) { > ${ @@ -57,8 +57,11 @@ export class HuiThemeSelectionEditor extends hassLocalizeLitMixin(LitElement) { } private _changed(ev): void { + if (!this.hass || ev.target.value === "") { + return; + } this.value = ev.target.value; - fireEvent(this, "change"); + fireEvent(this, "theme-changed"); } } diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index f4d761ddda..25452f64f2 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -26,11 +26,15 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement) private _configEntities?: ConfigEntity[]; static get properties(): PropertyDeclarations { - return { - hass: {}, - _config: {}, - _configEntities: {}, - }; + return { hass: {}, _config: {}, _configEntities: {} }; + } + + get _title(): string { + return this._config!.title || ""; + } + + get _theme(): string { + return this._config!.theme || "Backend-selected"; } public setConfig(config: Config): void { @@ -47,20 +51,20 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement) ${this.renderStyle()} { + if (!this._originalConfig) { + return; + } const conf = this._originalConfig; - const tag = conf!.type.startsWith(CUSTOM_TYPE_PREFIX) + const tag = conf.type.startsWith(CUSTOM_TYPE_PREFIX) ? conf!.type.substr(CUSTOM_TYPE_PREFIX.length) : `hui-${conf!.type}-card`; @@ -362,8 +364,9 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { configElement.addEventListener("config-changed", (ev) => this._handleUIConfigChanged(ev.detail.config) ); - this._configValue = { format: "json", value: conf! }; + this._configValue = { format: "json", value: conf }; this._configElement = configElement; + this._updatePreview(conf); } } From 14409ff5b77a751d13f277195c2b217138fc3013 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 21 Nov 2018 21:11:00 +0100 Subject: [PATCH 2/4] Use overrideIcon via data binding (#2078) --- src/components/entity/state-badge.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/entity/state-badge.js b/src/components/entity/state-badge.js index 783c36467a..ab3e0906ab 100644 --- a/src/components/entity/state-badge.js +++ b/src/components/entity/state-badge.js @@ -44,7 +44,7 @@ class StateBadge extends PolymerElement { id="icon" data-domain$="[[_computeDomain(stateObj)]]" data-state$="[[stateObj.state]]" - icon="[[_computeIcon(stateObj)]]" + icon="[[_computeIcon(stateObj, overrideIcon)]]" > `; } @@ -63,8 +63,8 @@ class StateBadge extends PolymerElement { return computeStateDomain(stateObj); } - _computeIcon(stateObj) { - return this.overrideIcon || stateIcon(stateObj); + _computeIcon(stateObj, overrideIcon) { + return overrideIcon || stateIcon(stateObj); } _updateIconAppearance(newVal) { From 0be0e9792f287703976b30b668ebc4893aec095d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Nov 2018 14:10:01 +0100 Subject: [PATCH 3/4] Convert authorize page to lit (#2115) * Convert authorize page to lit * Don't use ha-markdown * Simplify CSS --- src/auth/ha-auth-flow.js | 4 +- src/auth/ha-authorize.js | 154 ------------------------ src/auth/ha-authorize.ts | 158 +++++++++++++++++++++++++ src/auth/ha-pick-auth-provider.js | 4 +- src/data/auth.ts | 5 + src/mixins/lit-localize-lite-mixin.ts | 60 ++++++++++ src/mixins/localize-lite-base-mixin.ts | 44 +++++++ src/mixins/localize-lite-mixin.js | 72 ----------- src/mixins/localize-lite-mixin.ts | 40 +++++++ src/onboarding/ha-onboarding.js | 2 +- 10 files changed, 312 insertions(+), 231 deletions(-) delete mode 100644 src/auth/ha-authorize.js create mode 100644 src/auth/ha-authorize.ts create mode 100644 src/data/auth.ts create mode 100644 src/mixins/lit-localize-lite-mixin.ts create mode 100644 src/mixins/localize-lite-base-mixin.ts delete mode 100644 src/mixins/localize-lite-mixin.js create mode 100644 src/mixins/localize-lite-mixin.ts 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() { From 0b17a85c3bdf8217de68b7610593d3adbee7d701 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Nov 2018 14:11:11 +0100 Subject: [PATCH 4/4] Bumped version to 20181121.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 17eb385449..518c7e1a4d 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20181121.0", + version="20181121.1", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors",