From f489d88be4b1ae77beae66e77cb41e0eab9a0cd1 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Sat, 3 Nov 2018 14:15:51 -0400 Subject: [PATCH 01/68] Revert RTL PR (#1975) * Revert RTL PR * Missed this change --- src/layouts/home-assistant-main.js | 17 +---------------- src/translations/translationMetadata.json | 3 +-- src/types.ts | 1 - 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/layouts/home-assistant-main.js b/src/layouts/home-assistant-main.js index 2dff9ed87b..aed68ad85c 100644 --- a/src/layouts/home-assistant-main.js +++ b/src/layouts/home-assistant-main.js @@ -27,9 +27,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) { /* remove the grey tap highlights in iOS on the fullscreen touch targets */ -webkit-tap-highlight-color: rgba(0,0,0,0); } - :host([rtl]) { - direction: rtl; - } iron-pages, ha-sidebar { /* allow a light tap highlight on the actual interface elements */ -webkit-tap-highlight-color: rgba(0,0,0,0.1); @@ -45,7 +42,7 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) { - + @@ -72,11 +69,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) { type: Boolean, computed: "computeDockedSidebar(hass)", }, - rtl: { - type: Boolean, - reflectToAttribute: true, - computed: "computeRTL(hass)", - }, }; } @@ -131,13 +123,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) { return hass.dockedSidebar; } - computeRTL(hass) { - return ( - hass.translationMetadata.translations[hass.selectedLanguage].isRTL || - false - ); - } - _computeDisableSwipe(hass) { return NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1; } diff --git a/src/translations/translationMetadata.json b/src/translations/translationMetadata.json index 3a31673c25..faaceac74c 100644 --- a/src/translations/translationMetadata.json +++ b/src/translations/translationMetadata.json @@ -51,8 +51,7 @@ "nativeName": "Schwiizerdütsch" }, "he": { - "nativeName": "עברית", - "isRTL": true + "nativeName": "עברית" }, "hi": { "nativeName": "हिन्दी" diff --git a/src/types.ts b/src/types.ts index 0699af1e67..6cc016d2ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,7 +44,6 @@ export interface Panel { export interface Translation { nativeName: string; - isRTL: boolean; fingerprints: { [fragment: string]: string }; } From 481004237306f9d83a014e453164af47962309a1 Mon Sep 17 00:00:00 2001 From: Michael Scherer Date: Sat, 3 Nov 2018 21:51:55 +0100 Subject: [PATCH 02/68] Hide state if its unknown e.g. the climate entity does not have one (#1977) * Hide state if its unknown e.g. the climate entity does not have one * state can not be null * better comparsion * use double quotes --- src/components/ha-climate-state.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/ha-climate-state.js b/src/components/ha-climate-state.js index 8cdc2e37d9..4453f03b5a 100644 --- a/src/components/ha-climate-state.js +++ b/src/components/ha-climate-state.js @@ -32,9 +32,11 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
- - [[_localizeState(stateObj.state)]] - + [[computeTarget(hass, stateObj)]]
@@ -101,6 +103,10 @@ class HaClimateState extends LocalizeMixin(PolymerElement) { return ""; } + _hasKnownState(state) { + return state !== "unknown"; + } + _localizeState(state) { return this.localize(`state.climate.${state}`) || state; } From bcbf0ba75afc26fa257310ebc2de519e4bf5efcb Mon Sep 17 00:00:00 2001 From: Michael Scherer Date: Sat, 3 Nov 2018 22:57:45 +0100 Subject: [PATCH 03/68] add vscode extension recommondations (#1978) --- .gitignore | 3 ++- .vscode/extensions.json | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100755 .vscode/extensions.json diff --git a/.gitignore b/.gitignore index 6bfc09e27f..0cd680bb81 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,8 @@ bin dist # vscode -.vscode +.vscode/* +!.vscode/extensions.json # Secrets .lokalise_token diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100755 index 0000000000..276dd9eedd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "eg2.tslint", + "esbenp.prettier-vscode" + ] +} \ No newline at end of file From 1ca242405b64d095438900bda73d4e8e8f326c80 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 4 Nov 2018 10:01:33 +0100 Subject: [PATCH 04/68] Convert auth to TS (#1976) * Convert auth to TS * Lint * Update HA-JS-WS to 3.2.0 * Migrate ws collections to TS * Upgrade to latest HAWS * Bump HAWS * Lint * Add types to WS calls --- package.json | 2 +- .../{external_auth.js => external_auth.ts} | 60 +++++++++++++------ .../{token_storage.js => token_storage.ts} | 23 ++++++- ...s-notifications.js => ws-notifications.ts} | 9 ++- src/data/ws-panels.js | 10 ---- src/data/ws-panels.ts | 14 +++++ src/data/ws-themes.js | 15 ----- src/data/ws-themes.ts | 25 ++++++++ src/data/ws-user.js | 4 -- src/data/ws-user.ts | 20 +++++++ src/entrypoints/{core.js => core.ts} | 27 +++++++-- src/types.ts | 30 ++++++++-- webpack.config.js | 4 +- yarn.lock | 8 +-- 14 files changed, 182 insertions(+), 69 deletions(-) rename src/common/auth/{external_auth.js => external_auth.ts} (60%) rename src/common/auth/{token_storage.js => token_storage.ts} (67%) rename src/data/{ws-notifications.js => ws-notifications.ts} (62%) delete mode 100644 src/data/ws-panels.js create mode 100644 src/data/ws-panels.ts delete mode 100644 src/data/ws-themes.js create mode 100644 src/data/ws-themes.ts delete mode 100644 src/data/ws-user.js create mode 100644 src/data/ws-user.ts rename src/entrypoints/{core.js => core.ts} (81%) diff --git a/package.json b/package.json index 0e3308960d..1e6bc6abf7 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "es6-object-assign": "^1.1.0", "eslint-import-resolver-webpack": "^0.10.1", "fecha": "^2.3.3", - "home-assistant-js-websocket": "^3.1.5", + "home-assistant-js-websocket": "^3.2.4", "intl-messageformat": "^2.2.0", "jquery": "^3.3.1", "js-yaml": "^3.12.0", diff --git a/src/common/auth/external_auth.js b/src/common/auth/external_auth.ts similarity index 60% rename from src/common/auth/external_auth.js rename to src/common/auth/external_auth.ts index 2a9d3f1483..4f5613af15 100644 --- a/src/common/auth/external_auth.js +++ b/src/common/auth/external_auth.ts @@ -6,6 +6,34 @@ import { Auth } from "home-assistant-js-websocket"; const CALLBACK_SET_TOKEN = "externalAuthSetToken"; const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken"; +interface BasePayload { + callback: string; +} + +interface RefreshTokenResponse { + access_token: string; + expires_in: number; +} + +declare global { + interface Window { + externalApp?: { + getExternalAuth(payload: BasePayload); + revokeExternalAuth(payload: BasePayload); + }; + webkit?: { + messageHandlers: { + getExternalAuth: { + postMessage(payload: BasePayload); + }; + revokeExternalAuth: { + postMessage(payload: BasePayload); + }; + }; + }; + } +} + if (!window.externalApp && !window.webkit) { throw new Error( "External auth requires either externalApp or webkit defined on Window object." @@ -14,21 +42,24 @@ if (!window.externalApp && !window.webkit) { export default class ExternalAuth extends Auth { constructor(hassUrl) { - super(); - - this.data = { + super({ hassUrl, + clientId: "", + refresh_token: "", access_token: "", + expires_in: 0, // This will trigger connection to do a refresh right away expires: 0, - }; + }); } - async refreshAccessToken() { - const responseProm = new Promise((resolve, reject) => { - window[CALLBACK_SET_TOKEN] = (success, data) => - success ? resolve(data) : reject(data); - }); + public async refreshAccessToken() { + const responseProm = new Promise( + (resolve, reject) => { + window[CALLBACK_SET_TOKEN] = (success, data) => + success ? resolve(data) : reject(data); + } + ); // Allow promise to set resolve on window object. await 0; @@ -38,23 +69,18 @@ export default class ExternalAuth extends Auth { if (window.externalApp) { window.externalApp.getExternalAuth(callbackPayload); } else { - window.webkit.messageHandlers.getExternalAuth.postMessage( + window.webkit!.messageHandlers.getExternalAuth.postMessage( callbackPayload ); } - // Response we expect back: - // { - // "access_token": "qwere", - // "expires_in": 1800 - // } const tokens = await responseProm; this.data.access_token = tokens.access_token; this.data.expires = tokens.expires_in * 1000 + Date.now(); } - async revoke() { + public async revoke() { const responseProm = new Promise((resolve, reject) => { window[CALLBACK_REVOKE_TOKEN] = (success, data) => success ? resolve(data) : reject(data); @@ -68,7 +94,7 @@ export default class ExternalAuth extends Auth { if (window.externalApp) { window.externalApp.revokeExternalAuth(callbackPayload); } else { - window.webkit.messageHandlers.revokeExternalAuth.postMessage( + window.webkit!.messageHandlers.revokeExternalAuth.postMessage( callbackPayload ); } diff --git a/src/common/auth/token_storage.js b/src/common/auth/token_storage.ts similarity index 67% rename from src/common/auth/token_storage.js rename to src/common/auth/token_storage.ts index 6483665720..335cb65075 100644 --- a/src/common/auth/token_storage.js +++ b/src/common/auth/token_storage.ts @@ -1,5 +1,18 @@ +import { AuthData } from "home-assistant-js-websocket"; + const storage = window.localStorage || {}; +declare global { + interface Window { + __tokenCache: { + // undefined: we haven't loaded yet + // null: none stored + tokens?: AuthData | null; + writeEnabled?: boolean; + }; + } +} + // So that core.js and main app hit same shared object. let tokenCache = window.__tokenCache; if (!tokenCache) { @@ -15,18 +28,22 @@ export function askWrite() { ); } -export function saveTokens(tokens) { +export function saveTokens(tokens: AuthData | null) { tokenCache.tokens = tokens; if (tokenCache.writeEnabled) { try { storage.hassTokens = JSON.stringify(tokens); - } catch (err) {} // eslint-disable-line + } catch (err) { + // write failed, ignore it. Happens if storage is full or private mode. + } } } export function enableWrite() { tokenCache.writeEnabled = true; - saveTokens(tokenCache.tokens); + if (tokenCache.tokens) { + saveTokens(tokenCache.tokens); + } } export function loadTokens() { diff --git a/src/data/ws-notifications.js b/src/data/ws-notifications.ts similarity index 62% rename from src/data/ws-notifications.js rename to src/data/ws-notifications.ts index 1fb3b5e07d..261a32aac6 100644 --- a/src/data/ws-notifications.js +++ b/src/data/ws-notifications.ts @@ -1,4 +1,4 @@ -import { createCollection } from "home-assistant-js-websocket"; +import { createCollection, Connection } from "home-assistant-js-websocket"; const fetchNotifications = (conn) => conn.sendMessagePromise({ @@ -11,8 +11,11 @@ const subscribeUpdates = (conn, store) => "persistent_notifications_updated" ); -export const subscribeNotifications = (conn, onChange) => - createCollection( +export const subscribeNotifications = ( + conn: Connection, + onChange: (notifications: Notification[]) => void +) => + createCollection( "_ntf", fetchNotifications, subscribeUpdates, diff --git a/src/data/ws-panels.js b/src/data/ws-panels.js deleted file mode 100644 index 515be6628d..0000000000 --- a/src/data/ws-panels.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createCollection } from "home-assistant-js-websocket"; - -export const subscribePanels = (conn, onChange) => - createCollection( - "_pnl", - (conn_) => conn_.sendMessagePromise({ type: "get_panels" }), - null, - conn, - onChange - ); diff --git a/src/data/ws-panels.ts b/src/data/ws-panels.ts new file mode 100644 index 0000000000..88ec490d06 --- /dev/null +++ b/src/data/ws-panels.ts @@ -0,0 +1,14 @@ +import { createCollection, Connection } from "home-assistant-js-websocket"; +import { Panels } from "../types"; + +export const subscribePanels = ( + conn: Connection, + onChange: (panels: Panels) => void +) => + createCollection( + "_pnl", + () => conn.sendMessagePromise({ type: "get_panels" }), + undefined, + conn, + onChange + ); diff --git a/src/data/ws-themes.js b/src/data/ws-themes.js deleted file mode 100644 index d5e8cfba74..0000000000 --- a/src/data/ws-themes.js +++ /dev/null @@ -1,15 +0,0 @@ -import { createCollection } from "home-assistant-js-websocket"; - -const fetchThemes = (conn) => - conn.sendMessagePromise({ - type: "frontend/get_themes", - }); - -const subscribeUpdates = (conn, store) => - conn.subscribeEvents( - (event) => store.setState(event.data, true), - "themes_updated" - ); - -export const subscribeThemes = (conn, onChange) => - createCollection("_thm", fetchThemes, subscribeUpdates, conn, onChange); diff --git a/src/data/ws-themes.ts b/src/data/ws-themes.ts new file mode 100644 index 0000000000..13f37844d4 --- /dev/null +++ b/src/data/ws-themes.ts @@ -0,0 +1,25 @@ +import { createCollection, Connection } from "home-assistant-js-websocket"; +import { Themes } from "../types"; + +const fetchThemes = (conn) => + conn.sendMessagePromise({ + type: "frontend/get_themes", + }); + +const subscribeUpdates = (conn, store) => + conn.subscribeEvents( + (event) => store.setState(event.data, true), + "themes_updated" + ); + +export const subscribeThemes = ( + conn: Connection, + onChange: (themes: Themes) => void +) => + createCollection( + "_thm", + fetchThemes, + subscribeUpdates, + conn, + onChange + ); diff --git a/src/data/ws-user.js b/src/data/ws-user.js deleted file mode 100644 index 9e33811a62..0000000000 --- a/src/data/ws-user.js +++ /dev/null @@ -1,4 +0,0 @@ -import { createCollection, getUser } from "home-assistant-js-websocket"; - -export const subscribeUser = (conn, onChange) => - createCollection("_usr", (conn_) => getUser(conn_), null, conn, onChange); diff --git a/src/data/ws-user.ts b/src/data/ws-user.ts new file mode 100644 index 0000000000..e627c20673 --- /dev/null +++ b/src/data/ws-user.ts @@ -0,0 +1,20 @@ +import { + createCollection, + getUser, + Connection, +} from "home-assistant-js-websocket"; +import { User } from "../types"; + +export const subscribeUser = ( + conn: Connection, + onChange: (user: User) => void +) => + createCollection( + "_usr", + // the getUser command is mistyped in current verrsion of HAWS. + // Fixed in 3.2.5 + () => (getUser(conn) as unknown) as Promise, + undefined, + conn, + onChange + ); diff --git a/src/entrypoints/core.js b/src/entrypoints/core.ts similarity index 81% rename from src/entrypoints/core.js rename to src/entrypoints/core.ts index 560fb8c96c..c75e9680a6 100644 --- a/src/entrypoints/core.js +++ b/src/entrypoints/core.ts @@ -5,12 +5,21 @@ import { subscribeEntities, subscribeServices, ERR_INVALID_AUTH, + Auth, + Connection, } from "home-assistant-js-websocket"; import { loadTokens, saveTokens } from "../common/auth/token_storage"; import { subscribePanels } from "../data/ws-panels"; import { subscribeThemes } from "../data/ws-themes"; import { subscribeUser } from "../data/ws-user"; +import { HomeAssistant } from "../types"; + +declare global { + interface Window { + hassConnection: Promise<{ auth: Auth; conn: Connection }>; + } +} const hassUrl = `${location.protocol}//${location.host}`; const isExternal = location.search.includes("external_auth=1"); @@ -33,7 +42,7 @@ const connProm = async (auth) => { // Clear url if we have been able to establish a connection if (location.search.includes("auth_callback=1")) { - history.replaceState(null, null, location.pathname); + history.replaceState(null, "", location.pathname); } return { auth, conn }; @@ -43,7 +52,9 @@ const connProm = async (auth) => { } // We can get invalid auth if auth tokens were stored that are no longer valid // Clear stored tokens. - if (!isExternal) saveTokens(null); + if (!isExternal) { + saveTokens(null); + } auth = await authProm(); const conn = await createConnection({ auth }); return { auth, conn }; @@ -54,7 +65,9 @@ window.hassConnection = authProm().then(connProm); // Start fetching some of the data that we will need. window.hassConnection.then(({ conn }) => { - const noop = () => {}; + const noop = () => { + // do nothing + }; subscribeEntities(conn, noop); subscribeConfig(conn, noop); subscribeServices(conn, noop); @@ -64,8 +77,12 @@ window.hassConnection.then(({ conn }) => { }); window.addEventListener("error", (e) => { - const homeAssistant = document.querySelector("home-assistant"); - if (homeAssistant && homeAssistant.hass && homeAssistant.hass.callService) { + const homeAssistant = document.querySelector("home-assistant") as any; + if ( + homeAssistant && + homeAssistant.hass && + (homeAssistant.hass as HomeAssistant).callService + ) { homeAssistant.hass.callService("system_log", "write", { logger: `frontend.${ __DEV__ ? "js_dev" : "js" diff --git a/src/types.ts b/src/types.ts index 6cc016d2ee..281eb63a6d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,12 @@ import { HassEntityAttributeBase, } from "home-assistant-js-websocket"; +declare global { + var __DEV__: boolean; + var __BUILD__: "latest" | "es5"; + var __VERSION__: string; +} + export interface Credential { auth_provider_type: string; auth_provider_id: string; @@ -34,6 +40,11 @@ export interface Theme { "accent-color": string; } +export interface Themes { + default_theme: string; + themes: { [key: string]: Theme }; +} + export interface Panel { component_name: string; config?: { [key: string]: any }; @@ -42,22 +53,31 @@ export interface Panel { url_path: string; } +export interface Panels { + [name: string]: Panel; +} + export interface Translation { nativeName: string; fingerprints: { [fragment: string]: string }; } +export interface Notification { + notification_id: string; + message: string; + title: string; + status: "read" | "unread"; + created_at: string; +} + export interface HomeAssistant { auth: Auth; connection: Connection; connected: boolean; states: HassEntities; config: HassConfig; - themes: { - default_theme: string; - themes: { [key: string]: Theme }; - }; - panels: { [key: string]: Panel }; + themes: Themes; + panels: Panels; panelUrl: string; language: string; resources: { [key: string]: any }; diff --git a/webpack.config.js b/webpack.config.js index 87b7105aed..4bc3fb1cc4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -41,7 +41,7 @@ function createConfig(isProdBuild, latestBuild) { app: "./src/entrypoints/app.js", authorize: "./src/entrypoints/authorize.js", onboarding: "./src/entrypoints/onboarding.js", - core: "./src/entrypoints/core.js", + core: "./src/entrypoints/core.ts", compatibility: "./src/entrypoints/compatibility.js", "custom-panel": "./src/entrypoints/custom-panel.js", "hass-icons": "./src/entrypoints/hass-icons.js", @@ -173,7 +173,7 @@ function createConfig(isProdBuild, latestBuild) { swDest: "service_worker.js", importWorkboxFrom: "local", include: [ - /core.js$/, + /core.ts$/, /app.js$/, /custom-panel.js$/, /hass-icons.js$/, diff --git a/yarn.lock b/yarn.lock index 38b4426f63..6bb4ab49fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7426,10 +7426,10 @@ hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== -home-assistant-js-websocket@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.1.5.tgz#56358660bbccb5f24e16f9197bbad12739f9b756" - integrity sha512-BlANSkA9ob6wlUJCYS26n1tfMla+aJzB2rhhXSFi0iiTtWWfuOlcSOw8pxjeWhh1I9oAcbQ4qxhB7Np1EXG+Og== +home-assistant-js-websocket@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.2.4.tgz#0c4212e6ac57b60ed939aa420253994e4f9f0bef" + integrity sha512-DaHpWIjJFLwTWNbHeGSCEUsbeyLUWAyWUgsYkiVWxzbfm+vqC5YaLNRu+Ma64SQYh5yGSYr7h25p2hip1GvyhQ== home-or-tmp@^2.0.0: version "2.0.0" From e5fe2950afa46053b76252f050b3114185975ab9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Nov 2018 09:59:19 +0100 Subject: [PATCH 05/68] Split up cloud card (#1983) * Split up cloud card * Fix quotes --- src/panels/config/cloud/cloud-alexa-pref.ts | 83 ++++++++++++++++ src/panels/config/cloud/cloud-google-pref.ts | 97 +++++++++++++++++++ src/panels/config/cloud/data.ts | 17 ++++ .../config/cloud/ha-config-cloud-account.js | 91 +++-------------- src/panels/config/cloud/types.ts | 24 +++++ 5 files changed, 234 insertions(+), 78 deletions(-) create mode 100644 src/panels/config/cloud/cloud-alexa-pref.ts create mode 100644 src/panels/config/cloud/cloud-google-pref.ts create mode 100644 src/panels/config/cloud/data.ts create mode 100644 src/panels/config/cloud/types.ts diff --git a/src/panels/config/cloud/cloud-alexa-pref.ts b/src/panels/config/cloud/cloud-alexa-pref.ts new file mode 100644 index 0000000000..a076612f0d --- /dev/null +++ b/src/panels/config/cloud/cloud-alexa-pref.ts @@ -0,0 +1,83 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-button/paper-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-toggle-button/paper-toggle-button"; +// tslint:disable-next-line +import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button"; + +import { fireEvent } from "../../../common/dom/fire_event"; +import { HomeAssistant } from "../../../types"; +import { updatePref } from "./data"; +import { CloudStatusLoggedIn } from "./types"; + +export class CloudAlexaPref extends LitElement { + public hass?: HomeAssistant; + public cloudStatus?: CloudStatusLoggedIn; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + cloudStatus: {}, + }; + } + + protected render(): TemplateResult { + return html` + ${this.renderStyle()} + + +
+ With the Alexa integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Alexa-enabled device. + + This integration requires an Alexa-enabled device like the Amazon Echo. +
+
+ `; + } + + private async _toggleChanged(ev) { + const toggle = ev.target as PaperToggleButtonElement; + try { + await updatePref(this.hass!, { alexa_enabled: toggle.checked! }); + fireEvent(this, "ha-refresh-cloud-status"); + } catch (err) { + toggle.checked = !toggle.checked; + } + } + + private renderStyle(): TemplateResult { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-alexa-pref": CloudAlexaPref; + } +} + +customElements.define("cloud-alexa-pref", CloudAlexaPref); diff --git a/src/panels/config/cloud/cloud-google-pref.ts b/src/panels/config/cloud/cloud-google-pref.ts new file mode 100644 index 0000000000..19aaf7445d --- /dev/null +++ b/src/panels/config/cloud/cloud-google-pref.ts @@ -0,0 +1,97 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-button/paper-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-toggle-button/paper-toggle-button"; +// tslint:disable-next-line +import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button"; +import "../../../components/buttons/ha-call-api-button"; + +import { fireEvent } from "../../../common/dom/fire_event"; +import { HomeAssistant } from "../../../types"; +import { updatePref } from "./data"; +import { CloudStatusLoggedIn } from "./types"; + +export class CloudGooglePref extends LitElement { + public hass?: HomeAssistant; + public cloudStatus?: CloudStatusLoggedIn; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + cloudStatus: {}, + }; + } + + protected render(): TemplateResult { + return html` + ${this.renderStyle()} + + +
+ With the Google Assistant integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Google Assistant-enabled device. + + This integration requires a Google Assistant-enabled device like the Google Home or Android phone. +
+
+ Sync devices +
+
+ `; + } + + private async _toggleChanged(ev) { + const toggle = ev.target as PaperToggleButtonElement; + try { + await updatePref(this.hass!, { google_enabled: toggle.checked! }); + fireEvent(this, "ha-refresh-cloud-status"); + } catch (err) { + toggle.checked = !toggle.checked; + } + } + + private renderStyle(): TemplateResult { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-google-pref": CloudGooglePref; + } +} + +customElements.define("cloud-google-pref", CloudGooglePref); diff --git a/src/panels/config/cloud/data.ts b/src/panels/config/cloud/data.ts new file mode 100644 index 0000000000..afce1399d8 --- /dev/null +++ b/src/panels/config/cloud/data.ts @@ -0,0 +1,17 @@ +import { HomeAssistant } from "../../../types"; +import { SubscriptionInfo } from "./types"; + +export const fetchSubscriptionInfo = (hass: HomeAssistant) => + hass.callWS({ type: "cloud/subscription" }); + +export const updatePref = ( + hass: HomeAssistant, + prefs: { + google_enabled?: boolean; + alexa_enabled?: boolean; + } +) => + hass.callWS({ + type: "cloud/update_prefs", + ...prefs, + }); diff --git a/src/panels/config/cloud/ha-config-cloud-account.js b/src/panels/config/cloud/ha-config-cloud-account.js index 3aabd609de..cba2d25987 100644 --- a/src/panels/config/cloud/ha-config-cloud-account.js +++ b/src/panels/config/cloud/ha-config-cloud-account.js @@ -15,6 +15,10 @@ import formatDateTime from "../../../common/datetime/format_date_time"; import EventsMixin from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; +import { fetchSubscriptionInfo } from "./data"; +import "./cloud-alexa-pref"; +import "./cloud-google-pref"; + /* * @appliesMixin EventsMixin * @appliesMixin LocalizeMixin @@ -61,14 +65,6 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { color: var(--primary-color); font-weight: 500; } - a { - color: var(--primary-color); - } - paper-card > paper-toggle-button { - position: absolute; - right: 8px; - top: 16px; - }
@@ -115,56 +111,15 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {

- - -
- With the Alexa integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Alexa-enabled device. - -

This integration requires an Alexa-enabled device like the Amazon Echo.

-
-
+ - - -
- With the Google Assistant integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Google Assistant-enabled device. - -

This integration requires a Google Assistant-enabled device like the Google Home or Android phone.

-
-
- Sync devices -
-
+
@@ -189,7 +144,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { } async _fetchSubscriptionInfo() { - this._subscription = await this.hass.callWS({ type: "cloud/subscription" }); + this._subscription = await fetchSubscriptionInfo; if (this._subscription.provider && this.cloudStatus.cloud !== "connected") { this.fire("ha-refresh-cloud-status"); } @@ -220,26 +175,6 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { return description; } - - _alexaChanged(ev) { - this._handleToggleChange("alexa_enabled", ev.target); - } - - _googleChanged(ev) { - this._handleToggleChange("google_enabled", ev.target); - } - - async _handleToggleChange(property, element) { - try { - await this.hass.callWS({ - type: "cloud/update_prefs", - [property]: element.checked, - }); - this.fire("ha-refresh-cloud-status"); - } catch (err) { - element.checked = !element.checked; - } - } } customElements.define("ha-config-cloud-account", HaConfigCloudAccount); diff --git a/src/panels/config/cloud/types.ts b/src/panels/config/cloud/types.ts new file mode 100644 index 0000000000..98d8ff668e --- /dev/null +++ b/src/panels/config/cloud/types.ts @@ -0,0 +1,24 @@ +interface EntityFilter { + include_domains: string[]; + include_entities: string[]; + exclude_domains: string[]; + exclude_entities: string[]; +} +interface CloudStatusBase { + logged_in: boolean; + cloud: "disconnected" | "connecting" | "connected"; +} + +export type CloudStatusLoggedIn = CloudStatusBase & { + email: string; + google_enabled: boolean; + google_entities: EntityFilter; + alexa_enabled: boolean; + alexa_entities: EntityFilter; +}; + +export type CloudStatus = CloudStatusBase | CloudStatusLoggedIn; + +export interface SubscriptionInfo { + human_description: string; +} From cf19ceb19349eb8d26cb0e8bc31feaca00c28047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Kihlstr=C3=B6m?= Date: Mon, 5 Nov 2018 16:28:36 +0100 Subject: [PATCH 06/68] Filter out non number states instead of assigning them 0 (#1987) --- src/panels/lovelace/cards/hui-sensor-card.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/cards/hui-sensor-card.js b/src/panels/lovelace/cards/hui-sensor-card.js index f0fc56a771..9de1bacbb0 100644 --- a/src/panels/lovelace/cards/hui-sensor-card.js +++ b/src/panels/lovelace/cards/hui-sensor-card.js @@ -115,7 +115,9 @@ class HuiSensorCard extends EventsMixin(LitElement) { } _getValueArr(items) { - return items.map((item) => Number(item.state) || 0); + return items + .map((item) => Number(item.state)) + .filter((val) => !Number.isNaN(val)); } _calcCoordinates(values, width, height) { From 856ef3496416ada68c103cbea0991ccfb8209ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Kihlstr=C3=B6m?= Date: Mon, 5 Nov 2018 19:46:41 +0100 Subject: [PATCH 07/68] error fix & literals (#1993) --- src/panels/lovelace/cards/hui-sensor-card.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/panels/lovelace/cards/hui-sensor-card.js b/src/panels/lovelace/cards/hui-sensor-card.js index 9de1bacbb0..586b76976e 100644 --- a/src/panels/lovelace/cards/hui-sensor-card.js +++ b/src/panels/lovelace/cards/hui-sensor-card.js @@ -140,7 +140,6 @@ class HuiSensorCard extends EventsMixin(LitElement) { } _getPath(points) { - const SPACE = " "; let next; let Z; const X = 0; @@ -148,20 +147,17 @@ class HuiSensorCard extends EventsMixin(LitElement) { let path = ""; let point = points[0]; - path += "M" + point[X] + "," + point[Y]; - const first = point; + path += `M ${point[X]},${point[Y]}`; for (let i = 0; i < points.length; i++) { next = points[i]; Z = this._midPoint(point[X], point[Y], next[X], next[Y]); - path += SPACE + Z[X] + "," + Z[Y]; - path += "Q" + Math.floor(next[X]) + "," + next[Y]; + path += ` ${Z[X]},${Z[Y]}`; + path += ` Q${next[X]},${next[Y]}`; point = next; } - const second = points[1]; - Z = this._midPoint(first[X], first[Y], second[X], second[Y]); - path += SPACE + Math.floor(next[X]) + "." + points[points.length - 1]; + path += ` ${next[X]},${next[Y]}`; return path; } From cdb2093ea6afb89bc43ee2a4a6f36018a28640b9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Nov 2018 10:09:09 +0100 Subject: [PATCH 08/68] Ts all the tests (#1998) * Convert tests to TypeScript * Add types for tests * Rename files to TS * Fix up test imports * Fix TSC errors * Liiiint * Add types to util method signatures * Some more types --- package.json | 12 +- src/common/config/is_component_loaded.js | 4 - src/common/config/is_component_loaded.ts | 9 ++ src/common/config/{is_pwa.js => is_pwa.ts} | 2 +- src/common/config/location_name.js | 4 - src/common/config/location_name.ts | 6 + src/common/{const.js => const.ts} | 0 ...n_to_seconds.js => duration_to_seconds.ts} | 2 +- .../{format_date.js => format_date.ts} | 7 +- ...ormat_date_time.js => format_date_time.ts} | 7 +- .../{format_time.js => format_time.ts} | 7 +- src/common/datetime/relative_time.js | 33 ---- src/common/datetime/relative_time.ts | 40 +++++ ..._to_duration.js => seconds_to_duration.ts} | 4 +- ..._image_base64.js => empty_image_base64.ts} | 0 src/common/entity/attribute_class_names.js | 9 -- src/common/entity/attribute_class_names.ts | 17 ++ ...y_sensor_icon.js => binary_sensor_icon.ts} | 6 +- ..._toggle_domain.js => can_toggle_domain.ts} | 4 +- ...an_toggle_state.js => can_toggle_state.ts} | 10 +- src/common/entity/compute_domain.js | 3 - src/common/entity/compute_domain.ts | 3 + ...pute_object_id.js => compute_object_id.ts} | 2 +- src/common/entity/compute_state_display.js | 84 ---------- src/common/entity/compute_state_display.ts | 91 +++++++++++ src/common/entity/compute_state_domain.js | 5 - src/common/entity/compute_state_domain.ts | 6 + src/common/entity/compute_state_name.js | 11 -- src/common/entity/compute_state_name.ts | 18 +++ .../entity/{cover_icon.js => cover_icon.ts} | 5 +- .../entity/{domain_icon.js => domain_icon.ts} | 5 +- .../{extract_views.js => extract_views.ts} | 5 +- src/common/entity/feature_class_names.js | 11 -- src/common/entity/feature_class_names.ts | 22 +++ ...roup_entities.js => get_group_entities.ts} | 8 +- ..._view_entities.js => get_view_entities.ts} | 9 +- src/common/entity/has_location.js | 5 - src/common/entity/has_location.ts | 7 + ...eteime_icon.js => input_dateteime_icon.ts} | 3 +- .../entity/{sensor_icon.js => sensor_icon.ts} | 10 +- ...{split_by_groups.js => split_by_groups.ts} | 7 +- ...{state_card_type.js => state_card_type.ts} | 7 +- .../entity/{state_icon.js => state_icon.ts} | 3 +- ...e_info_type.js => state_more_info_type.ts} | 3 +- ...sort_by_name.js => states_sort_by_name.ts} | 6 +- ...e_remaining.js => timer_time_remaining.ts} | 7 +- src/common/entity/valid_entity_id.js | 3 - src/common/entity/valid_entity_id.ts | 2 + ...-aspect-ratio.js => parse-aspect-ratio.ts} | 8 +- src/panels/lovelace/cards/hui-glance-card.ts | 6 +- .../elements/hui-state-label-element.ts | 4 +- src/types.ts | 12 ++ ...ds_test.js => duration_to_seconds_test.ts} | 0 .../{format_date.js => format_date.ts} | 0 ...ormat_date_time.js => format_date_time.ts} | 0 .../{format_time.js => format_time.ts} | 0 ...on_test.js => seconds_to_duration_test.ts} | 0 ..._test.js => attribute_class_names_test.ts} | 8 +- ...main_test.js => can_toggle_domain_test.ts} | 2 +- ...state_test.js => can_toggle_state_test.ts} | 15 +- .../{compute_domain.js => compute_domain.ts} | 0 ...te_display.js => compute_state_display.ts} | 74 +++++---- ...tate_domain.js => compute_state_domain.ts} | 2 +- ...ct_views.spec.js => extract_views.spec.ts} | 2 +- ...es_test.js => feature_class_names_test.ts} | 12 +- ...ies.spec.js => get_group_entities.spec.ts} | 2 +- ...ties.spec.js => get_view_entities.spec.ts} | 2 +- ..._location.test.js => has_location.test.ts} | 8 +- ...groups.spec.js => split_by_groups.spec.ts} | 2 +- ...d_type_test.js => state_card_type_test.ts} | 12 +- ...e_test.js => state_more_info_type_test.ts} | 6 +- .../entity/{test_util.js => test_util.ts} | 2 +- ...g_test.js => timer_time_remaining_test.ts} | 8 +- ...tio_test.js => parse_aspect_ratio_test.ts} | 2 +- test-mocha/mocha.opts | 3 +- test-mocha/tsconfig.test.json | 6 + test-mocha/tslint.json | 6 + yarn.lock | 150 +++++++++++------- 78 files changed, 524 insertions(+), 364 deletions(-) delete mode 100644 src/common/config/is_component_loaded.js create mode 100644 src/common/config/is_component_loaded.ts rename src/common/config/{is_pwa.js => is_pwa.ts} (75%) delete mode 100644 src/common/config/location_name.js create mode 100644 src/common/config/location_name.ts rename src/common/{const.js => const.ts} (100%) rename src/common/datetime/{duration_to_seconds.js => duration_to_seconds.ts} (59%) rename src/common/datetime/{format_date.js => format_date.ts} (70%) rename src/common/datetime/{format_date_time.js => format_date_time.ts} (72%) rename src/common/datetime/{format_time.js => format_time.ts} (70%) delete mode 100644 src/common/datetime/relative_time.js create mode 100644 src/common/datetime/relative_time.ts rename src/common/datetime/{seconds_to_duration.js => seconds_to_duration.ts} (71%) rename src/common/{empty_image_base64.js => empty_image_base64.ts} (100%) delete mode 100644 src/common/entity/attribute_class_names.js create mode 100644 src/common/entity/attribute_class_names.ts rename src/common/entity/{binary_sensor_icon.js => binary_sensor_icon.ts} (90%) rename src/common/entity/{can_toggle_domain.js => can_toggle_domain.ts} (66%) rename src/common/entity/{can_toggle_state.js => can_toggle_state.ts} (52%) delete mode 100644 src/common/entity/compute_domain.js create mode 100644 src/common/entity/compute_domain.ts rename src/common/entity/{compute_object_id.js => compute_object_id.ts} (58%) delete mode 100644 src/common/entity/compute_state_display.js create mode 100644 src/common/entity/compute_state_display.ts delete mode 100644 src/common/entity/compute_state_domain.js create mode 100644 src/common/entity/compute_state_domain.ts delete mode 100644 src/common/entity/compute_state_name.js create mode 100644 src/common/entity/compute_state_name.ts rename src/common/entity/{cover_icon.js => cover_icon.ts} (62%) rename src/common/entity/{domain_icon.js => domain_icon.ts} (95%) rename src/common/entity/{extract_views.js => extract_views.ts} (74%) delete mode 100644 src/common/entity/feature_class_names.js create mode 100644 src/common/entity/feature_class_names.ts rename src/common/entity/{get_group_entities.js => get_group_entities.ts} (51%) rename src/common/entity/{get_view_entities.js => get_view_entities.ts} (73%) delete mode 100644 src/common/entity/has_location.js create mode 100644 src/common/entity/has_location.ts rename src/common/entity/{input_dateteime_icon.js => input_dateteime_icon.ts} (68%) rename src/common/entity/{sensor_icon.js => sensor_icon.ts} (80%) rename src/common/entity/{split_by_groups.js => split_by_groups.ts} (72%) rename src/common/entity/{state_card_type.js => state_card_type.ts} (71%) rename src/common/entity/{state_icon.js => state_icon.ts} (87%) rename src/common/entity/{state_more_info_type.js => state_more_info_type.ts} (73%) rename src/common/entity/{states_sort_by_name.js => states_sort_by_name.ts} (71%) rename src/common/entity/{timer_time_remaining.js => timer_time_remaining.ts} (55%) delete mode 100644 src/common/entity/valid_entity_id.js create mode 100644 src/common/entity/valid_entity_id.ts rename src/common/util/{parse-aspect-ratio.js => parse-aspect-ratio.ts} (76%) rename test-mocha/common/datetime/{duration_to_seconds_test.js => duration_to_seconds_test.ts} (100%) rename test-mocha/common/datetime/{format_date.js => format_date.ts} (100%) rename test-mocha/common/datetime/{format_date_time.js => format_date_time.ts} (100%) rename test-mocha/common/datetime/{format_time.js => format_time.ts} (100%) rename test-mocha/common/datetime/{seconds_to_duration_test.js => seconds_to_duration_test.ts} (100%) rename test-mocha/common/entity/{attribute_class_names_test.js => attribute_class_names_test.ts} (89%) rename test-mocha/common/entity/{can_toggle_domain_test.js => can_toggle_domain_test.ts} (97%) rename test-mocha/common/entity/{can_toggle_state_test.js => can_toggle_state_test.ts} (83%) rename test-mocha/common/entity/{compute_domain.js => compute_domain.ts} (100%) rename test-mocha/common/entity/{compute_state_display.js => compute_state_display.ts} (82%) rename test-mocha/common/entity/{compute_state_domain.js => compute_state_domain.ts} (91%) rename test-mocha/common/entity/{extract_views.spec.js => extract_views.spec.ts} (95%) rename test-mocha/common/entity/{feature_class_names_test.js => feature_class_names_test.ts} (74%) rename test-mocha/common/entity/{get_group_entities.spec.js => get_group_entities.spec.ts} (96%) rename test-mocha/common/entity/{get_view_entities.spec.js => get_view_entities.spec.ts} (98%) rename test-mocha/common/entity/{has_location.test.js => has_location.test.ts} (87%) rename test-mocha/common/entity/{split_by_groups.spec.js => split_by_groups.spec.ts} (96%) rename test-mocha/common/entity/{state_card_type_test.js => state_card_type_test.ts} (88%) rename test-mocha/common/entity/{state_more_info_type_test.js => state_more_info_type_test.ts} (89%) rename test-mocha/common/entity/{test_util.js => test_util.ts} (97%) rename test-mocha/common/entity/{timer_time_remaining_test.js => timer_time_remaining_test.ts} (92%) rename test-mocha/common/util/{parse_aspect_ratio_test.js => parse_aspect_ratio_test.ts} (97%) create mode 100644 test-mocha/tsconfig.test.json create mode 100644 test-mocha/tslint.json diff --git a/package.json b/package.json index 1e6bc6abf7..82f4d700b1 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "version": "1.0.0", "scripts": { "build": "script/build_frontend", - "lint": "eslint src hassio/src gallery/src test-mocha && tslint -c tslint.json 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc", - "mocha": "node_modules/.bin/mocha --opts test-mocha/mocha.opts", + "lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc", + "mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts", "test": "npm run lint && npm run mocha", "docker_build": "sh ./script/docker_run.sh build $npm_package_version", "bash": "sh ./script/docker_run.sh bash $npm_package_version" @@ -100,10 +100,12 @@ "@babel/preset-env": "^7.1.0", "@babel/preset-typescript": "^7.1.0", "@gfx/zopfli": "^1.0.9", + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.5", "babel-eslint": "^10", "babel-loader": "^8.0.4", "babel-minify-webpack-plugin": "^0.3.1", - "chai": "^4.1.2", + "chai": "^4.2.0", "compression-webpack-plugin": "^2.0.0", "copy-webpack-plugin": "^4.5.2", "del": "^3.0.0", @@ -126,7 +128,6 @@ "husky": "^1.1.0", "lint-staged": "^8.0.2", "merge-stream": "^1.0.1", - "mocha": "^5.2.0", "parse5": "^5.1.0", "polymer-analyzer": "^3.1.2", "polymer-bundler": "^4.0.2", @@ -135,7 +136,8 @@ "raw-loader": "^0.5.1", "reify": "^0.18.1", "require-dir": "^1.0.0", - "sinon": "^7.1.0", + "sinon": "^7.1.1", + "ts-mocha": "^2.0.0", "tslint": "^5.11.0", "tslint-config-prettier": "^1.15.0", "tslint-eslint-rules": "^5.4.0", diff --git a/src/common/config/is_component_loaded.js b/src/common/config/is_component_loaded.js deleted file mode 100644 index 318eb56936..0000000000 --- a/src/common/config/is_component_loaded.js +++ /dev/null @@ -1,4 +0,0 @@ -/** Return if a component is loaded. */ -export default function isComponentLoaded(hass, component) { - return hass && hass.config.components.indexOf(component) !== -1; -} diff --git a/src/common/config/is_component_loaded.ts b/src/common/config/is_component_loaded.ts new file mode 100644 index 0000000000..c8535cda6b --- /dev/null +++ b/src/common/config/is_component_loaded.ts @@ -0,0 +1,9 @@ +import { HomeAssistant } from "../../types"; + +/** Return if a component is loaded. */ +export default function isComponentLoaded( + hass: HomeAssistant, + component: string +): boolean { + return hass && hass.config.components.indexOf(component) !== -1; +} diff --git a/src/common/config/is_pwa.js b/src/common/config/is_pwa.ts similarity index 75% rename from src/common/config/is_pwa.js rename to src/common/config/is_pwa.ts index c2a4bcbde6..a7fa4f4376 100644 --- a/src/common/config/is_pwa.js +++ b/src/common/config/is_pwa.ts @@ -1,4 +1,4 @@ /** Return if the displaymode is in standalone mode (PWA). */ -export default function isPwa() { +export default function isPwa(): boolean { return window.matchMedia("(display-mode: standalone)").matches; } diff --git a/src/common/config/location_name.js b/src/common/config/location_name.js deleted file mode 100644 index 83d8fc32a4..0000000000 --- a/src/common/config/location_name.js +++ /dev/null @@ -1,4 +0,0 @@ -/** Get the location name from a hass object. */ -export default function computeLocationName(hass) { - return hass && hass.config.location_name; -} diff --git a/src/common/config/location_name.ts b/src/common/config/location_name.ts new file mode 100644 index 0000000000..d198951b24 --- /dev/null +++ b/src/common/config/location_name.ts @@ -0,0 +1,6 @@ +import { HomeAssistant } from "../../types"; + +/** Get the location name from a hass object. */ +export default function computeLocationName(hass: HomeAssistant): string { + return hass && hass.config.location_name; +} diff --git a/src/common/const.js b/src/common/const.ts similarity index 100% rename from src/common/const.js rename to src/common/const.ts diff --git a/src/common/datetime/duration_to_seconds.js b/src/common/datetime/duration_to_seconds.ts similarity index 59% rename from src/common/datetime/duration_to_seconds.js rename to src/common/datetime/duration_to_seconds.ts index bdd4a006a9..f579c57b88 100644 --- a/src/common/datetime/duration_to_seconds.js +++ b/src/common/datetime/duration_to_seconds.ts @@ -1,4 +1,4 @@ -export default function durationToSeconds(duration) { +export default function durationToSeconds(duration: string): number { const parts = duration.split(":").map(Number); return parts[0] * 3600 + parts[1] * 60 + parts[2]; } diff --git a/src/common/datetime/format_date.js b/src/common/datetime/format_date.ts similarity index 70% rename from src/common/datetime/format_date.js rename to src/common/datetime/format_date.ts index fac7fb1ce9..09cd0223e2 100644 --- a/src/common/datetime/format_date.js +++ b/src/common/datetime/format_date.ts @@ -1,4 +1,4 @@ -import fecha from "fecha"; +import * as fecha from "fecha"; // Check for support of native locale string options function toLocaleDateStringSupportsOptions() { @@ -11,11 +11,10 @@ function toLocaleDateStringSupportsOptions() { } export default (toLocaleDateStringSupportsOptions() - ? (dateObj, locales) => + ? (dateObj: Date, locales: string) => dateObj.toLocaleDateString(locales, { year: "numeric", month: "long", day: "numeric", }) - : // eslint-disable-next-line no-unused-vars - (dateObj, locales) => fecha.format(dateObj, "mediumDate")); + : (dateObj: Date) => fecha.format(dateObj, "mediumDate")); diff --git a/src/common/datetime/format_date_time.js b/src/common/datetime/format_date_time.ts similarity index 72% rename from src/common/datetime/format_date_time.js rename to src/common/datetime/format_date_time.ts index bc8d54cd47..3421a5d291 100644 --- a/src/common/datetime/format_date_time.js +++ b/src/common/datetime/format_date_time.ts @@ -1,4 +1,4 @@ -import fecha from "fecha"; +import * as fecha from "fecha"; // Check for support of native locale string options function toLocaleStringSupportsOptions() { @@ -11,7 +11,7 @@ function toLocaleStringSupportsOptions() { } export default (toLocaleStringSupportsOptions() - ? (dateObj, locales) => + ? (dateObj: Date, locales: string) => dateObj.toLocaleString(locales, { year: "numeric", month: "long", @@ -19,5 +19,4 @@ export default (toLocaleStringSupportsOptions() hour: "numeric", minute: "2-digit", }) - : // eslint-disable-next-line no-unused-vars - (dateObj, locales) => fecha.format(dateObj, "haDateTime")); + : (dateObj: Date) => fecha.format(dateObj, "haDateTime")); diff --git a/src/common/datetime/format_time.js b/src/common/datetime/format_time.ts similarity index 70% rename from src/common/datetime/format_time.js rename to src/common/datetime/format_time.ts index dcf7d32d7d..e5abe800b6 100644 --- a/src/common/datetime/format_time.js +++ b/src/common/datetime/format_time.ts @@ -1,4 +1,4 @@ -import fecha from "fecha"; +import * as fecha from "fecha"; // Check for support of native locale string options function toLocaleTimeStringSupportsOptions() { @@ -11,10 +11,9 @@ function toLocaleTimeStringSupportsOptions() { } export default (toLocaleTimeStringSupportsOptions() - ? (dateObj, locales) => + ? (dateObj: Date, locales: string) => dateObj.toLocaleTimeString(locales, { hour: "numeric", minute: "2-digit", }) - : // eslint-disable-next-line no-unused-vars - (dateObj, locales) => fecha.format(dateObj, "shortTime")); + : (dateObj: Date) => fecha.format(dateObj, "shortTime")); diff --git a/src/common/datetime/relative_time.js b/src/common/datetime/relative_time.js deleted file mode 100644 index c76d071c22..0000000000 --- a/src/common/datetime/relative_time.js +++ /dev/null @@ -1,33 +0,0 @@ -/** Calculate a string representing a date object as relative time from now. - * - * Example output: 5 minutes ago, in 3 days. - */ -const tests = [60, "second", 60, "minute", 24, "hour", 7, "day"]; - -export default function relativeTime(dateObj, localize) { - let delta = (new Date() - dateObj) / 1000; - const tense = delta >= 0 ? "past" : "future"; - delta = Math.abs(delta); - - for (let i = 0; i < tests.length; i += 2) { - if (delta < tests[i]) { - delta = Math.floor(delta); - const time = localize( - `ui.components.relative_time.duration.${tests[i + 1]}`, - "count", - delta - ); - return localize(`ui.components.relative_time.${tense}`, "time", time); - } - - delta /= tests[i]; - } - - delta = Math.floor(delta); - const time = localize( - "ui.components.relative_time.duration.week", - "count", - delta - ); - return localize(`ui.components.relative_time.${tense}`, "time", time); -} diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts new file mode 100644 index 0000000000..7daabdc657 --- /dev/null +++ b/src/common/datetime/relative_time.ts @@ -0,0 +1,40 @@ +import { LocalizeFunc } from "../../mixins/localize-base-mixin"; + +/** + * Calculate a string representing a date object as relative time from now. + * + * Example output: 5 minutes ago, in 3 days. + */ +const tests = [60, 60, 24, 7]; +const langKey = ["second", "minute", "hour", "day"]; + +export default function relativeTime( + dateObj: Date, + localize: LocalizeFunc +): string { + let delta = (new Date().getTime() - dateObj.getTime()) / 1000; + const tense = delta >= 0 ? "past" : "future"; + delta = Math.abs(delta); + + for (let i = 0; i < tests.length; i++) { + if (delta < tests[i]) { + delta = Math.floor(delta); + const timeDesc = localize( + `ui.components.relative_time.duration.${langKey[i]}`, + "count", + delta + ); + return localize(`ui.components.relative_time.${tense}`, "time", timeDesc); + } + + delta /= tests[i]; + } + + delta = Math.floor(delta); + const time = localize( + "ui.components.relative_time.duration.week", + "count", + delta + ); + return localize(`ui.components.relative_time.${tense}`, "time", time); +} diff --git a/src/common/datetime/seconds_to_duration.js b/src/common/datetime/seconds_to_duration.ts similarity index 71% rename from src/common/datetime/seconds_to_duration.js rename to src/common/datetime/seconds_to_duration.ts index a256c14632..4874137803 100644 --- a/src/common/datetime/seconds_to_duration.js +++ b/src/common/datetime/seconds_to_duration.ts @@ -1,6 +1,6 @@ -const leftPad = (number) => (number < 10 ? `0${number}` : number); +const leftPad = (num: number) => (num < 10 ? `0${num}` : num); -export default function secondsToDuration(d) { +export default function secondsToDuration(d: number) { const h = Math.floor(d / 3600); const m = Math.floor((d % 3600) / 60); const s = Math.floor((d % 3600) % 60); diff --git a/src/common/empty_image_base64.js b/src/common/empty_image_base64.ts similarity index 100% rename from src/common/empty_image_base64.js rename to src/common/empty_image_base64.ts diff --git a/src/common/entity/attribute_class_names.js b/src/common/entity/attribute_class_names.js deleted file mode 100644 index cde81825ac..0000000000 --- a/src/common/entity/attribute_class_names.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function attributeClassNames(stateObj, attributes) { - if (!stateObj) return ""; - return attributes - .map(function(attribute) { - return attribute in stateObj.attributes ? "has-" + attribute : ""; - }) - .filter((attr) => attr !== "") - .join(" "); -} diff --git a/src/common/entity/attribute_class_names.ts b/src/common/entity/attribute_class_names.ts new file mode 100644 index 0000000000..28d06f53fa --- /dev/null +++ b/src/common/entity/attribute_class_names.ts @@ -0,0 +1,17 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export default function attributeClassNames( + stateObj: HassEntity, + attributes: string[] +): string { + if (!stateObj) { + return ""; + } + return attributes + .map( + (attribute) => + attribute in stateObj.attributes ? "has-" + attribute : "" + ) + .filter((attr) => attr !== "") + .join(" "); +} diff --git a/src/common/entity/binary_sensor_icon.js b/src/common/entity/binary_sensor_icon.ts similarity index 90% rename from src/common/entity/binary_sensor_icon.js rename to src/common/entity/binary_sensor_icon.ts index e617f10f6d..365aba566b 100644 --- a/src/common/entity/binary_sensor_icon.js +++ b/src/common/entity/binary_sensor_icon.ts @@ -1,7 +1,9 @@ +import { HassEntity } from "home-assistant-js-websocket"; + /** Return an icon representing a binary sensor state. */ -export default function binarySensorIcon(state) { - var activated = state.state && state.state === "off"; +export default function binarySensorIcon(state: HassEntity) { + const activated = state.state && state.state === "off"; switch (state.attributes.device_class) { case "battery": return activated ? "hass:battery" : "hass:battery-outline"; diff --git a/src/common/entity/can_toggle_domain.js b/src/common/entity/can_toggle_domain.ts similarity index 66% rename from src/common/entity/can_toggle_domain.js rename to src/common/entity/can_toggle_domain.ts index 6b284e5f17..f1a0bc47b3 100644 --- a/src/common/entity/can_toggle_domain.js +++ b/src/common/entity/can_toggle_domain.ts @@ -1,4 +1,6 @@ -export default function canToggleDomain(hass, domain) { +import { HomeAssistant } from "../../types"; + +export default function canToggleDomain(hass: HomeAssistant, domain: string) { const services = hass.services[domain]; if (!services) { return false; diff --git a/src/common/entity/can_toggle_state.js b/src/common/entity/can_toggle_state.ts similarity index 52% rename from src/common/entity/can_toggle_state.js rename to src/common/entity/can_toggle_state.ts index 8b7a1eaee0..71a1b5d6c8 100644 --- a/src/common/entity/can_toggle_state.js +++ b/src/common/entity/can_toggle_state.ts @@ -1,13 +1,19 @@ +import { HassEntity } from "home-assistant-js-websocket"; import canToggleDomain from "./can_toggle_domain"; import computeStateDomain from "./compute_state_domain"; +import { HomeAssistant } from "../../types"; -export default function canToggleState(hass, stateObj) { +export default function canToggleState( + hass: HomeAssistant, + stateObj: HassEntity +) { const domain = computeStateDomain(stateObj); if (domain === "group") { return stateObj.state === "on" || stateObj.state === "off"; } if (domain === "climate") { - return !!((stateObj.attributes || {}).supported_features & 4096); + // tslint:disable-next-line + return (stateObj.attributes.supported_features! & 4096) !== 0; } return canToggleDomain(hass, domain); diff --git a/src/common/entity/compute_domain.js b/src/common/entity/compute_domain.js deleted file mode 100644 index b9d6fecaf7..0000000000 --- a/src/common/entity/compute_domain.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function computeDomain(entityId) { - return entityId.substr(0, entityId.indexOf(".")); -} diff --git a/src/common/entity/compute_domain.ts b/src/common/entity/compute_domain.ts new file mode 100644 index 0000000000..47eeb9e890 --- /dev/null +++ b/src/common/entity/compute_domain.ts @@ -0,0 +1,3 @@ +export default function computeDomain(entityId: string): string { + return entityId.substr(0, entityId.indexOf(".")); +} diff --git a/src/common/entity/compute_object_id.js b/src/common/entity/compute_object_id.ts similarity index 58% rename from src/common/entity/compute_object_id.js rename to src/common/entity/compute_object_id.ts index 85888b5c79..865ab6254d 100644 --- a/src/common/entity/compute_object_id.js +++ b/src/common/entity/compute_object_id.ts @@ -1,4 +1,4 @@ /** Compute the object ID of a state. */ -export default function computeObjectId(entityId) { +export default function computeObjectId(entityId: string): string { return entityId.substr(entityId.indexOf(".") + 1); } diff --git a/src/common/entity/compute_state_display.js b/src/common/entity/compute_state_display.js deleted file mode 100644 index 56f5d28c48..0000000000 --- a/src/common/entity/compute_state_display.js +++ /dev/null @@ -1,84 +0,0 @@ -import computeStateDomain from "./compute_state_domain"; -import formatDateTime from "../datetime/format_date_time"; -import formatDate from "../datetime/format_date"; -import formatTime from "../datetime/format_time"; - -export default function computeStateDisplay(localize, stateObj, language) { - if (!stateObj._stateDisplay) { - const domain = computeStateDomain(stateObj); - if (domain === "binary_sensor") { - // Try device class translation, then default binary sensor translation - if (stateObj.attributes.device_class) { - stateObj._stateDisplay = localize( - `state.${domain}.${stateObj.attributes.device_class}.${ - stateObj.state - }` - ); - } - if (!stateObj._stateDisplay) { - stateObj._stateDisplay = localize( - `state.${domain}.default.${stateObj.state}` - ); - } - } else if ( - stateObj.attributes.unit_of_measurement && - !["unknown", "unavailable"].includes(stateObj.state) - ) { - stateObj._stateDisplay = - stateObj.state + " " + stateObj.attributes.unit_of_measurement; - } else if (domain === "input_datetime") { - let date; - if (!stateObj.attributes.has_time) { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day - ); - stateObj._stateDisplay = formatDate(date, language); - } else if (!stateObj.attributes.has_date) { - const now = new Date(); - date = new Date( - // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 - // don't use artificial 1970 year. - now.getFullYear(), - now.getMonth(), - now.getDay(), - stateObj.attributes.hour, - stateObj.attributes.minute - ); - stateObj._stateDisplay = formatTime(date, language); - } else { - date = new Date( - stateObj.attributes.year, - stateObj.attributes.month - 1, - stateObj.attributes.day, - stateObj.attributes.hour, - stateObj.attributes.minute - ); - stateObj._stateDisplay = formatDateTime(date, language); - } - } else if (domain === "zwave") { - if (["initializing", "dead"].includes(stateObj.state)) { - stateObj._stateDisplay = localize( - `state.zwave.query_stage.${stateObj.state}`, - "query_stage", - stateObj.attributes.query_stage - ); - } else { - stateObj._stateDisplay = localize( - `state.zwave.default.${stateObj.state}` - ); - } - } else { - stateObj._stateDisplay = localize(`state.${domain}.${stateObj.state}`); - } - // Fall back to default, component backend translation, or raw state if nothing else matches. - stateObj._stateDisplay = - stateObj._stateDisplay || - localize(`state.default.${stateObj.state}`) || - localize(`component.${domain}.state.${stateObj.state}`) || - stateObj.state; - } - - return stateObj._stateDisplay; -} diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts new file mode 100644 index 0000000000..83fb1f5578 --- /dev/null +++ b/src/common/entity/compute_state_display.ts @@ -0,0 +1,91 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import computeStateDomain from "./compute_state_domain"; +import formatDateTime from "../datetime/format_date_time"; +import formatDate from "../datetime/format_date"; +import formatTime from "../datetime/format_time"; +import { LocalizeFunc } from "../../mixins/localize-base-mixin"; + +type CachedDisplayEntity = HassEntity & { + _stateDisplay?: string; +}; + +export default function computeStateDisplay( + localize: LocalizeFunc, + stateObj: HassEntity, + language: string +) { + const state = stateObj as CachedDisplayEntity; + if (!state._stateDisplay) { + const domain = computeStateDomain(state); + if (domain === "binary_sensor") { + // Try device class translation, then default binary sensor translation + if (state.attributes.device_class) { + state._stateDisplay = localize( + `state.${domain}.${state.attributes.device_class}.${state.state}` + ); + } + if (!state._stateDisplay) { + state._stateDisplay = localize( + `state.${domain}.default.${state.state}` + ); + } + } else if ( + state.attributes.unit_of_measurement && + !["unknown", "unavailable"].includes(state.state) + ) { + state._stateDisplay = + state.state + " " + state.attributes.unit_of_measurement; + } else if (domain === "input_datetime") { + let date; + if (!state.attributes.has_time) { + date = new Date( + state.attributes.year, + state.attributes.month - 1, + state.attributes.day + ); + state._stateDisplay = formatDate(date, language); + } else if (!state.attributes.has_date) { + const now = new Date(); + date = new Date( + // Due to bugs.chromium.org/p/chromium/issues/detail?id=797548 + // don't use artificial 1970 year. + now.getFullYear(), + now.getMonth(), + now.getDay(), + state.attributes.hour, + state.attributes.minute + ); + state._stateDisplay = formatTime(date, language); + } else { + date = new Date( + state.attributes.year, + state.attributes.month - 1, + state.attributes.day, + state.attributes.hour, + state.attributes.minute + ); + state._stateDisplay = formatDateTime(date, language); + } + } else if (domain === "zwave") { + if (["initializing", "dead"].includes(state.state)) { + state._stateDisplay = localize( + `state.zwave.query_stage.${state.state}`, + "query_stage", + state.attributes.query_stage + ); + } else { + state._stateDisplay = localize(`state.zwave.default.${state.state}`); + } + } else { + state._stateDisplay = localize(`state.${domain}.${state.state}`); + } + // Fall back to default, component backend translation, or raw state if nothing else matches. + state._stateDisplay = + state._stateDisplay || + localize(`state.default.${state.state}`) || + localize(`component.${domain}.state.${state.state}`) || + state.state; + } + + return state._stateDisplay; +} diff --git a/src/common/entity/compute_state_domain.js b/src/common/entity/compute_state_domain.js deleted file mode 100644 index 492b700eaa..0000000000 --- a/src/common/entity/compute_state_domain.js +++ /dev/null @@ -1,5 +0,0 @@ -import computeDomain from "./compute_domain"; - -export default function computeStateDomain(stateObj) { - return computeDomain(stateObj.entity_id); -} diff --git a/src/common/entity/compute_state_domain.ts b/src/common/entity/compute_state_domain.ts new file mode 100644 index 0000000000..1ef02f4e8f --- /dev/null +++ b/src/common/entity/compute_state_domain.ts @@ -0,0 +1,6 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import computeDomain from "./compute_domain"; + +export default function computeStateDomain(stateObj: HassEntity) { + return computeDomain(stateObj.entity_id); +} diff --git a/src/common/entity/compute_state_name.js b/src/common/entity/compute_state_name.js deleted file mode 100644 index 0aa20d7ccf..0000000000 --- a/src/common/entity/compute_state_name.js +++ /dev/null @@ -1,11 +0,0 @@ -import computeObjectId from "./compute_object_id"; - -export default function computeStateName(stateObj) { - if (stateObj._entityDisplay === undefined) { - stateObj._entityDisplay = - stateObj.attributes.friendly_name || - computeObjectId(stateObj.entity_id).replace(/_/g, " "); - } - - return stateObj._entityDisplay; -} diff --git a/src/common/entity/compute_state_name.ts b/src/common/entity/compute_state_name.ts new file mode 100644 index 0000000000..c20bde78b7 --- /dev/null +++ b/src/common/entity/compute_state_name.ts @@ -0,0 +1,18 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import computeObjectId from "./compute_object_id"; + +type CachedDisplayEntity = HassEntity & { + _entityDisplay?: string; +}; + +export default function computeStateName(stateObj: HassEntity) { + const state = stateObj as CachedDisplayEntity; + + if (state._entityDisplay === undefined) { + state._entityDisplay = + state.attributes.friendly_name || + computeObjectId(state.entity_id).replace(/_/g, " "); + } + + return state._entityDisplay; +} diff --git a/src/common/entity/cover_icon.js b/src/common/entity/cover_icon.ts similarity index 62% rename from src/common/entity/cover_icon.js rename to src/common/entity/cover_icon.ts index 78876025b4..88fe03f774 100644 --- a/src/common/entity/cover_icon.js +++ b/src/common/entity/cover_icon.ts @@ -1,8 +1,9 @@ /** Return an icon representing a cover state. */ +import { HassEntity } from "home-assistant-js-websocket"; import domainIcon from "./domain_icon"; -export default function coverIcon(state) { - var open = state.state && state.state !== "closed"; +export default function coverIcon(state: HassEntity): string { + const open = state.state !== "closed"; switch (state.attributes.device_class) { case "garage": return open ? "hass:garage-open" : "hass:garage"; diff --git a/src/common/entity/domain_icon.js b/src/common/entity/domain_icon.ts similarity index 95% rename from src/common/entity/domain_icon.js rename to src/common/entity/domain_icon.ts index a50fcb943e..5c6ac2a181 100644 --- a/src/common/entity/domain_icon.js +++ b/src/common/entity/domain_icon.ts @@ -44,7 +44,7 @@ const fixedIcons = { weblink: "hass:open-in-new", }; -export default function domainIcon(domain, state) { +export default function domainIcon(domain: string, state?: string): string { if (domain in fixedIcons) { return fixedIcons[domain]; } @@ -93,11 +93,10 @@ export default function domainIcon(domain, state) { } default: - /* eslint-disable no-console */ + // tslint:disable-next-line console.warn( "Unable to find icon for domain " + domain + " (" + state + ")" ); - /* eslint-enable no-console */ return DEFAULT_DOMAIN_ICON; } } diff --git a/src/common/entity/extract_views.js b/src/common/entity/extract_views.ts similarity index 74% rename from src/common/entity/extract_views.js rename to src/common/entity/extract_views.ts index c3b7949911..bcc7084646 100644 --- a/src/common/entity/extract_views.js +++ b/src/common/entity/extract_views.ts @@ -1,8 +1,9 @@ +import { HassEntities, HassEntity } from "home-assistant-js-websocket"; import { DEFAULT_VIEW_ENTITY_ID } from "../const"; // Return an ordered array of available views -export default function extractViews(entities) { - const views = []; +export default function extractViews(entities: HassEntities): HassEntity[] { + const views: HassEntity[] = []; Object.keys(entities).forEach((entityId) => { const entity = entities[entityId]; diff --git a/src/common/entity/feature_class_names.js b/src/common/entity/feature_class_names.js deleted file mode 100644 index 93766e549b..0000000000 --- a/src/common/entity/feature_class_names.js +++ /dev/null @@ -1,11 +0,0 @@ -// Expects classNames to be an object mapping feature-bit -> className -export default function featureClassNames(stateObj, classNames) { - if (!stateObj || !stateObj.attributes.supported_features) return ""; - - const features = stateObj.attributes.supported_features; - - return Object.keys(classNames) - .map((feature) => ((features & feature) !== 0 ? classNames[feature] : "")) - .filter((attr) => attr !== "") - .join(" "); -} diff --git a/src/common/entity/feature_class_names.ts b/src/common/entity/feature_class_names.ts new file mode 100644 index 0000000000..4cafc68b86 --- /dev/null +++ b/src/common/entity/feature_class_names.ts @@ -0,0 +1,22 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +// Expects classNames to be an object mapping feature-bit -> className +export default function featureClassNames( + stateObj: HassEntity, + classNames: { [feature: number]: string } +) { + if (!stateObj || !stateObj.attributes.supported_features) { + return ""; + } + + const features = stateObj.attributes.supported_features; + + return Object.keys(classNames) + .map( + (feature) => + // tslint:disable-next-line + (features & Number(feature)) !== 0 ? classNames[feature] : "" + ) + .filter((attr) => attr !== "") + .join(" "); +} diff --git a/src/common/entity/get_group_entities.js b/src/common/entity/get_group_entities.ts similarity index 51% rename from src/common/entity/get_group_entities.js rename to src/common/entity/get_group_entities.ts index 5e034a9df8..b77353e0c1 100644 --- a/src/common/entity/get_group_entities.js +++ b/src/common/entity/get_group_entities.ts @@ -1,4 +1,10 @@ -export default function getGroupEntities(entities, group) { +import { HassEntities } from "home-assistant-js-websocket"; +import { GroupEntity } from "../../types"; + +export default function getGroupEntities( + entities: HassEntities, + group: GroupEntity +) { const result = {}; group.attributes.entity_id.forEach((entityId) => { diff --git a/src/common/entity/get_view_entities.js b/src/common/entity/get_view_entities.ts similarity index 73% rename from src/common/entity/get_view_entities.js rename to src/common/entity/get_view_entities.ts index 00b21ee30e..62aa7e5d89 100644 --- a/src/common/entity/get_view_entities.js +++ b/src/common/entity/get_view_entities.ts @@ -1,9 +1,14 @@ +import { HassEntities } from "home-assistant-js-websocket"; import computeDomain from "./compute_domain"; import getGroupEntities from "./get_group_entities"; +import { GroupEntity } from "../../types"; // Return an object containing all entities that the view will show // including embedded groups. -export default function getViewEntities(entities, view) { +export default function getViewEntities( + entities: HassEntities, + view: GroupEntity +) { const viewEntities = {}; view.attributes.entity_id.forEach((entityId) => { @@ -13,7 +18,7 @@ export default function getViewEntities(entities, view) { viewEntities[entity.entity_id] = entity; if (computeDomain(entity.entity_id) === "group") { - const groupEntities = getGroupEntities(entities, entity); + const groupEntities = getGroupEntities(entities, entity as GroupEntity); Object.keys(groupEntities).forEach((grEntityId) => { const grEntity = groupEntities[grEntityId]; diff --git a/src/common/entity/has_location.js b/src/common/entity/has_location.js deleted file mode 100644 index 5cbcd3a801..0000000000 --- a/src/common/entity/has_location.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function hasLocation(stateObj) { - return ( - "latitude" in stateObj.attributes && "longitude" in stateObj.attributes - ); -} diff --git a/src/common/entity/has_location.ts b/src/common/entity/has_location.ts new file mode 100644 index 0000000000..5c290887bf --- /dev/null +++ b/src/common/entity/has_location.ts @@ -0,0 +1,7 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export default function hasLocation(stateObj: HassEntity) { + return ( + "latitude" in stateObj.attributes && "longitude" in stateObj.attributes + ); +} diff --git a/src/common/entity/input_dateteime_icon.js b/src/common/entity/input_dateteime_icon.ts similarity index 68% rename from src/common/entity/input_dateteime_icon.js rename to src/common/entity/input_dateteime_icon.ts index 8418226e90..016b19cc48 100644 --- a/src/common/entity/input_dateteime_icon.js +++ b/src/common/entity/input_dateteime_icon.ts @@ -1,7 +1,8 @@ /** Return an icon representing an input datetime state. */ import domainIcon from "./domain_icon"; +import { HassEntity } from "home-assistant-js-websocket"; -export default function inputDateTimeIcon(state) { +export default function inputDateTimeIcon(state: HassEntity): string { if (!state.attributes.has_date) { return "hass:clock"; } diff --git a/src/common/entity/sensor_icon.js b/src/common/entity/sensor_icon.ts similarity index 80% rename from src/common/entity/sensor_icon.js rename to src/common/entity/sensor_icon.ts index 44e6f3ce09..d08f7c6e97 100644 --- a/src/common/entity/sensor_icon.js +++ b/src/common/entity/sensor_icon.ts @@ -1,4 +1,5 @@ /** Return an icon representing a sensor state. */ +import { HassEntity } from "home-assistant-js-websocket"; import { UNIT_C, UNIT_F } from "../const"; import domainIcon from "./domain_icon"; @@ -9,17 +10,18 @@ const fixedDeviceClassIcons = { pressure: "hass:gauge", }; -export default function sensorIcon(state) { +export default function sensorIcon(state: HassEntity) { const dclass = state.attributes.device_class; - if (dclass in fixedDeviceClassIcons) { + if (dclass && dclass in fixedDeviceClassIcons) { return fixedDeviceClassIcons[dclass]; } if (dclass === "battery") { - if (isNaN(state.state)) { + const battery = Number(state.state); + if (isNaN(battery)) { return "hass:battery-unknown"; } - const batteryRound = Math.round(state.state / 10) * 10; + const batteryRound = Math.round(battery / 10) * 10; if (batteryRound >= 100) { return "hass:battery"; } diff --git a/src/common/entity/split_by_groups.js b/src/common/entity/split_by_groups.ts similarity index 72% rename from src/common/entity/split_by_groups.js rename to src/common/entity/split_by_groups.ts index e0631dfdda..a894472d8c 100644 --- a/src/common/entity/split_by_groups.js +++ b/src/common/entity/split_by_groups.ts @@ -1,11 +1,12 @@ import computeDomain from "./compute_domain"; +import { HassEntity, HassEntities } from "home-assistant-js-websocket"; // Split a collection into a list of groups and a 'rest' list of ungrouped // entities. // Returns { groups: [], ungrouped: {} } -export default function splitByGroups(entities) { - const groups = []; - const ungrouped = {}; +export default function splitByGroups(entities: HassEntities) { + const groups: HassEntity[] = []; + const ungrouped: HassEntities = {}; Object.keys(entities).forEach((entityId) => { const entity = entities[entityId]; diff --git a/src/common/entity/state_card_type.js b/src/common/entity/state_card_type.ts similarity index 71% rename from src/common/entity/state_card_type.js rename to src/common/entity/state_card_type.ts index 170ca9cb8b..00f6b73b6a 100644 --- a/src/common/entity/state_card_type.js +++ b/src/common/entity/state_card_type.ts @@ -1,8 +1,13 @@ +import { HassEntity } from "home-assistant-js-websocket"; import canToggleState from "./can_toggle_state"; import computeStateDomain from "./compute_state_domain"; import { DOMAINS_WITH_CARD } from "../const"; +import { HomeAssistant } from "../../types"; -export default function stateCardType(hass, stateObj) { +export default function stateCardType( + hass: HomeAssistant, + stateObj: HassEntity +) { if (stateObj.state === "unavailable") { return "display"; } diff --git a/src/common/entity/state_icon.js b/src/common/entity/state_icon.ts similarity index 87% rename from src/common/entity/state_icon.js rename to src/common/entity/state_icon.ts index 928d7bc186..351db86d12 100644 --- a/src/common/entity/state_icon.js +++ b/src/common/entity/state_icon.ts @@ -1,4 +1,5 @@ /** Return an icon representing a state. */ +import { HassEntity } from "home-assistant-js-websocket"; import { DEFAULT_DOMAIN_ICON } from "../const"; import computeDomain from "./compute_domain"; @@ -16,7 +17,7 @@ const domainIcons = { input_datetime: inputDateTimeIcon, }; -export default function stateIcon(state) { +export default function stateIcon(state: HassEntity) { if (!state) { return DEFAULT_DOMAIN_ICON; } diff --git a/src/common/entity/state_more_info_type.js b/src/common/entity/state_more_info_type.ts similarity index 73% rename from src/common/entity/state_more_info_type.js rename to src/common/entity/state_more_info_type.ts index a35ad61f35..dbdac2e198 100644 --- a/src/common/entity/state_more_info_type.js +++ b/src/common/entity/state_more_info_type.ts @@ -1,7 +1,8 @@ +import { HassEntity } from "home-assistant-js-websocket"; import computeStateDomain from "./compute_state_domain"; import { DOMAINS_HIDE_MORE_INFO, DOMAINS_WITH_MORE_INFO } from "../const"; -export default function stateMoreInfoType(stateObj) { +export default function stateMoreInfoType(stateObj: HassEntity) { const domain = computeStateDomain(stateObj); if (DOMAINS_WITH_MORE_INFO.includes(domain)) { diff --git a/src/common/entity/states_sort_by_name.js b/src/common/entity/states_sort_by_name.ts similarity index 71% rename from src/common/entity/states_sort_by_name.js rename to src/common/entity/states_sort_by_name.ts index f4b67fe2b4..e7afcd0559 100644 --- a/src/common/entity/states_sort_by_name.js +++ b/src/common/entity/states_sort_by_name.ts @@ -5,9 +5,13 @@ * const states = [state1, state2] * states.sort(statesSortByName); */ +import { HassEntity } from "home-assistant-js-websocket"; import computeStateName from "./compute_state_name"; -export default function sortStatesByName(entityA, entityB) { +export default function sortStatesByName( + entityA: HassEntity, + entityB: HassEntity +) { const nameA = computeStateName(entityA); const nameB = computeStateName(entityB); if (nameA < nameB) { diff --git a/src/common/entity/timer_time_remaining.js b/src/common/entity/timer_time_remaining.ts similarity index 55% rename from src/common/entity/timer_time_remaining.js rename to src/common/entity/timer_time_remaining.ts index 100c55a88c..05e19a46bc 100644 --- a/src/common/entity/timer_time_remaining.js +++ b/src/common/entity/timer_time_remaining.ts @@ -1,11 +1,12 @@ +import { HassEntity } from "home-assistant-js-websocket"; import durationToSeconds from "../datetime/duration_to_seconds"; -export default function timerTimeRemaining(stateObj) { +export default function timerTimeRemaining(stateObj: HassEntity) { let timeRemaining = durationToSeconds(stateObj.attributes.remaining); if (stateObj.state === "active") { - const now = new Date(); - const madeActive = new Date(stateObj.last_changed); + const now = new Date().getTime(); + const madeActive = new Date(stateObj.last_changed).getTime(); timeRemaining = Math.max(timeRemaining - (now - madeActive) / 1000, 0); } diff --git a/src/common/entity/valid_entity_id.js b/src/common/entity/valid_entity_id.js deleted file mode 100644 index 5a4ea9e23a..0000000000 --- a/src/common/entity/valid_entity_id.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function validEntityId(entityId) { - return /^(\w+)\.(\w+)$/.test(entityId); -} diff --git a/src/common/entity/valid_entity_id.ts b/src/common/entity/valid_entity_id.ts new file mode 100644 index 0000000000..29d345c633 --- /dev/null +++ b/src/common/entity/valid_entity_id.ts @@ -0,0 +1,2 @@ +const validEntityId = /^(\w+)\.(\w+)$/; +export default validEntityId.test; diff --git a/src/common/util/parse-aspect-ratio.js b/src/common/util/parse-aspect-ratio.ts similarity index 76% rename from src/common/util/parse-aspect-ratio.js rename to src/common/util/parse-aspect-ratio.ts index 1797e81e3a..cd80d4b90d 100644 --- a/src/common/util/parse-aspect-ratio.js +++ b/src/common/util/parse-aspect-ratio.ts @@ -1,9 +1,11 @@ export default function parseAspectRatio(input) { // Handle 16x9, 16:9, 1.78x1, 1.78:1, 1.78 // Ignore everything else - function parseOrThrow(number) { - const parsed = parseFloat(number); - if (isNaN(parsed)) throw new Error(`${number} is not a number`); + function parseOrThrow(num) { + const parsed = parseFloat(num); + if (isNaN(parsed)) { + throw new Error(`${num} is not a number`); + } return parsed; } try { diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 0bda6acae7..261ad84dfa 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -211,7 +211,11 @@ export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement) > ${ this._config!.show_state !== false - ? html`
${computeStateDisplay(this.localize, stateObj)}
` + ? html`
${computeStateDisplay( + this.localize, + stateObj, + this.hass!.language + )}
` : "" } diff --git a/src/panels/lovelace/elements/hui-state-label-element.ts b/src/panels/lovelace/elements/hui-state-label-element.ts index 20b8c3287d..5d838ecf9d 100644 --- a/src/panels/lovelace/elements/hui-state-label-element.ts +++ b/src/panels/lovelace/elements/hui-state-label-element.ts @@ -48,7 +48,9 @@ class HuiStateLabelElement extends hassLocalizeLitMixin(LitElement) .longPress="${longPress()}" > ${this._config.prefix}${ - state ? computeStateDisplay(this.localize, state) : "-" + state + ? computeStateDisplay(this.localize, state, this.hass!.language) + : "-" }${this._config.suffix} `; diff --git a/src/types.ts b/src/types.ts index 281eb63a6d..3dacd9f349 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ import { MessageBase, HassEntityBase, HassEntityAttributeBase, + HassServices, } from "home-assistant-js-websocket"; declare global { @@ -75,6 +76,7 @@ export interface HomeAssistant { connection: Connection; connected: boolean; states: HassEntities; + services: HassServices; config: HassConfig; themes: Themes; panels: Panels; @@ -141,3 +143,13 @@ export type LightEntity = HassEntityBase & { hs_color: number[]; }; }; + +export type GroupEntity = HassEntityBase & { + attributes: HassEntityAttributeBase & { + entity_id: string[]; + order: number; + auto?: boolean; + view?: boolean; + control?: "hidden"; + }; +}; diff --git a/test-mocha/common/datetime/duration_to_seconds_test.js b/test-mocha/common/datetime/duration_to_seconds_test.ts similarity index 100% rename from test-mocha/common/datetime/duration_to_seconds_test.js rename to test-mocha/common/datetime/duration_to_seconds_test.ts diff --git a/test-mocha/common/datetime/format_date.js b/test-mocha/common/datetime/format_date.ts similarity index 100% rename from test-mocha/common/datetime/format_date.js rename to test-mocha/common/datetime/format_date.ts diff --git a/test-mocha/common/datetime/format_date_time.js b/test-mocha/common/datetime/format_date_time.ts similarity index 100% rename from test-mocha/common/datetime/format_date_time.js rename to test-mocha/common/datetime/format_date_time.ts diff --git a/test-mocha/common/datetime/format_time.js b/test-mocha/common/datetime/format_time.ts similarity index 100% rename from test-mocha/common/datetime/format_time.js rename to test-mocha/common/datetime/format_time.ts diff --git a/test-mocha/common/datetime/seconds_to_duration_test.js b/test-mocha/common/datetime/seconds_to_duration_test.ts similarity index 100% rename from test-mocha/common/datetime/seconds_to_duration_test.js rename to test-mocha/common/datetime/seconds_to_duration_test.ts diff --git a/test-mocha/common/entity/attribute_class_names_test.js b/test-mocha/common/entity/attribute_class_names_test.ts similarity index 89% rename from test-mocha/common/entity/attribute_class_names_test.js rename to test-mocha/common/entity/attribute_class_names_test.ts index 1a5b29f327..b6a904b00a 100644 --- a/test-mocha/common/entity/attribute_class_names_test.js +++ b/test-mocha/common/entity/attribute_class_names_test.ts @@ -6,12 +6,12 @@ describe("attributeClassNames", () => { const attrs = ["mock_attr1", "mock_attr2"]; it("Skips null states", () => { - const stateObj = null; + const stateObj: any = null; assert.strictEqual(attributeClassNames(stateObj, attrs), ""); }); it("Matches no attrbutes", () => { - const stateObj = { + const stateObj: any = { attributes: { other_attr_1: 1, other_attr_2: 2, @@ -21,7 +21,7 @@ describe("attributeClassNames", () => { }); it("Matches one attrbute", () => { - const stateObj = { + const stateObj: any = { attributes: { other_attr_1: 1, other_attr_2: 2, @@ -32,7 +32,7 @@ describe("attributeClassNames", () => { }); it("Matches two attrbutes", () => { - const stateObj = { + const stateObj: any = { attributes: { other_attr_1: 1, other_attr_2: 2, diff --git a/test-mocha/common/entity/can_toggle_domain_test.js b/test-mocha/common/entity/can_toggle_domain_test.ts similarity index 97% rename from test-mocha/common/entity/can_toggle_domain_test.js rename to test-mocha/common/entity/can_toggle_domain_test.ts index 9416e885c2..a3d3d36b30 100644 --- a/test-mocha/common/entity/can_toggle_domain_test.js +++ b/test-mocha/common/entity/can_toggle_domain_test.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import canToggleDomain from "../../../src/common/entity/can_toggle_domain"; describe("canToggleDomain", () => { - const hass = { + const hass: any = { services: { light: { turn_on: null, // Service keys only need to be present for test diff --git a/test-mocha/common/entity/can_toggle_state_test.js b/test-mocha/common/entity/can_toggle_state_test.ts similarity index 83% rename from test-mocha/common/entity/can_toggle_state_test.js rename to test-mocha/common/entity/can_toggle_state_test.ts index 2a1c706031..b0239dd376 100644 --- a/test-mocha/common/entity/can_toggle_state_test.js +++ b/test-mocha/common/entity/can_toggle_state_test.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import canToggleState from "../../../src/common/entity/can_toggle_state"; describe("canToggleState", () => { - const hass = { + const hass: any = { services: { light: { turn_on: null, // Service keys only need to be present for test @@ -13,7 +13,7 @@ describe("canToggleState", () => { }; it("Detects lights toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "light.bla", state: "on", }; @@ -21,7 +21,7 @@ describe("canToggleState", () => { }); it("Detects group with toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "group.bla", state: "on", }; @@ -29,7 +29,7 @@ describe("canToggleState", () => { }); it("Detects group without toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "group.devices", state: "home", }; @@ -37,7 +37,7 @@ describe("canToggleState", () => { }); it("Detects climate with toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "climate.bla", attributes: { supported_features: 4096, @@ -47,8 +47,11 @@ describe("canToggleState", () => { }); it("Detects climate without toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "climate.bla", + attributes: { + supported_features: 0, + }, }; assert.isFalse(canToggleState(hass, stateObj)); }); diff --git a/test-mocha/common/entity/compute_domain.js b/test-mocha/common/entity/compute_domain.ts similarity index 100% rename from test-mocha/common/entity/compute_domain.js rename to test-mocha/common/entity/compute_domain.ts diff --git a/test-mocha/common/entity/compute_state_display.js b/test-mocha/common/entity/compute_state_display.ts similarity index 82% rename from test-mocha/common/entity/compute_state_display.js rename to test-mocha/common/entity/compute_state_display.ts index d599d0fc74..3d0da10b56 100644 --- a/test-mocha/common/entity/compute_state_display.js +++ b/test-mocha/common/entity/compute_state_display.ts @@ -3,13 +3,12 @@ import { assert } from "chai"; import computeStateDisplay from "../../../src/common/entity/compute_state_display"; describe("computeStateDisplay", () => { - const localize = function(message, ...args) { - // Mock Localize function for testing - return message + (args.length ? ": " + args.join(",") : ""); - }; + // Mock Localize function for testing + const localize = (message, ...args) => + message + (args.length ? ": " + args.join(",") : ""); it("Localizes binary sensor defaults", () => { - const stateObj = { + const stateObj: any = { entity_id: "binary_sensor.test", state: "off", attributes: {}, @@ -21,7 +20,7 @@ describe("computeStateDisplay", () => { }); it("Localizes binary sensor device class", () => { - const stateObj = { + const stateObj: any = { entity_id: "binary_sensor.test", state: "off", attributes: { @@ -35,12 +34,13 @@ describe("computeStateDisplay", () => { }); it("Localizes binary sensor invalid device class", () => { - const altLocalize = function(message, ...args) { - if (message === "state.binary_sensor.invalid_device_class.off") - return null; + const altLocalize = (message, ...args) => { + if (message === "state.binary_sensor.invalid_device_class.off") { + return ""; + } return localize(message, ...args); }; - const stateObj = { + const stateObj: any = { entity_id: "binary_sensor.test", state: "off", attributes: { @@ -54,7 +54,7 @@ describe("computeStateDisplay", () => { }); it("Localizes sensor value with units", () => { - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", state: "123", attributes: { @@ -65,11 +65,13 @@ describe("computeStateDisplay", () => { }); it("Localizes unknown sensor value with units", () => { - const altLocalize = function(message, ...args) { - if (message === "state.sensor.unknown") return null; + const altLocalize = (message, ...args) => { + if (message === "state.sensor.unknown") { + return ""; + } return localize(message, ...args); }; - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", state: "unknown", attributes: { @@ -83,11 +85,13 @@ describe("computeStateDisplay", () => { }); it("Localizes unavailable sensor value with units", () => { - const altLocalize = function(message, ...args) { - if (message === "state.sensor.unavailable") return null; + const altLocalize = (message, ...args) => { + if (message === "state.sensor.unavailable") { + return ""; + } return localize(message, ...args); }; - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", state: "unavailable", attributes: { @@ -101,11 +105,13 @@ describe("computeStateDisplay", () => { }); it("Localizes sensor value with component translation", () => { - const altLocalize = function(message, ...args) { - if (message !== "component.sensor.state.custom_state") return null; + const altLocalize = (message, ...args) => { + if (message !== "component.sensor.state.custom_state") { + return ""; + } return localize(message, ...args); }; - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", state: "custom_state", attributes: {}, @@ -117,7 +123,7 @@ describe("computeStateDisplay", () => { }); it("Localizes input_datetime with full date time", () => { - const stateObj = { + const stateObj: any = { entity_id: "input_datetime.test", state: "123", attributes: { @@ -138,7 +144,7 @@ describe("computeStateDisplay", () => { }); it("Localizes input_datetime with date", () => { - const stateObj = { + const stateObj: any = { entity_id: "input_datetime.test", state: "123", attributes: { @@ -159,7 +165,7 @@ describe("computeStateDisplay", () => { }); it("Localizes input_datetime with time", () => { - const stateObj = { + const stateObj: any = { entity_id: "input_datetime.test", state: "123", attributes: { @@ -180,7 +186,7 @@ describe("computeStateDisplay", () => { }); it("Localizes zwave ready", () => { - const stateObj = { + const stateObj: any = { entity_id: "zwave.test", state: "ready", attributes: { @@ -194,7 +200,7 @@ describe("computeStateDisplay", () => { }); it("Localizes zwave initializing", () => { - const stateObj = { + const stateObj: any = { entity_id: "zwave.test", state: "initializing", attributes: { @@ -208,7 +214,7 @@ describe("computeStateDisplay", () => { }); it("Localizes cover open", () => { - const stateObj = { + const stateObj: any = { entity_id: "cover.test", state: "open", attributes: {}, @@ -220,11 +226,13 @@ describe("computeStateDisplay", () => { }); it("Localizes unavailable", () => { - const altLocalize = function(message, ...args) { - if (message === "state.sensor.unavailable") return null; + const altLocalize = (message, ...args) => { + if (message === "state.sensor.unavailable") { + return ""; + } return localize(message, ...args); }; - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", state: "unavailable", attributes: {}, @@ -236,11 +244,11 @@ describe("computeStateDisplay", () => { }); it("Localizes custom state", () => { - const altLocalize = function() { + const altLocalize = () => { // No matches can be found - return null; + return ""; }; - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", state: "My Custom State", attributes: {}, @@ -252,7 +260,7 @@ describe("computeStateDisplay", () => { }); it("Only calculates state display once per immutable state object", () => { - const stateObj = { + const stateObj: any = { entity_id: "cover.test", state: "open", attributes: {}, diff --git a/test-mocha/common/entity/compute_state_domain.js b/test-mocha/common/entity/compute_state_domain.ts similarity index 91% rename from test-mocha/common/entity/compute_state_domain.js rename to test-mocha/common/entity/compute_state_domain.ts index f65cb2bac7..803737fa5a 100644 --- a/test-mocha/common/entity/compute_state_domain.js +++ b/test-mocha/common/entity/compute_state_domain.ts @@ -4,7 +4,7 @@ import computeStateDomain from "../../../src/common/entity/compute_state_domain" describe("computeStateDomain", () => { it("Detects sensor domain", () => { - const stateObj = { + const stateObj: any = { entity_id: "sensor.test", }; assert.strictEqual(computeStateDomain(stateObj), "sensor"); diff --git a/test-mocha/common/entity/extract_views.spec.js b/test-mocha/common/entity/extract_views.spec.ts similarity index 95% rename from test-mocha/common/entity/extract_views.spec.js rename to test-mocha/common/entity/extract_views.spec.ts index 2feb8879ea..9528f21a9a 100644 --- a/test-mocha/common/entity/extract_views.spec.js +++ b/test-mocha/common/entity/extract_views.spec.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import * as assert from "assert"; import extractViews from "../../../src/common/entity/extract_views"; diff --git a/test-mocha/common/entity/feature_class_names_test.js b/test-mocha/common/entity/feature_class_names_test.ts similarity index 74% rename from test-mocha/common/entity/feature_class_names_test.js rename to test-mocha/common/entity/feature_class_names_test.ts index 005359e2eb..544d95a1e6 100644 --- a/test-mocha/common/entity/feature_class_names_test.js +++ b/test-mocha/common/entity/feature_class_names_test.ts @@ -1,6 +1,7 @@ import { assert } from "chai"; import featureClassNames from "../../../src/common/entity/feature_class_names"; +import { HassEntity } from "home-assistant-js-websocket"; describe("featureClassNames", () => { const classNames = { @@ -12,11 +13,12 @@ describe("featureClassNames", () => { it("Skips null states", () => { const stateObj = null; - assert.strictEqual(featureClassNames(stateObj, classNames), ""); + assert.strictEqual(featureClassNames(stateObj!, classNames), ""); }); it("Matches no features", () => { - const stateObj = { + // tslint:disable-next-line + const stateObj = { attributes: { supported_features: 64, }, @@ -25,7 +27,8 @@ describe("featureClassNames", () => { }); it("Matches one feature", () => { - const stateObj = { + // tslint:disable-next-line + const stateObj = { attributes: { supported_features: 72, }, @@ -37,7 +40,8 @@ describe("featureClassNames", () => { }); it("Matches two features", () => { - const stateObj = { + // tslint:disable-next-line + const stateObj = { attributes: { supported_features: 73, }, diff --git a/test-mocha/common/entity/get_group_entities.spec.js b/test-mocha/common/entity/get_group_entities.spec.ts similarity index 96% rename from test-mocha/common/entity/get_group_entities.spec.js rename to test-mocha/common/entity/get_group_entities.spec.ts index 35052dee0a..a6c5d6177b 100644 --- a/test-mocha/common/entity/get_group_entities.spec.js +++ b/test-mocha/common/entity/get_group_entities.spec.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import * as assert from "assert"; import getGroupEntities from "../../../src/common/entity/get_group_entities"; diff --git a/test-mocha/common/entity/get_view_entities.spec.js b/test-mocha/common/entity/get_view_entities.spec.ts similarity index 98% rename from test-mocha/common/entity/get_view_entities.spec.js rename to test-mocha/common/entity/get_view_entities.spec.ts index b95cb573ac..c39b8a37ed 100644 --- a/test-mocha/common/entity/get_view_entities.spec.js +++ b/test-mocha/common/entity/get_view_entities.spec.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import * as assert from "assert"; import getViewEntities from "../../../src/common/entity/get_view_entities"; diff --git a/test-mocha/common/entity/has_location.test.js b/test-mocha/common/entity/has_location.test.ts similarity index 87% rename from test-mocha/common/entity/has_location.test.js rename to test-mocha/common/entity/has_location.test.ts index 10389d35be..88ee4f7508 100644 --- a/test-mocha/common/entity/has_location.test.js +++ b/test-mocha/common/entity/has_location.test.ts @@ -4,7 +4,7 @@ import hasLocation from "../../../src/common/entity/has_location"; describe("hasLocation", () => { it("flags states with location", () => { - const stateObj = { + const stateObj: any = { attributes: { latitude: 12.34, longitude: 12.34, @@ -13,7 +13,7 @@ describe("hasLocation", () => { assert(hasLocation(stateObj)); }); it("does not flag states with only latitude", () => { - const stateObj = { + const stateObj: any = { attributes: { latitude: 12.34, }, @@ -21,7 +21,7 @@ describe("hasLocation", () => { assert(!hasLocation(stateObj)); }); it("does not flag states with only longitude", () => { - const stateObj = { + const stateObj: any = { attributes: { longitude: 12.34, }, @@ -29,7 +29,7 @@ describe("hasLocation", () => { assert(!hasLocation(stateObj)); }); it("does not flag states with no location", () => { - const stateObj = { + const stateObj: any = { attributes: {}, }; assert(!hasLocation(stateObj)); diff --git a/test-mocha/common/entity/split_by_groups.spec.js b/test-mocha/common/entity/split_by_groups.spec.ts similarity index 96% rename from test-mocha/common/entity/split_by_groups.spec.js rename to test-mocha/common/entity/split_by_groups.spec.ts index 85c31f4715..ede3c3ea19 100644 --- a/test-mocha/common/entity/split_by_groups.spec.js +++ b/test-mocha/common/entity/split_by_groups.spec.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import * as assert from "assert"; import splitByGroups from "../../../src/common/entity/split_by_groups"; diff --git a/test-mocha/common/entity/state_card_type_test.js b/test-mocha/common/entity/state_card_type_test.ts similarity index 88% rename from test-mocha/common/entity/state_card_type_test.js rename to test-mocha/common/entity/state_card_type_test.ts index 3d9c66edbe..deb455784c 100644 --- a/test-mocha/common/entity/state_card_type_test.js +++ b/test-mocha/common/entity/state_card_type_test.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import stateCardType from "../../../src/common/entity/state_card_type"; describe("stateCardType", () => { - const hass = { + const hass: any = { services: { light: { turn_on: null, // Service keys only need to be present for test @@ -13,21 +13,21 @@ describe("stateCardType", () => { }; it("Returns display for unavailable states", () => { - const stateObj = { + const stateObj: any = { state: "unavailable", }; assert.strictEqual(stateCardType(hass, stateObj), "display"); }); it("Returns media_player for media_player states", () => { - const stateObj = { + const stateObj: any = { entity_id: "media_player.bla", }; assert.strictEqual(stateCardType(hass, stateObj), "media_player"); }); it("Returns toggle for states that can toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "light.bla", attributes: {}, }; @@ -35,7 +35,7 @@ describe("stateCardType", () => { }); it("Returns display for states with hidden control", () => { - const stateObj = { + const stateObj: any = { entity_id: "light.bla", attributes: { control: "hidden", @@ -45,7 +45,7 @@ describe("stateCardType", () => { }); it("Returns display for entities that cannot toggle", () => { - const stateObj = { + const stateObj: any = { entity_id: "sensor.bla", }; assert.strictEqual(stateCardType(hass, stateObj), "display"); diff --git a/test-mocha/common/entity/state_more_info_type_test.js b/test-mocha/common/entity/state_more_info_type_test.ts similarity index 89% rename from test-mocha/common/entity/state_more_info_type_test.js rename to test-mocha/common/entity/state_more_info_type_test.ts index 93a1e8d7cb..1d62f53b8d 100644 --- a/test-mocha/common/entity/state_more_info_type_test.js +++ b/test-mocha/common/entity/state_more_info_type_test.ts @@ -4,14 +4,14 @@ import stateMoreInfoType from "../../../src/common/entity/state_more_info_type"; describe("stateMoreInfoType", () => { it("Returns media_player for media_player states", () => { - const stateObj = { + const stateObj: any = { entity_id: "media_player.bla", }; assert.strictEqual(stateMoreInfoType(stateObj), "media_player"); }); it("Returns hidden for input_select states", () => { - const stateObj = { + const stateObj: any = { entity_id: "input_select.bla", attributes: {}, }; @@ -19,7 +19,7 @@ describe("stateMoreInfoType", () => { }); it("Returns default for switch states", () => { - const stateObj = { + const stateObj: any = { entity_id: "switch.bla", attributes: {}, }; diff --git a/test-mocha/common/entity/test_util.js b/test-mocha/common/entity/test_util.ts similarity index 97% rename from test-mocha/common/entity/test_util.js rename to test-mocha/common/entity/test_util.ts index 32aa92aef6..1d7b3f203c 100644 --- a/test-mocha/common/entity/test_util.js +++ b/test-mocha/common/entity/test_util.ts @@ -27,7 +27,7 @@ export function createView(entity) { return createGroup(entity); } -export function createLightEntity(isOn) { +export function createLightEntity(isOn?) { mockState++; if (isOn === undefined) { isOn = Math.random() > 0.5; diff --git a/test-mocha/common/entity/timer_time_remaining_test.js b/test-mocha/common/entity/timer_time_remaining_test.ts similarity index 92% rename from test-mocha/common/entity/timer_time_remaining_test.js rename to test-mocha/common/entity/timer_time_remaining_test.ts index fd77bc92b8..e049fa1015 100644 --- a/test-mocha/common/entity/timer_time_remaining_test.js +++ b/test-mocha/common/entity/timer_time_remaining_test.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import sinon from "sinon"; +import * as sinon from "sinon"; import timerTimeRemaining from "../../../src/common/entity/timer_time_remaining"; @@ -11,7 +11,7 @@ describe("timerTimeRemaining", () => { attributes: { remaining: "0:01:05", }, - }), + } as any), 65 ); }); @@ -23,7 +23,7 @@ describe("timerTimeRemaining", () => { attributes: { remaining: "0:01:05", }, - }), + } as any), 65 ); }); @@ -44,7 +44,7 @@ describe("timerTimeRemaining", () => { remaining: "0:01:05", }, last_changed: "2018-01-17T16:15:12Z", - }), + } as any), 47 ); }); diff --git a/test-mocha/common/util/parse_aspect_ratio_test.js b/test-mocha/common/util/parse_aspect_ratio_test.ts similarity index 97% rename from test-mocha/common/util/parse_aspect_ratio_test.js rename to test-mocha/common/util/parse_aspect_ratio_test.ts index dd6af4f3cc..15b42a0cc1 100644 --- a/test-mocha/common/util/parse_aspect_ratio_test.js +++ b/test-mocha/common/util/parse_aspect_ratio_test.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import * as assert from "assert"; import parseAspectRatio from "../../../src/common/util/parse-aspect-ratio"; diff --git a/test-mocha/mocha.opts b/test-mocha/mocha.opts index f25388f803..7157db4af7 100644 --- a/test-mocha/mocha.opts +++ b/test-mocha/mocha.opts @@ -1,4 +1,3 @@ --recursive ---require reify --timeout 10000 -test-mocha +test-mocha/**/*.ts diff --git a/test-mocha/tsconfig.test.json b/test-mocha/tsconfig.test.json new file mode 100644 index 0000000000..0e7afc6855 --- /dev/null +++ b/test-mocha/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "commonjs" + } +} diff --git a/test-mocha/tslint.json b/test-mocha/tslint.json new file mode 100644 index 0000000000..1e32f15d50 --- /dev/null +++ b/test-mocha/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": ["../tslint.json"], + "rules": { + "no-implicit-dependencies": [true, "dev"] + } +} diff --git a/yarn.lock b/yarn.lock index 6bb4ab49fa..f88b1a14d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1372,10 +1372,10 @@ dependencies: any-observable "^0.3.0" -"@sinonjs/commons@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.0.2.tgz#3e0ac737781627b8844257fadc3d803997d0526e" - integrity sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw== +"@sinonjs/commons@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.3.0.tgz#50a2754016b6f30a994ceda6d9a0a8c36adda849" + integrity sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA== dependencies: type-detect "4.0.8" @@ -1449,7 +1449,7 @@ dependencies: "@types/chai" "*" -"@types/chai@*": +"@types/chai@*", "@types/chai@^4.1.7": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a" integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA== @@ -1624,6 +1624,11 @@ resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-0.2.0.tgz#6f24ee48731d31168ea510610d6dd15e5fc9c6ff" integrity sha1-byTuSHMdMRaOpRBhDW3RXl/Jxv8= +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/launchpad@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@types/launchpad/-/launchpad-0.6.0.tgz#37296109b7f277f6e6c5fd7e0c0706bc918fbb51" @@ -1646,6 +1651,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/mocha@^5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" + integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== + "@types/mz@0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.29.tgz#bc24728c649973f1c7851e9033f9ce525668c27b" @@ -3667,11 +3677,6 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -3777,7 +3782,7 @@ buffer-fill@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= -buffer-from@^1.0.0: +buffer-from@^1.0.0, buffer-from@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== @@ -3965,7 +3970,7 @@ chai@^3.5.0: deep-eql "^0.1.3" type-detect "^1.0.0" -chai@^4.1.2: +chai@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== @@ -4369,7 +4374,7 @@ command-line-usage@^5.0.5: table-layout "^0.4.3" typical "^2.6.1" -commander@2.15.1, commander@2.15.x, commander@~2.15.0: +commander@2.15.x, commander@~2.15.0: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== @@ -4848,7 +4853,7 @@ debug@2.6.8: dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0, debug@~3.1.0: +debug@=3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -4939,6 +4944,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^2.0.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + default-gateway@^2.6.0: version "2.7.2" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" @@ -5118,16 +5128,16 @@ diff@3.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= -diff@3.5.0, diff@^3.1.0, diff@^3.2.0, diff@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - diff@^2.1.2: version "2.2.3" resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k= +diff@^3.1.0, diff@^3.2.0, diff@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -6772,18 +6782,6 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^4.3.1: version "4.5.3" resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" @@ -7065,11 +7063,6 @@ grouped-queue@^0.3.0, grouped-queue@^0.3.3: dependencies: lodash "^4.17.2" -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -9334,6 +9327,11 @@ make-dir@^1.0.0, make-dir@^1.1.0: dependencies: pify "^3.0.0" +make-error@^1.1.1: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== + map-age-cleaner@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74" @@ -9599,7 +9597,7 @@ minimatch-all@^1.1.0: dependencies: minimatch "^3.0.2" -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -9721,23 +9719,6 @@ mocha@^3.4.2: mkdirp "0.5.1" supports-color "3.1.2" -mocha@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== - dependencies: - browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.5" - he "1.1.1" - minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" - moment@^2.10.2, moment@^2.22.0: version "2.22.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" @@ -12321,12 +12302,12 @@ sinon@^2.3.5: text-encoding "0.6.4" type-detect "^4.0.0" -sinon@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.1.0.tgz#819b63002ee09a90a3b50a0da4e0bdecb2e3f345" - integrity sha512-ffASxced8xr8eU0EGyfj9K++bRCtv/NyOFOxl7UBD86YH97oZjVxvecMhObwRlXe27GRUa6rVFEn67khPZ29rQ== +sinon@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.1.1.tgz#1202f317aa14d93cb9b69ff50b6bd49c0e05ffc9" + integrity sha512-iYagtjLVt1vN3zZY7D8oH7dkjNJEjLjyuzy8daX5+3bbQl8gaohrheB9VfH1O3L6LKuue5WTJvFluHiuZ9y3nQ== dependencies: - "@sinonjs/commons" "^1.0.2" + "@sinonjs/commons" "^1.2.0" "@sinonjs/formatio" "^3.0.0" "@sinonjs/samsam" "^2.1.2" diff "^3.5.0" @@ -12503,6 +12484,14 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" +source-map-support@^0.5.6: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -12513,7 +12502,7 @@ source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -12875,7 +12864,7 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -supports-color@3.1.2, supports-color@5.4.0, supports-color@^0.2.0, supports-color@^2.0.0, supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@3.1.2, supports-color@^0.2.0, supports-color@^2.0.0, supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" integrity sha1-cqJiiU2dQIuVbKBf83su2KbiotU= @@ -13251,6 +13240,40 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== +ts-mocha@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-mocha/-/ts-mocha-2.0.0.tgz#0dbd3cd04671df9933b9303b4aa46347573c5635" + integrity sha512-Rj6+vvwKtOTs5GsNO1jLl4DIXUGnyAg5HFt2Yb4SHIRN45clTJkHWpNdTxCSL0u+1oeavSYJah6d1PZ++Ju5pw== + dependencies: + ts-node "7.0.0" + optionalDependencies: + tsconfig-paths "^3.5.0" + +ts-node@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.0.tgz#a94a13c75e5e1aa6b82814b84c68deb339ba7bff" + integrity sha512-klJsfswHP0FuOLsvBZ/zzCfUvakOSSxds78mVeK7I+qP76YWtxf16hEZsp3U+b0kIo82R5UatGFeblYMqabb2Q== + dependencies: + arrify "^1.0.0" + buffer-from "^1.1.0" + diff "^3.1.0" + make-error "^1.1.1" + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map-support "^0.5.6" + yn "^2.0.0" + +tsconfig-paths@^3.5.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.6.0.tgz#f14078630d9d6e8b1dc690c1fc0cfb9cd0663891" + integrity sha512-mrqQIP2F4e03aMTCiPdedCIT300//+q0ET53o5WqqtQjmEICxP9yfz/sHTpPqXpssuJEzODsEzJaLRaf5J2X1g== + dependencies: + "@types/json5" "^0.0.29" + deepmerge "^2.0.1" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + tslib@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" @@ -14645,6 +14668,11 @@ yeoman-generator@^3.1.1: through2 "^2.0.0" yeoman-environment "^2.0.5" +yn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" + integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo= + zip-stream@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" From 935639e5e0ab828e7e3795095adbff28a14c817e Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Tue, 6 Nov 2018 04:09:28 -0500 Subject: [PATCH 09/68] Add getElementConfig to Glance + Add Form UI for updating YAML (#1944) * Working version * Working kind of * Some more changes * More review changes * Progress * Review updates * Adding new changes * Remove un-needed code * Adding Types * Updating UI Editor a bit * Updating from missed reviews * Updates from Reviews * Yaml is not update each time. Instead stored as LovelaceConfig. * Update to not pull config from preview but store it each time it changed * Updating from Reviews * Try catch fix * Update hui-dialog-edit-card.ts --- src/panels/lovelace/cards/hui-glance-card.ts | 30 ++-- .../lovelace/editor/hui-dialog-edit-card.ts | 152 +++++++++++++++--- .../lovelace/editor/hui-glance-card-editor.ts | 79 +++++++++ .../lovelace/editor/hui-yaml-card-preview.ts | 17 +- src/panels/lovelace/editor/hui-yaml-editor.ts | 15 +- src/panels/lovelace/editor/types.ts | 12 ++ src/panels/lovelace/types.ts | 5 + 7 files changed, 260 insertions(+), 50 deletions(-) create mode 100644 src/panels/lovelace/editor/hui-glance-card-editor.ts create mode 100644 src/panels/lovelace/editor/types.ts diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 261ad84dfa..df27398474 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -7,24 +7,23 @@ import { import { TemplateResult } from "lit-html"; import { classMap } from "lit-html/directives/classMap"; -import computeStateDisplay from "../../../common/entity/compute_state_display"; -import computeStateName from "../../../common/entity/compute_state_name"; -import processConfigEntities from "../common/process-config-entities"; -import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; +import { fireEvent } from "../../../common/dom/fire_event.js"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../types.js"; +import { LovelaceCard, LovelaceConfig, LovelaceCardEditor } from "../types.js"; +import { longPress } from "../common/directives/long-press-directive"; -import toggleEntity from "../common/entity/toggle-entity"; +import computeStateDisplay from "../../../common/entity/compute_state_display.js"; +import computeStateName from "../../../common/entity/compute_state_name.js"; +import processConfigEntities from "../common/process-config-entities"; +import applyThemesOnElement from "../../../common/dom/apply_themes_on_element.js"; +import toggleEntity from "../common/entity/toggle-entity.js"; import "../../../components/entity/state-badge"; import "../../../components/ha-card"; import "../../../components/ha-icon"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard, LovelaceConfig } from "../types"; -import { longPress } from "../common/directives/long-press-directive"; - -interface EntityConfig { +export interface EntityConfig { name: string; icon: string; entity: string; @@ -34,7 +33,7 @@ interface EntityConfig { service_data?: object; } -interface Config extends LovelaceConfig { +export interface Config extends LovelaceConfig { show_name?: boolean; show_state?: boolean; title?: string; @@ -45,6 +44,11 @@ interface Config extends LovelaceConfig { export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement) implements LovelaceCard { + public static async getConfigElement(): Promise { + await import("../editor/hui-glance-card-editor"); + return document.createElement("hui-glance-card-editor"); + } + public hass?: HomeAssistant; private _config?: Config; private _configEntities?: EntityConfig[]; diff --git a/src/panels/lovelace/editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/hui-dialog-edit-card.ts index bc4b2eea0c..c34288d621 100644 --- a/src/panels/lovelace/editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/hui-dialog-edit-card.ts @@ -1,5 +1,7 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; -import { fireEvent } from "../../../common/dom/fire_event"; +import yaml from "js-yaml"; +import { when } from "lit-html/directives/when"; +import { TemplateResult } from "lit-html"; import "@polymer/paper-button/paper-button"; import "@polymer/paper-input/paper-textarea"; @@ -10,18 +12,26 @@ import "@polymer/paper-dialog/paper-dialog"; import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; import { HomeAssistant } from "../../../types"; import { getCardConfig, updateCardConfig } from "../common/data"; +import { fireEvent } from "../../../common/dom/fire_event"; import "./hui-yaml-editor"; import "./hui-yaml-card-preview"; // This is not a duplicate import, one is for types, one is for element. // tslint:disable-next-line import { HuiYAMLCardPreview } from "./hui-yaml-card-preview"; +import { LovelaceCardEditor, LovelaceConfig } from "../types"; +import { YamlChangedEvent, ConfigValue } from "./types"; + +const CUSTOM_TYPE_PREFIX = "custom:"; export class HuiDialogEditCard extends LitElement { protected hass?: HomeAssistant; private _cardId?: string; - private _cardConfig?: string; + private _originalConfigYaml?: string; + private _configElement?: LovelaceCardEditor | null; private _reloadLovelace?: () => void; + private _editorToggle?: boolean; + private _configValue?: ConfigValue; static get properties(): PropertyDeclarations { return { @@ -29,8 +39,9 @@ export class HuiDialogEditCard extends LitElement { cardId: { type: Number, }, - _cardConfig: {}, _dialogClosedCallback: {}, + _configElement: {}, + _editorToggle: {}, }; } @@ -38,8 +49,10 @@ export class HuiDialogEditCard extends LitElement { this.hass = hass; this._cardId = cardId; this._reloadLovelace = reloadLovelace; - this._cardConfig = ""; - this._loadConfig(); + this._editorToggle = true; + this._configElement = undefined; + this._configValue = { format: "yaml", value: "" }; + this._loadConfig().then(() => this._loadConfigElement()); // Wait till dialog is rendered. await this.updateComplete; this._dialog.open(); @@ -53,58 +66,147 @@ export class HuiDialogEditCard extends LitElement { return this.shadowRoot!.querySelector("hui-yaml-card-preview")!; } - protected render() { + protected render(): TemplateResult { return html`

Card Configuration

- + ${ + this._editorToggle && this._configElement !== null + ? html`
${when( + this._configElement, + () => this._configElement, + () => html`Loading...` + )}
` + : html` + ` + }
- Cancel - Save + Toggle Editor + Cancel + Save
`; } - private _handleYamlChanged(ev) { - this._previewEl.yaml = ev.detail.yaml; + private _handleYamlChanged(ev: YamlChangedEvent): void { + this._configValue = { format: "yaml", value: ev.detail.yaml }; + this._updatePreview(this._configValue); } - private _closeDialog() { + private _handleJSConfigChanged(value: LovelaceConfig): void { + this._configElement!.setConfig(value); + this._configValue = { format: "js", value }; + this._updatePreview(this._configValue); + } + + private _updatePreview(value: ConfigValue) { + if (!this._previewEl) { + return; + } + this._previewEl.value = value; + } + + private _closeDialog(): void { this._dialog.close(); } - private async _loadConfig() { - this._cardConfig = await getCardConfig(this.hass!, this._cardId!); - await this.updateComplete; - // This will center the dialog with the updated config + private _toggleEditor(): void { + if (this._editorToggle && this._configValue!.format === "js") { + this._configValue = { + format: "yaml", + value: yaml.safeDump(this._configValue!.value), + }; + } else if (this._configElement && this._configValue!.format === "yaml") { + this._configValue = { + format: "js", + value: yaml.safeLoad(this._configValue!.value), + }; + this._configElement.setConfig(this._configValue!.value as LovelaceConfig); + } + this._editorToggle = !this._editorToggle; + } + + private async _loadConfig(): Promise { + const cardConfig = await getCardConfig(this.hass!, this._cardId!); + this._configValue = { + format: "yaml", + value: cardConfig, + }; + this._originalConfigYaml = cardConfig; + } + + private async _loadConfigElement(): Promise { + const conf = yaml.safeLoad(this._configValue!.value); + + const tag = conf.type.startsWith(CUSTOM_TYPE_PREFIX) + ? conf.type.substr(CUSTOM_TYPE_PREFIX.length) + : `hui-${conf.type}-card`; + + const elClass = customElements.get(tag); + let configElement; + + try { + configElement = await elClass.getConfigElement(); + } catch (err) { + this._configElement = null; + return; + } + + configElement.setConfig(conf); + configElement.hass = this.hass; + configElement.addEventListener("config-changed", (ev) => + this._handleJSConfigChanged(ev.detail.config) + ); + this._configValue = { format: "js", value: conf }; + this._configElement = configElement; + + // This will center the dialog with the updated config Element fireEvent(this._dialog, "iron-resize"); } - private async _updateConfig() { - const newCardConfig = this.shadowRoot!.querySelector("hui-yaml-editor")! - .yaml; + private async _updateConfigInBackend(): Promise { + if (this._configValue!.format === "js") { + this._configValue = { + format: "yaml", + value: yaml.safeDump(this._configValue!.value), + }; + } - if (this._cardConfig === newCardConfig) { + if (this._configValue!.value === this._originalConfigYaml) { this._dialog.close(); return; } + try { - await updateCardConfig(this.hass!, this._cardId!, newCardConfig); + await updateCardConfig( + this.hass!, + this._cardId!, + this._configValue!.value + ); this._dialog.close(); this._reloadLovelace!(); } catch (err) { diff --git a/src/panels/lovelace/editor/hui-glance-card-editor.ts b/src/panels/lovelace/editor/hui-glance-card-editor.ts new file mode 100644 index 0000000000..328e2a7fa5 --- /dev/null +++ b/src/panels/lovelace/editor/hui-glance-card-editor.ts @@ -0,0 +1,79 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-checkbox/paper-checkbox.js"; + +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../types.js"; +import { LovelaceCardEditor } from "../types.js"; +import { fireEvent } from "../../../common/dom/fire_event.js"; +import { Config } from "../cards/hui-glance-card"; + +import "../../../components/entity/state-badge.js"; +import "../../../components/entity/ha-entity-picker"; +import "../../../components/ha-card.js"; +import "../../../components/ha-icon.js"; + +export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + _config: {}, + }; + } + + public setConfig(config: Config): void { + this._config = { type: "glance", ...config }; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` +
+ Show Entity's Name?

+ Show Entity's State Text?
+ `; + } + + private _valueChanged(ev: MouseEvent): void { + if (!this._config || !this.hass) { + return; + } + + const target = ev.target! as any; + + const newValue = + target.checked !== undefined ? target.checked : target.value; + + fireEvent(this, "config-changed", { + config: { ...this._config, [target.configValue]: newValue }, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-glance-card-editor": HuiGlanceCardEditor; + } +} + +customElements.define("hui-glance-card-editor", HuiGlanceCardEditor); diff --git a/src/panels/lovelace/editor/hui-yaml-card-preview.ts b/src/panels/lovelace/editor/hui-yaml-card-preview.ts index 3ca81c2c5f..c187c2c496 100644 --- a/src/panels/lovelace/editor/hui-yaml-card-preview.ts +++ b/src/panels/lovelace/editor/hui-yaml-card-preview.ts @@ -6,6 +6,7 @@ import createCardElement from "../common/create-card-element"; import createErrorCardConfig from "../common/create-error-card-config"; import { HomeAssistant } from "../../../types"; import { LovelaceCard } from "../types"; +import { ConfigValue } from "./types"; export class HuiYAMLCardPreview extends HTMLElement { private _hass?: HomeAssistant; @@ -17,20 +18,24 @@ export class HuiYAMLCardPreview extends HTMLElement { } } - set yaml(value: string) { + set value(configValue: ConfigValue) { if (this.lastChild) { this.removeChild(this.lastChild); } - if (value === "") { + if (!configValue.value || configValue.value === "") { return; } let conf; - try { - conf = yaml.safeLoad(value); - } catch (err) { - conf = createErrorCardConfig(`Invalid YAML: ${err.message}`, undefined); + if (configValue.format === "yaml") { + try { + conf = yaml.safeLoad(configValue.value); + } catch (err) { + conf = createErrorCardConfig(`Invalid YAML: ${err.message}`, undefined); + } + } else { + conf = configValue.value; } const element = createCardElement(conf); diff --git a/src/panels/lovelace/editor/hui-yaml-editor.ts b/src/panels/lovelace/editor/hui-yaml-editor.ts index e1977849c7..3191f8de2b 100644 --- a/src/panels/lovelace/editor/hui-yaml-editor.ts +++ b/src/panels/lovelace/editor/hui-yaml-editor.ts @@ -1,8 +1,9 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; -import { fireEvent } from "../../../common/dom/fire_event"; - +import { TemplateResult } from "lit-html"; import "@polymer/paper-input/paper-textarea"; +import { fireEvent } from "../../../common/dom/fire_event"; + export class HuiYAMLEditor extends LitElement { public yaml?: string; @@ -12,7 +13,7 @@ export class HuiYAMLEditor extends LitElement { }; } - protected render() { + protected render(): TemplateResult { return html` `; } - private _valueChanged(ev) { - this.yaml = ev.target.value; - fireEvent(this, "yaml-changed", { yaml: ev.target.value }); + private _valueChanged(ev: MouseEvent): void { + const target = ev.target! as any; + this.yaml = target.value; + fireEvent(this, "yaml-changed", { yaml: target.value }); } } diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts new file mode 100644 index 0000000000..c585cad86f --- /dev/null +++ b/src/panels/lovelace/editor/types.ts @@ -0,0 +1,12 @@ +import { LovelaceConfig } from "../types"; + +export interface YamlChangedEvent extends Event { + detail: { + yaml: string; + }; +} + +export interface ConfigValue { + format: "js" | "yaml"; + value: string | LovelaceConfig; +} diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index def084c78b..32905d0534 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -9,3 +9,8 @@ export interface LovelaceCard extends HTMLElement { getCardSize(): number; setConfig(config: LovelaceConfig): void; } + +export interface LovelaceCardEditor extends HTMLElement { + hass?: HomeAssistant; + setConfig(config: LovelaceConfig): void; +} From 6432207bf1cb651d6f7a780a1ff02094a0ef650f Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Tue, 6 Nov 2018 03:47:24 -0600 Subject: [PATCH 10/68] New Card: Shopping List (#1970) * New Card: Shopping List Following features: - Add item - Edit item - Complete item - Clear items * Address Travis complaint * Addressed review comments * Update translation variable name * Line up input row text * Taking MVP to heart Addressed review comments and scaled this back to just get a simple shopping list card out there and we can discuss/debate how best to add the additional pieces with smaller PRs * Remove calling connected in set hass --- gallery/src/data/provide_hass.js | 6 + .../src/demos/demo-hui-shopping-list-card.js | 55 ++++++ src/data/shopping-list.ts | 28 +++ .../lovelace/cards/hui-shopping-list-card.ts | 165 ++++++++++++++++++ .../lovelace/common/create-card-element.js | 2 + 5 files changed, 256 insertions(+) create mode 100644 gallery/src/demos/demo-hui-shopping-list-card.js create mode 100644 src/data/shopping-list.ts create mode 100644 src/panels/lovelace/cards/hui-shopping-list-card.ts diff --git a/gallery/src/data/provide_hass.js b/gallery/src/data/provide_hass.js index 79db10cbda..8d260a2213 100644 --- a/gallery/src/data/provide_hass.js +++ b/gallery/src/data/provide_hass.js @@ -29,6 +29,12 @@ export default (elements, { initialStates = {} } = {}) => { resources: demoResources, states: initialStates, themes: {}, + connection: { + subscribeEvents: async (callback, event) => { + console.log("subscribeEvents", event); + return () => console.log("unsubscribeEvents", event); + }, + }, // Mock properties mockEntities: entities, diff --git a/gallery/src/demos/demo-hui-shopping-list-card.js b/gallery/src/demos/demo-hui-shopping-list-card.js new file mode 100644 index 0000000000..2f485dfcc0 --- /dev/null +++ b/gallery/src/demos/demo-hui-shopping-list-card.js @@ -0,0 +1,55 @@ +import { html } from "@polymer/polymer/lib/utils/html-tag"; +import { PolymerElement } from "@polymer/polymer/polymer-element"; + +import provideHass from "../data/provide_hass"; +import "../components/demo-cards"; + +const CONFIGS = [ + { + heading: "List example", + config: ` +- type: shopping-list + `, + }, + { + heading: "List with title example", + config: ` +- type: shopping-list + title: Shopping List + `, + }, +]; + +class DemoShoppingListEntity extends PolymerElement { + static get template() { + return html` + + `; + } + + static get properties() { + return { + _configs: { + type: Object, + value: CONFIGS, + }, + }; + } + + ready() { + super.ready(); + const hass = provideHass(this.$.demos); + + hass.mockAPI("shopping_list", () => [ + { name: "list", id: 1, complete: false }, + { name: "all", id: 2, complete: false }, + { name: "the", id: 3, complete: false }, + { name: "things", id: 4, complete: true }, + ]); + } +} + +customElements.define("demo-hui-shopping-list-card", DemoShoppingListEntity); diff --git a/src/data/shopping-list.ts b/src/data/shopping-list.ts new file mode 100644 index 0000000000..9766354e21 --- /dev/null +++ b/src/data/shopping-list.ts @@ -0,0 +1,28 @@ +import { HomeAssistant } from "../types"; + +export interface ShoppingListItem { + id: number; + name: string; + complete: boolean; +} + +export const fetchItems = (hass: HomeAssistant): Promise => + hass.callApi("GET", "shopping_list"); + +export const saveEdit = ( + hass: HomeAssistant, + itemId: number, + name: string +): Promise => + hass.callApi("POST", "shopping_list/item/" + itemId, { + name, + }); + +export const completeItem = ( + hass: HomeAssistant, + itemId: number, + complete: boolean +): Promise => + hass.callApi("POST", "shopping_list/item/" + itemId, { + complete, + }); diff --git a/src/panels/lovelace/cards/hui-shopping-list-card.ts b/src/panels/lovelace/cards/hui-shopping-list-card.ts new file mode 100644 index 0000000000..ac918c856d --- /dev/null +++ b/src/panels/lovelace/cards/hui-shopping-list-card.ts @@ -0,0 +1,165 @@ +import { html, LitElement } from "@polymer/lit-element"; +import { repeat } from "lit-html/directives/repeat"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-checkbox/paper-checkbox"; +import "@polymer/paper-input/paper-input"; + +import "../../../components/ha-card"; + +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCard, LovelaceConfig } from "../types"; +import { + fetchItems, + completeItem, + saveEdit, + ShoppingListItem, +} from "../../../data/shopping-list"; + +interface Config extends LovelaceConfig { + title?: string; +} + +class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement) + implements LovelaceCard { + private _hass?: HomeAssistant; + private _config?: Config; + private _items?: ShoppingListItem[]; + private _unsubEvents?: Promise<() => Promise>; + + static get properties() { + return { + _config: {}, + _items: {}, + }; + } + + set hass(hass: HomeAssistant) { + this._hass = hass; + } + + public getCardSize(): number { + return ( + (this._config ? (this._config.title ? 1 : 0) : 0) + + (this._items ? this._items.length : 3) + ); + } + + public setConfig(config: Config): void { + this._config = config; + this._items = []; + this._fetchData(); + } + + public connectedCallback(): void { + super.connectedCallback(); + + if (this._hass) { + this._unsubEvents = this._hass.connection.subscribeEvents( + () => this._fetchData(), + "shopping_list_updated" + ); + this._fetchData(); + } + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + + if (this._unsubEvents) { + this._unsubEvents.then((unsub) => unsub()); + } + } + + protected render(): TemplateResult { + if (!this._config || !this._hass) { + return html``; + } + + return html` + ${this.renderStyle()} + + ${repeat( + this._items!, + (item) => item.id, + (item, index) => + html` +
+ + + + +
+ ` + )} +
+ `; + } + + private renderStyle(): TemplateResult { + return html` + + `; + } + + private async _fetchData(): Promise { + if (this._hass) { + this._items = await fetchItems(this._hass); + } + } + + private _completeItem(ev): void { + completeItem(this._hass!, ev.target.itemId, ev.target.checked).catch(() => + this._fetchData() + ); + } + + private _saveEdit(ev): void { + saveEdit(this._hass!, ev.target.itemId, ev.target.value).catch(() => + this._fetchData() + ); + + ev.target.blur(); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-shopping-list-card": HuiShoppingListCard; + } +} + +customElements.define("hui-shopping-list-card", HuiShoppingListCard); diff --git a/src/panels/lovelace/common/create-card-element.js b/src/panels/lovelace/common/create-card-element.js index f7cc0db4a3..c4cbc406a2 100644 --- a/src/panels/lovelace/common/create-card-element.js +++ b/src/panels/lovelace/common/create-card-element.js @@ -21,6 +21,7 @@ import "../cards/hui-picture-glance-card"; import "../cards/hui-plant-status-card"; import "../cards/hui-sensor-card"; import "../cards/hui-vertical-stack-card.ts"; +import "../cards/hui-shopping-list-card"; import "../cards/hui-thermostat-card.ts"; import "../cards/hui-weather-forecast-card"; import "../cards/hui-gauge-card"; @@ -49,6 +50,7 @@ const CARD_TYPES = new Set([ "picture-glance", "plant-status", "sensor", + "shopping-list", "thermostat", "vertical-stack", "weather-forecast", From c10e409634520e29c050cdc93e486e778ebf8bec Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Tue, 6 Nov 2018 04:07:15 -0600 Subject: [PATCH 11/68] Convert cover-row to TypeScript/LitElement (#1933) * Convert cover-row to TypeScript/LitElement * Extract `supports` methods from cover model * Address review comments * Revert line endings mixup I suck at vs code apparently... * Address review comments * Address review comments: error-row not working --- .../lovelace/common/create-row-element.js | 1 - .../entity-rows/hui-cover-entity-row.js | 74 ---------------- .../entity-rows/hui-cover-entity-row.ts | 85 +++++++++++++++++++ src/util/cover-model.js | 33 ++++++- 4 files changed, 116 insertions(+), 77 deletions(-) delete mode 100644 src/panels/lovelace/entity-rows/hui-cover-entity-row.js create mode 100644 src/panels/lovelace/entity-rows/hui-cover-entity-row.ts diff --git a/src/panels/lovelace/common/create-row-element.js b/src/panels/lovelace/common/create-row-element.js index 2d775def52..00d9d36d17 100644 --- a/src/panels/lovelace/common/create-row-element.js +++ b/src/panels/lovelace/common/create-row-element.js @@ -13,7 +13,6 @@ import "../entity-rows/hui-script-entity-row"; import "../entity-rows/hui-text-entity-row"; import "../entity-rows/hui-timer-entity-row"; import "../entity-rows/hui-toggle-entity-row"; - import "../special-rows/hui-call-service-row"; import "../special-rows/hui-divider-row"; import "../special-rows/hui-section-row"; diff --git a/src/panels/lovelace/entity-rows/hui-cover-entity-row.js b/src/panels/lovelace/entity-rows/hui-cover-entity-row.js deleted file mode 100644 index 4cdc254166..0000000000 --- a/src/panels/lovelace/entity-rows/hui-cover-entity-row.js +++ /dev/null @@ -1,74 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../components/hui-generic-entity-row"; -import "../../../components/ha-cover-controls"; -import "../../../components/ha-cover-tilt-controls"; -import CoverEntity from "../../../util/cover-model"; - -class HuiCoverEntityRow extends PolymerElement { - static get template() { - return html` - ${this.styleTemplate} - - ${this.coverControlTemplate} - - `; - } - - static get styleTemplate() { - return html` - - `; - } - - static get coverControlTemplate() { - return html` - - - `; - } - - static get properties() { - return { - hass: Object, - _config: Object, - _stateObj: { - type: Object, - computed: "_computeStateObj(hass.states, _config.entity)", - }, - _entityObj: { - type: Object, - computed: "_computeEntityObj(hass, _stateObj)", - }, - }; - } - - _computeStateObj(states, entityId) { - return states && entityId in states ? states[entityId] : null; - } - - _computeEntityObj(hass, stateObj) { - return stateObj ? new CoverEntity(hass, stateObj) : null; - } - - setConfig(config) { - if (!config || !config.entity) { - throw new Error("Entity not configured."); - } - this._config = config; - } -} -customElements.define("hui-cover-entity-row", HuiCoverEntityRow); diff --git a/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts b/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts new file mode 100644 index 0000000000..0def319cc1 --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts @@ -0,0 +1,85 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import "../components/hui-generic-entity-row.js"; +import "../../../components/ha-cover-controls.js"; +import "../../../components/ha-cover-tilt-controls.js"; + +import { isTiltOnly } from "../../../util/cover-model.js"; +import { HomeAssistant } from "../../../types.js"; +import { EntityRow, EntityConfig } from "./types.js"; + +class HuiCoverEntityRow extends LitElement implements EntityRow { + public hass?: HomeAssistant; + private _config?: EntityConfig; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + _config: {}, + }; + } + + public setConfig(config: EntityConfig): void { + if (!config) { + throw new Error("Configuration error"); + } + this._config = config; + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity]; + + if (!stateObj) { + return html` + `; + } + + return html` + ${this.renderStyle()} + + ${ + isTiltOnly(stateObj) + ? html` + ` + : html` + ` + } + + `; + } + + private renderStyle(): TemplateResult { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-cover-entity-row": HuiCoverEntityRow; + } +} + +customElements.define("hui-cover-entity-row", HuiCoverEntityRow); diff --git a/src/util/cover-model.js b/src/util/cover-model.js index 09666718a7..45de4ad911 100644 --- a/src/util/cover-model.js +++ b/src/util/cover-model.js @@ -72,9 +72,9 @@ export default class CoverEntity { } get isTiltOnly() { - var supportsCover = + const supportsCover = this.supportsOpen || this.supportsClose || this.supportsStop; - var supportsTilt = + const supportsTilt = this.supportsOpenTilt || this.supportsCloseTilt || this.supportsStopTilt; return supportsTilt && !supportsCover; } @@ -120,3 +120,32 @@ export default class CoverEntity { this.hass.callService("cover", service, data); } } + +const support = (stateObj, feature) => + (stateObj.attributes.supported_features & feature) !== 0; + +export const supportsOpen = (stateObj) => support(stateObj, 1); + +export const supportsClose = (stateObj) => support(stateObj, 2); + +export const supportsSetPosition = (stateObj) => support(stateObj, 4); + +export const supportsStop = (stateObj) => support(stateObj, 8); + +export const supportsOpenTilt = (stateObj) => support(stateObj, 16); + +export const supportsCloseTilt = (stateObj) => support(stateObj, 32); + +export const supportsStopTilt = (stateObj) => support(stateObj, 64); + +export const supportsSetTiltPosition = (stateObj) => support(stateObj, 128); + +export function isTiltOnly(stateObj) { + const supportsCover = + supportsOpen(stateObj) || supportsClose(stateObj) || supportsStop(stateObj); + const supportsTilt = + supportsOpenTilt(stateObj) || + supportsCloseTilt(stateObj) || + supportsStopTilt(stateObj); + return supportsTilt && !supportsCover; +} From 5891a6ee7dab4e84522f62906b3b8505bcb17918 Mon Sep 17 00:00:00 2001 From: Michael Scherer Date: Tue, 6 Nov 2018 11:25:25 +0100 Subject: [PATCH 12/68] Fix for thermostats without current temperature (#1979) * fix for thermostats without current temperature * make linter happy * always render value, uom only when there is a value * Update hui-thermostat-card.ts --- src/panels/lovelace/cards/hui-thermostat-card.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index b072ef5561..2619009369 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -93,12 +93,15 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
${computeStateName(stateObj)}
- ${ - stateObj.attributes.current_temperature - } - ${ - this.hass.config.unit_system.temperature - } + + ${stateObj.attributes.current_temperature} + ${ + stateObj.attributes.current_temperature + ? html`${ + this.hass.config.unit_system.temperature + }` + : "" + }
From 92af45d7fd038825d9a48f83a76540fc2cbc3973 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Nov 2018 11:51:33 +0100 Subject: [PATCH 13/68] Lint --- package.json | 2 +- src/common/datetime/format_date.ts | 2 +- src/common/datetime/format_date_time.ts | 2 +- src/common/datetime/format_time.ts | 2 +- src/common/entity/compute_state_display.ts | 2 +- src/panels/lovelace/cards/hui-glance-card.ts | 14 +++++++------- .../lovelace/editor/hui-glance-card-editor.ts | 14 +++++++------- .../lovelace/entity-rows/hui-cover-entity-row.ts | 12 ++++++------ yarn.lock | 6 +++--- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 82f4d700b1..31a58cd59c 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "@webcomponents/shadycss": "^1.5.2", "@vaadin/vaadin-overlay": "3.2.0-alpha3", "@vaadin/vaadin-lumo-styles": "1.2.0", - "fecha": "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz" + "fecha": "https://github.com/balloob/fecha/archive/5b6ee2fc9f7a0470f0ea9ed1c079cf9fa148aa25.tar.gz" }, "main": "src/home-assistant.js", "husky": { diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index 09cd0223e2..dc86b708cd 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -1,4 +1,4 @@ -import * as fecha from "fecha"; +import fecha from "fecha"; // Check for support of native locale string options function toLocaleDateStringSupportsOptions() { diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts index 3421a5d291..bbbb5520a3 100644 --- a/src/common/datetime/format_date_time.ts +++ b/src/common/datetime/format_date_time.ts @@ -1,4 +1,4 @@ -import * as fecha from "fecha"; +import fecha from "fecha"; // Check for support of native locale string options function toLocaleStringSupportsOptions() { diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index e5abe800b6..db38b0a558 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -1,4 +1,4 @@ -import * as fecha from "fecha"; +import fecha from "fecha"; // Check for support of native locale string options function toLocaleTimeStringSupportsOptions() { diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 83fb1f5578..135c1f5234 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -36,7 +36,7 @@ export default function computeStateDisplay( state._stateDisplay = state.state + " " + state.attributes.unit_of_measurement; } else if (domain === "input_datetime") { - let date; + let date: Date; if (!state.attributes.has_time) { date = new Date( state.attributes.year, diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index df27398474..83c724cf91 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -7,17 +7,17 @@ import { import { TemplateResult } from "lit-html"; import { classMap } from "lit-html/directives/classMap"; -import { fireEvent } from "../../../common/dom/fire_event.js"; +import { fireEvent } from "../../../common/dom/fire_event"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { HomeAssistant } from "../../../types.js"; -import { LovelaceCard, LovelaceConfig, LovelaceCardEditor } from "../types.js"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCard, LovelaceConfig, LovelaceCardEditor } from "../types"; import { longPress } from "../common/directives/long-press-directive"; -import computeStateDisplay from "../../../common/entity/compute_state_display.js"; -import computeStateName from "../../../common/entity/compute_state_name.js"; +import computeStateDisplay from "../../../common/entity/compute_state_display"; +import computeStateName from "../../../common/entity/compute_state_name"; import processConfigEntities from "../common/process-config-entities"; -import applyThemesOnElement from "../../../common/dom/apply_themes_on_element.js"; -import toggleEntity from "../common/entity/toggle-entity.js"; +import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; +import toggleEntity from "../common/entity/toggle-entity"; import "../../../components/entity/state-badge"; import "../../../components/ha-card"; diff --git a/src/panels/lovelace/editor/hui-glance-card-editor.ts b/src/panels/lovelace/editor/hui-glance-card-editor.ts index 328e2a7fa5..21b2a92950 100644 --- a/src/panels/lovelace/editor/hui-glance-card-editor.ts +++ b/src/panels/lovelace/editor/hui-glance-card-editor.ts @@ -1,17 +1,17 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import "@polymer/paper-checkbox/paper-checkbox.js"; +import "@polymer/paper-checkbox/paper-checkbox"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { HomeAssistant } from "../../../types.js"; -import { LovelaceCardEditor } from "../types.js"; -import { fireEvent } from "../../../common/dom/fire_event.js"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCardEditor } from "../types"; +import { fireEvent } from "../../../common/dom/fire_event"; import { Config } from "../cards/hui-glance-card"; -import "../../../components/entity/state-badge.js"; +import "../../../components/entity/state-badge"; import "../../../components/entity/ha-entity-picker"; -import "../../../components/ha-card.js"; -import "../../../components/ha-icon.js"; +import "../../../components/ha-card"; +import "../../../components/ha-icon"; export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) implements LovelaceCardEditor { diff --git a/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts b/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts index 0def319cc1..80b2ee431a 100644 --- a/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts @@ -1,13 +1,13 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import "../components/hui-generic-entity-row.js"; -import "../../../components/ha-cover-controls.js"; -import "../../../components/ha-cover-tilt-controls.js"; +import "../components/hui-generic-entity-row"; +import "../../../components/ha-cover-controls"; +import "../../../components/ha-cover-tilt-controls"; -import { isTiltOnly } from "../../../util/cover-model.js"; -import { HomeAssistant } from "../../../types.js"; -import { EntityRow, EntityConfig } from "./types.js"; +import { isTiltOnly } from "../../../util/cover-model"; +import { HomeAssistant } from "../../../types"; +import { EntityRow, EntityConfig } from "./types"; class HuiCoverEntityRow extends LitElement implements EntityRow { public hass?: HomeAssistant; diff --git a/yarn.lock b/yarn.lock index f88b1a14d0..ac098fafe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6220,9 +6220,9 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fecha@^2.3.3, "fecha@https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz": - version "2.3.3" - resolved "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz#bfb1e49121dd7821601af35faf4fe93dbd19200a" +fecha@^2.3.3, "fecha@https://github.com/balloob/fecha/archive/5b6ee2fc9f7a0470f0ea9ed1c079cf9fa148aa25.tar.gz": + version "3.0.0" + resolved "https://github.com/balloob/fecha/archive/5b6ee2fc9f7a0470f0ea9ed1c079cf9fa148aa25.tar.gz#de665a5224b24c3d2d5441affa9fd936d320b434" figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: version "3.5.1" From 54e43758d3e482e27dd8492b11ec1eede59e9231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Tue, 6 Nov 2018 12:39:00 +0100 Subject: [PATCH 14/68] Make conditional not take up space in stacks when hidden (#1999) * Make conditional not take up space in stacks when hidden * Update hui-conditional-card.ts --- src/panels/lovelace/cards/hui-conditional-card.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/panels/lovelace/cards/hui-conditional-card.ts b/src/panels/lovelace/cards/hui-conditional-card.ts index 3ad239be4f..067848eb67 100644 --- a/src/panels/lovelace/cards/hui-conditional-card.ts +++ b/src/panels/lovelace/cards/hui-conditional-card.ts @@ -67,6 +67,8 @@ class HuiConditionalCard extends HTMLElement implements LovelaceCard { } else if (this._card.parentElement) { this.removeChild(this._card); } + // This will hide the complete card so it won't get styled by parent + this.style.setProperty("display", visible ? "" : "none"); } public getCardSize() { From e2b9893b171f221fc7337ed48c797b5bac2fcc7e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Nov 2018 14:22:59 +0100 Subject: [PATCH 15/68] Expose entities for Google/Alexa (#680) * Add entity filter * Show exposed entities on cloud panel * Fix tests * Revert some testing changes * Cursor: pointer * Fix * Update tests to TS --- src/common/entity/entity_filter.ts | 64 ++++++++++ src/panels/config/cloud/cloud-alexa-pref.ts | 17 ++- .../config/cloud/cloud-exposed-entities.ts | 116 ++++++++++++++++++ src/panels/config/cloud/cloud-google-pref.ts | 19 ++- src/panels/config/cloud/types.ts | 4 +- test-mocha/common/entity/entity_filter.ts | 97 +++++++++++++++ 6 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 src/common/entity/entity_filter.ts create mode 100644 src/panels/config/cloud/cloud-exposed-entities.ts create mode 100644 test-mocha/common/entity/entity_filter.ts diff --git a/src/common/entity/entity_filter.ts b/src/common/entity/entity_filter.ts new file mode 100644 index 0000000000..6267b61f05 --- /dev/null +++ b/src/common/entity/entity_filter.ts @@ -0,0 +1,64 @@ +import computeDomain from "./compute_domain"; + +export type FilterFunc = (entityId: string) => boolean; + +export const generateFilter = ( + includeDomains?: string[], + includeEntities?: string[], + excludeDomains?: string[], + excludeEntities?: string[] +): FilterFunc => { + const includeDomainsSet = new Set(includeDomains); + const includeEntitiesSet = new Set(includeEntities); + const excludeDomainsSet = new Set(excludeDomains); + const excludeEntitiesSet = new Set(excludeEntities); + + const haveInclude = includeDomainsSet.size > 0 || includeEntitiesSet.size > 0; + const haveExclude = excludeDomainsSet.size > 0 || excludeEntitiesSet.size > 0; + + // Case 1 - no includes or excludes - pass all entities + if (!haveInclude && !haveExclude) { + return () => true; + } + + // Case 2 - includes, no excludes - only include specified entities + if (haveInclude && !haveExclude) { + return (entityId) => + includeEntitiesSet.has(entityId) || + includeDomainsSet.has(computeDomain(entityId)); + } + + // Case 3 - excludes, no includes - only exclude specified entities + if (!haveInclude && haveExclude) { + return (entityId) => + !excludeEntitiesSet.has(entityId) && + !excludeDomainsSet.has(computeDomain(entityId)); + } + + // Case 4 - both includes and excludes specified + // Case 4a - include domain specified + // - if domain is included, pass if entity not excluded + // - if domain is not included, pass if entity is included + // note: if both include and exclude domains specified, + // the exclude domains are ignored + if (includeDomainsSet.size) { + return (entityId) => + includeDomainsSet.has(computeDomain(entityId)) + ? !excludeEntitiesSet.has(entityId) + : includeEntitiesSet.has(entityId); + } + + // Case 4b - exclude domain specified + // - if domain is excluded, pass if entity is included + // - if domain is not excluded, pass if entity not excluded + if (excludeDomainsSet.size) { + return (entityId) => + excludeDomainsSet.has(computeDomain(entityId)) + ? includeEntitiesSet.has(entityId) + : !excludeEntitiesSet.has(entityId); + } + + // Case 4c - neither include or exclude domain specified + // - Only pass if entity is included. Ignore entity excludes. + return (entityId) => includeEntitiesSet.has(entityId); +}; diff --git a/src/panels/config/cloud/cloud-alexa-pref.ts b/src/panels/config/cloud/cloud-alexa-pref.ts index a076612f0d..c195a112fd 100644 --- a/src/panels/config/cloud/cloud-alexa-pref.ts +++ b/src/panels/config/cloud/cloud-alexa-pref.ts @@ -10,6 +10,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { HomeAssistant } from "../../../types"; import { updatePref } from "./data"; import { CloudStatusLoggedIn } from "./types"; +import "./cloud-exposed-entities"; export class CloudAlexaPref extends LitElement { public hass?: HomeAssistant; @@ -23,11 +24,13 @@ export class CloudAlexaPref extends LitElement { } protected render(): TemplateResult { + const enabled = this.cloudStatus!.alexa_enabled; + return html` ${this.renderStyle()}
@@ -43,6 +46,18 @@ export class CloudAlexaPref extends LitElement { This integration requires an Alexa-enabled device like the Amazon Echo. + ${ + enabled + ? html` +

Exposed entities:

+ + ` + : "" + }
`; diff --git a/src/panels/config/cloud/cloud-exposed-entities.ts b/src/panels/config/cloud/cloud-exposed-entities.ts new file mode 100644 index 0000000000..bf8fab8084 --- /dev/null +++ b/src/panels/config/cloud/cloud-exposed-entities.ts @@ -0,0 +1,116 @@ +import { + html, + LitElement, + PropertyDeclarations, + PropertyValues, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import { repeat } from "lit-html/directives/repeat"; +import "@polymer/paper-tooltip/paper-tooltip"; +import { HassEntityBase } from "home-assistant-js-websocket"; +import "../../../components/entity/ha-state-icon"; + +import { fireEvent } from "../../../common/dom/fire_event"; +import { HomeAssistant } from "../../../types"; +import { EntityFilter } from "./types"; +import computeStateName from "../../../common/entity/compute_state_name"; +import { + FilterFunc, + generateFilter, +} from "../../../common/entity/entity_filter"; + +export class CloudExposedEntities extends LitElement { + public hass?: HomeAssistant; + public filter?: EntityFilter; + public supportedDomains?: string[]; + private _filterFunc?: FilterFunc; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + filter: {}, + supportedDomains: {}, + _filterFunc: {}, + }; + } + + protected render(): TemplateResult { + if (!this._filterFunc) { + return html``; + } + + const states: Array<[string, HassEntityBase]> = []; + + Object.keys(this.hass!.states).forEach((entityId) => { + if (this._filterFunc!(entityId)) { + const stateObj = this.hass!.states[entityId]; + states.push([computeStateName(stateObj), stateObj]); + } + }); + states.sort(); + + return html` + ${this.renderStyle()} + ${repeat( + states!, + (stateInfo) => stateInfo[1].entity_id, + (stateInfo) => html` + + + ${stateInfo[0]} + + ` + )} + `; + } + + protected updated(changedProperties: PropertyValues) { + if ( + changedProperties.has("filter") && + changedProperties.get("filter") !== this.filter + ) { + const filter = this.filter!; + const filterFunc = generateFilter( + filter.include_domains, + filter.include_entities, + filter.exclude_domains, + filter.exclude_entities + ); + const domains = new Set(this.supportedDomains); + this._filterFunc = (entityId: string) => { + const domain = entityId.split(".")[0]; + return domains.has(domain) && filterFunc(entityId); + }; + } + } + + private _handleMoreInfo(ev: MouseEvent) { + fireEvent(this, "hass-more-info", { + entityId: (ev.currentTarget as any).stateObj.entity_id, + }); + } + + private renderStyle(): TemplateResult { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-exposed-entities": CloudExposedEntities; + } +} + +customElements.define("cloud-exposed-entities", CloudExposedEntities); diff --git a/src/panels/config/cloud/cloud-google-pref.ts b/src/panels/config/cloud/cloud-google-pref.ts index 19aaf7445d..7ab042e728 100644 --- a/src/panels/config/cloud/cloud-google-pref.ts +++ b/src/panels/config/cloud/cloud-google-pref.ts @@ -11,6 +11,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { HomeAssistant } from "../../../types"; import { updatePref } from "./data"; import { CloudStatusLoggedIn } from "./types"; +import "./cloud-exposed-entities"; export class CloudGooglePref extends LitElement { public hass?: HomeAssistant; @@ -24,11 +25,13 @@ export class CloudGooglePref extends LitElement { } protected render(): TemplateResult { + const enabled = this.cloudStatus!.google_enabled; + return html` ${this.renderStyle()}
@@ -46,11 +49,23 @@ export class CloudGooglePref extends LitElement { This integration requires a Google Assistant-enabled device like the Google Home or Android phone. + ${ + enabled + ? html` +

Exposed entities:

+ + ` + : "" + }
Sync devices
diff --git a/src/panels/config/cloud/types.ts b/src/panels/config/cloud/types.ts index 98d8ff668e..4783d1776c 100644 --- a/src/panels/config/cloud/types.ts +++ b/src/panels/config/cloud/types.ts @@ -1,4 +1,4 @@ -interface EntityFilter { +export interface EntityFilter { include_domains: string[]; include_entities: string[]; exclude_domains: string[]; @@ -13,8 +13,10 @@ export type CloudStatusLoggedIn = CloudStatusBase & { email: string; google_enabled: boolean; google_entities: EntityFilter; + google_domains: string[]; alexa_enabled: boolean; alexa_entities: EntityFilter; + alexa_domains: string[]; }; export type CloudStatus = CloudStatusBase | CloudStatusLoggedIn; diff --git a/test-mocha/common/entity/entity_filter.ts b/test-mocha/common/entity/entity_filter.ts new file mode 100644 index 0000000000..d236a78037 --- /dev/null +++ b/test-mocha/common/entity/entity_filter.ts @@ -0,0 +1,97 @@ +import { generateFilter } from "../../../src/common/entity/entity_filter"; + +import * as assert from "assert"; + +describe("EntityFilter", () => { + // case 1 + it("passes all when no filters passed in", () => { + const filter = generateFilter(); + + assert(filter("sensor.test")); + assert(filter("sun.sun")); + assert(filter("light.test")); + }); + + // case 2 + it("allows whitelisting entities by entity id", () => { + const filter = generateFilter(undefined, ["light.kitchen"]); + + assert(filter("light.kitchen")); + assert(!filter("light.living_room")); + }); + + it("allows whitelisting entities by domain", () => { + const filter = generateFilter(["switch"]); + + assert(filter("switch.bla")); + assert(!filter("light.kitchen")); + }); + + // case 3 + it("allows blacklisting entities by entity id", () => { + const filter = generateFilter(undefined, undefined, undefined, [ + "light.kitchen", + ]); + + assert(!filter("light.kitchen")); + assert(filter("light.living_room")); + }); + + it("allows blacklisting entities by domain", () => { + const filter = generateFilter(undefined, undefined, ["switch"]); + + assert(!filter("switch.bla")); + assert(filter("light.kitchen")); + }); + + // case 4a + it("allows whitelisting domain and blacklisting entity", () => { + const filter = generateFilter(["switch"], undefined, undefined, [ + "switch.kitchen", + ]); + + assert(filter("switch.living_room")); + assert(!filter("switch.kitchen")); + assert(!filter("sensor.bla")); + }); + + it("allows whitelisting entity while whitelisting other domains", () => { + const filter = generateFilter(["switch"], ["light.kitchen"]); + + assert(filter("switch.living_room")); + assert(filter("light.kitchen")); + assert(!filter("sensor.bla")); + }); + + // case 4b + it("allows blacklisting domain and whitelisting entity", () => { + const filter = generateFilter(undefined, ["switch.kitchen"], ["switch"]); + + assert(filter("switch.kitchen")); + assert(!filter("switch.living_room")); + assert(filter("sensor.bla")); + }); + + it("allows blacklisting domain and excluding entities", () => { + const filter = generateFilter( + undefined, + undefined, + ["switch"], + ["light.kitchen"] + ); + + assert(!filter("switch.living_room")); + assert(!filter("light.kitchen")); + assert(filter("sensor.bla")); + }); + + // case 4c + it("allows whitelisting entities", () => { + const filter = generateFilter(undefined, ["light.kitchen"]); + + assert(filter("light.kitchen")); + assert(!filter("switch.living_room")); + assert(!filter("light.living_room")); + assert(!filter("sensor.bla")); + }); +}); From 14b959b91b6e3dd5fc8447d242e72ee62b7473d9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Nov 2018 15:17:01 +0100 Subject: [PATCH 16/68] Fix cover showing error --- src/common/entity/valid_entity_id.ts | 2 +- src/panels/lovelace/entity-rows/hui-cover-entity-row.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/entity/valid_entity_id.ts b/src/common/entity/valid_entity_id.ts index 29d345c633..45888dab44 100644 --- a/src/common/entity/valid_entity_id.ts +++ b/src/common/entity/valid_entity_id.ts @@ -1,2 +1,2 @@ const validEntityId = /^(\w+)\.(\w+)$/; -export default validEntityId.test; +export default (entityId: string) => validEntityId.test(entityId); diff --git a/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts b/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts index 80b2ee431a..ce430041f6 100644 --- a/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-cover-entity-row.ts @@ -4,6 +4,7 @@ import { TemplateResult } from "lit-html"; import "../components/hui-generic-entity-row"; import "../../../components/ha-cover-controls"; import "../../../components/ha-cover-tilt-controls"; +import "./hui-error-entity-row"; import { isTiltOnly } from "../../../util/cover-model"; import { HomeAssistant } from "../../../types"; From 9ce74e2da1c36af55c0acdbdf7120353270abb1e Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Wed, 7 Nov 2018 02:30:05 -0600 Subject: [PATCH 17/68] Convert hui-toggle-entity-row to TypeScript/LitElement (#1939) * Convert hui-toggle-entity-row to TypeScript/LitElement * Properly set generic-entity properties * Addressed review comments * Address review comments --- .../entity-rows/hui-toggle-entity-row.js | 76 ------------------ .../entity-rows/hui-toggle-entity-row.ts | 78 +++++++++++++++++++ 2 files changed, 78 insertions(+), 76 deletions(-) delete mode 100644 src/panels/lovelace/entity-rows/hui-toggle-entity-row.js create mode 100644 src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts diff --git a/src/panels/lovelace/entity-rows/hui-toggle-entity-row.js b/src/panels/lovelace/entity-rows/hui-toggle-entity-row.js deleted file mode 100644 index 531462ff3b..0000000000 --- a/src/panels/lovelace/entity-rows/hui-toggle-entity-row.js +++ /dev/null @@ -1,76 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../components/hui-generic-entity-row"; -import "../../../components/entity/ha-entity-toggle"; - -import computeStateDisplay from "../../../common/entity/compute_state_display"; - -import LocalizeMixin from "../../../mixins/localize-mixin"; - -/* - * @appliesMixin LocalizeMixin - */ -class HuiToggleEntityRow extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - ${this.toggleControlTemplate} - - `; - } - - static get toggleControlTemplate() { - return html` - - - `; - } - - static get properties() { - return { - hass: Object, - _config: Object, - _stateObj: { - type: Object, - computed: "_computeStateObj(hass.states, _config.entity)", - }, - _canToggle: { - type: Boolean, - computed: "_computeCanToggle(_stateObj.state)", - }, - }; - } - - _computeStateObj(states, entityId) { - return states && entityId in states ? states[entityId] : null; - } - - _computeCanToggle(state) { - return state === "on" || state === "off"; - } - - _computeState(stateObj) { - return stateObj && computeStateDisplay(this.localize, stateObj); - } - - setConfig(config) { - if (!config || !config.entity) { - throw new Error("Entity not configured."); - } - this._config = config; - } -} -customElements.define("hui-toggle-entity-row", HuiToggleEntityRow); diff --git a/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts b/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts new file mode 100644 index 0000000000..b986c313b5 --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts @@ -0,0 +1,78 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import "../components/hui-generic-entity-row"; +import "../../../components/entity/ha-entity-toggle"; +import "./hui-error-entity-row"; + +import computeStateDisplay from "../../../common/entity/compute_state_display"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../types"; +import { EntityRow, EntityConfig } from "./types"; + +class HuiToggleEntityRow extends hassLocalizeLitMixin(LitElement) + implements EntityRow { + public hass?: HomeAssistant; + private _config?: EntityConfig; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + _config: {}, + }; + } + + public setConfig(config: EntityConfig): void { + if (!config) { + throw new Error("Configuration error"); + } + this._config = config; + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity]; + + if (!stateObj) { + return html` + `; + } + + return html` + + ${ + stateObj.state === "on" || stateObj.state === "off" + ? html` + ` + : html` +
+ ${computeStateDisplay( + this.localize, + stateObj, + this.hass!.language + )} +
` + } +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-toggle-entity-row": HuiToggleEntityRow; + } +} + +customElements.define("hui-toggle-entity-row", HuiToggleEntityRow); From f9719957b0b262cbcce2a86467758ad13630108b Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Wed, 7 Nov 2018 02:31:01 -0600 Subject: [PATCH 18/68] Convert call-service to TypeScript (#1985) * Convert call-service to TypeScript * Address Travis error * Address review comments --- src/panels/lovelace/common/call-service.js | 10 ---------- src/panels/lovelace/common/call-service.ts | 12 ++++++++++++ src/panels/lovelace/entity-rows/types.ts | 6 ++---- .../lovelace/special-rows/hui-call-service-row.ts | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 src/panels/lovelace/common/call-service.js create mode 100644 src/panels/lovelace/common/call-service.ts diff --git a/src/panels/lovelace/common/call-service.js b/src/panels/lovelace/common/call-service.js deleted file mode 100644 index 21c2b8e273..0000000000 --- a/src/panels/lovelace/common/call-service.js +++ /dev/null @@ -1,10 +0,0 @@ -export default function callService(config, hass) { - const entityId = config.entity; - const [domain, service] = config.service.split(".", 2); - const serviceData = Object.assign( - {}, - { entity_id: entityId }, - config.service_data - ); - hass.callService(domain, service, serviceData); -} diff --git a/src/panels/lovelace/common/call-service.ts b/src/panels/lovelace/common/call-service.ts new file mode 100644 index 0000000000..de3b39984b --- /dev/null +++ b/src/panels/lovelace/common/call-service.ts @@ -0,0 +1,12 @@ +import { HomeAssistant } from "../../../types"; +import { CallServiceConfig } from "../entity-rows/types"; + +export const callService = ( + config: CallServiceConfig, + hass: HomeAssistant +): void => { + const entityId = config.entity; + const [domain, service] = config.service.split(".", 2); + const serviceData = { entity_id: entityId, ...config.service_data }; + hass.callService(domain, service, serviceData); +}; diff --git a/src/panels/lovelace/entity-rows/types.ts b/src/panels/lovelace/entity-rows/types.ts index 648b9cf9cb..b2c7d272cf 100644 --- a/src/panels/lovelace/entity-rows/types.ts +++ b/src/panels/lovelace/entity-rows/types.ts @@ -16,12 +16,10 @@ export interface WeblinkConfig { icon?: string; url: string; } -export interface CallServiceConfig { - name: string; - icon?: string; +export interface CallServiceConfig extends EntityConfig { action_name?: string; service: string; - service_data?: string; + service_data?: { [key: string]: any }; } export type EntityRowConfig = | EntityConfig diff --git a/src/panels/lovelace/special-rows/hui-call-service-row.ts b/src/panels/lovelace/special-rows/hui-call-service-row.ts index 4ac0d03ff4..b0aa0222ba 100644 --- a/src/panels/lovelace/special-rows/hui-call-service-row.ts +++ b/src/panels/lovelace/special-rows/hui-call-service-row.ts @@ -3,7 +3,7 @@ import "@polymer/paper-button/paper-button"; import "../../../components/ha-icon"; -import callService from "../common/call-service"; +import { callService } from "../common/call-service"; import { EntityRow, CallServiceConfig } from "../entity-rows/types"; import { HomeAssistant } from "../../../types"; import { TemplateResult } from "lit-html"; @@ -80,7 +80,7 @@ class HuiCallServiceRow extends LitElement implements EntityRow { } private _callService() { - callService(this._config, this.hass); + callService(this._config!, this.hass!); } } From 7c2135f444d5e34fb322f95b39b6a3df078643a3 Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Wed, 7 Nov 2018 02:42:46 -0600 Subject: [PATCH 19/68] Fix iframe aspect_ratio (#2004) --- src/panels/lovelace/cards/hui-iframe-card.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/cards/hui-iframe-card.ts b/src/panels/lovelace/cards/hui-iframe-card.ts index 13827cc89e..a8089344d7 100644 --- a/src/panels/lovelace/cards/hui-iframe-card.ts +++ b/src/panels/lovelace/cards/hui-iframe-card.ts @@ -4,6 +4,7 @@ import "../../../components/ha-card"; import { LovelaceCard, LovelaceConfig } from "../types"; import { TemplateResult } from "lit-html"; +import { styleMap } from "lit-html/directives/styleMap"; interface Config extends LovelaceConfig { aspect_ratio?: string; @@ -37,10 +38,16 @@ export class HuiIframeCard extends LitElement implements LovelaceCard { return html``; } + const aspectRatio = this._config.aspect_ratio || "50%"; + return html` ${this.renderStyle()} -
+
From a58a3240738929cf8b915e638fd16a2a6a1f298f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 7 Nov 2018 09:56:43 +0100 Subject: [PATCH 20/68] Format html (#2006) * Upgrade prettier * Format files with prettier --- gallery/src/components/demo-card.js | 4 +- gallery/src/components/demo-cards.js | 18 +- gallery/src/components/demo-more-info.js | 90 +-- gallery/src/components/demo-more-infos.js | 18 +- .../src/demos/demo-hui-alarm-panel-card.js | 6 +- .../src/demos/demo-hui-conditional-card.js | 4 +- gallery/src/demos/demo-hui-entities-card.js | 5 +- .../src/demos/demo-hui-entity-button-card.js | 6 +- .../src/demos/demo-hui-entity-filter-card.js | 5 +- gallery/src/demos/demo-hui-glance-card.js | 5 +- gallery/src/demos/demo-hui-light-card.js | 2 +- .../src/demos/demo-hui-media-player-rows.js | 4 +- .../demos/demo-hui-picture-elements-card.js | 5 +- .../src/demos/demo-hui-shopping-list-card.js | 5 +- gallery/src/demos/demo-hui-stack-card.js | 5 +- gallery/src/demos/demo-hui-thermostat-card.js | 5 +- gallery/src/demos/demo-more-info-light.js | 4 +- .../addon-store/hassio-addon-repository.js | 68 ++- hassio/src/addon-store/hassio-addon-store.js | 27 +- .../addon-store/hassio-repositories-editor.js | 97 +-- hassio/src/addon-view/hassio-addon-audio.js | 96 +-- hassio/src/addon-view/hassio-addon-config.js | 83 +-- hassio/src/addon-view/hassio-addon-info.js | 317 ++++++---- hassio/src/addon-view/hassio-addon-logs.js | 34 +- hassio/src/addon-view/hassio-addon-network.js | 97 +-- hassio/src/addon-view/hassio-addon-view.js | 143 +++-- hassio/src/components/hassio-card-content.js | 102 ++-- hassio/src/dashboard/hassio-addons.js | 60 +- hassio/src/dashboard/hassio-dashboard.js | 26 +- hassio/src/dashboard/hassio-hass-update.js | 86 +-- hassio/src/hassio-app.js | 13 +- hassio/src/hassio-main.js | 52 +- hassio/src/hassio-markdown-dialog.js | 93 +-- hassio/src/hassio-pages-with-tabs.js | 122 ++-- hassio/src/snapshots/hassio-snapshot.js | 199 ++++--- hassio/src/snapshots/hassio-snapshots.js | 186 +++--- hassio/src/system/hassio-host-info.js | 178 +++--- hassio/src/system/hassio-supervisor-info.js | 147 +++-- hassio/src/system/hassio-supervisor-log.js | 32 +- hassio/src/system/hassio-system.js | 46 +- src/auth/ha-auth-flow.js | 95 +-- src/auth/ha-authorize.js | 76 +-- src/auth/ha-pick-auth-provider.js | 32 +- src/cards/ha-badges-card.js | 23 +- src/cards/ha-camera-card.js | 88 +-- src/cards/ha-entities-card.js | 102 ++-- src/cards/ha-history_graph-card.js | 83 +-- src/cards/ha-media_player-card.js | 297 +++++----- src/cards/ha-persistent_notification-card.js | 68 +-- src/cards/ha-plant-card.js | 153 ++--- src/cards/ha-weather-card.js | 45 +- src/common/entity/attribute_class_names.ts | 5 +- src/common/entity/feature_class_names.ts | 7 +- src/components/buttons/ha-call-api-button.js | 3 +- .../buttons/ha-call-service-button.js | 9 +- src/components/buttons/ha-progress-button.js | 86 +-- src/components/entity/ha-chart-base.js | 232 ++++---- src/components/entity/ha-entity-picker.js | 114 ++-- src/components/entity/ha-entity-toggle.js | 68 ++- src/components/entity/ha-state-icon.js | 4 +- src/components/entity/ha-state-label-badge.js | 74 ++- src/components/entity/state-badge.js | 70 +-- src/components/entity/state-info.js | 96 +-- src/components/ha-attributes.js | 46 +- src/components/ha-card.js | 40 +- src/components/ha-cards.js | 113 ++-- src/components/ha-climate-control.js | 76 +-- src/components/ha-climate-state.js | 62 +- src/components/ha-color-picker.js | 188 +++--- src/components/ha-combo-box.js | 79 ++- src/components/ha-cover-controls.js | 42 +- src/components/ha-cover-tilt-controls.js | 43 +- src/components/ha-demo-badge.js | 18 +- src/components/ha-form.js | 223 ++++--- src/components/ha-label-badge.js | 172 +++--- src/components/ha-labeled-slider.js | 62 +- src/components/ha-menu-button.js | 10 +- .../ha-push-notifications-toggle.js | 10 +- src/components/ha-service-description.js | 4 +- src/components/ha-service-picker.js | 9 +- src/components/ha-start-voice-button.js | 8 +- src/components/ha-textarea.js | 5 +- src/components/ha-vacuum-state.js | 9 +- src/components/ha-water_heater-control.js | 76 +-- src/components/ha-water_heater-state.js | 58 +- src/components/paper-time-input.js | 62 +- src/components/state-history-chart-line.js | 25 +- .../state-history-chart-timeline.js | 28 +- src/components/state-history-charts.js | 90 +-- src/dialogs/ha-more-info-dialog.js | 125 ++-- src/dialogs/ha-store-auth-card.js | 16 +- src/dialogs/ha-voice-command-dialog.js | 220 +++---- .../controls/more-info-alarm_control_panel.js | 108 +++- .../controls/more-info-automation.js | 9 +- .../more-info/controls/more-info-camera.js | 25 +- .../more-info/controls/more-info-climate.js | 382 +++++++----- .../controls/more-info-configurator.js | 113 ++-- .../more-info/controls/more-info-cover.js | 88 +-- .../more-info/controls/more-info-default.js | 4 +- .../more-info/controls/more-info-fan.js | 112 ++-- .../more-info/controls/more-info-group.js | 33 +- .../controls/more-info-history_graph.js | 24 +- .../controls/more-info-input_datetime.js | 35 +- .../more-info/controls/more-info-light.js | 197 +++--- .../more-info/controls/more-info-lock.js | 26 +- .../controls/more-info-media_player.js | 251 +++++--- .../more-info/controls/more-info-script.js | 14 +- .../more-info/controls/more-info-sun.js | 28 +- .../more-info/controls/more-info-updater.js | 23 +- .../more-info/controls/more-info-vacuum.js | 176 ++++-- .../controls/more-info-water_heater.js | 170 +++--- .../more-info/controls/more-info-weather.js | 179 +++--- src/dialogs/more-info/more-info-controls.js | 125 ++-- src/dialogs/more-info/more-info-settings.js | 80 +-- src/layouts/app/home-assistant.js | 30 +- src/layouts/ha-app-layout.js | 119 ++-- src/layouts/ha-init-page.js | 36 +- src/layouts/hass-error-screen.js | 42 +- src/layouts/hass-loading-screen.js | 37 +- src/layouts/hass-subpage.js | 27 +- src/layouts/home-assistant-main.js | 99 +++- src/layouts/partial-cards.js | 161 +++-- src/layouts/partial-panel-resolver.js | 46 +- src/managers/notification-manager.js | 20 +- src/panels/calendar/ha-big-calendar.js | 8 +- src/panels/calendar/ha-panel-calendar.js | 27 +- .../config/automation/ha-automation-editor.js | 148 ++--- .../config/automation/ha-automation-picker.js | 133 +++-- .../config/automation/ha-config-automation.js | 50 +- src/panels/config/cloud/cloud-alexa-pref.ts | 30 +- .../config/cloud/cloud-exposed-entities.ts | 30 +- src/panels/config/cloud/cloud-google-pref.ts | 36 +- .../config/cloud/ha-config-cloud-account.js | 195 +++--- .../cloud/ha-config-cloud-forgot-password.js | 107 ++-- .../config/cloud/ha-config-cloud-login.js | 220 ++++--- src/panels/config/cloud/ha-config-cloud.js | 87 +-- .../config-entries/ha-ce-entities-card.js | 50 +- .../ha-config-entries-dashboard.js | 191 +++--- .../config-entries/ha-config-entries.js | 47 +- .../config-entries/ha-config-entry-page.js | 101 ++-- .../config/config-entries/ha-config-flow.js | 186 +++--- .../config/config-entries/ha-device-card.js | 145 ++--- src/panels/config/core/ha-config-core.js | 41 +- .../config/core/ha-config-section-core.js | 210 ++++--- .../config/customize/ha-config-customize.js | 54 +- .../customize/ha-customize-attribute.js | 36 +- .../customize/ha-form-customize-attributes.js | 20 +- .../config/customize/ha-form-customize.js | 122 ++-- .../customize/types/ha-customize-array.js | 34 +- .../customize/types/ha-customize-boolean.js | 8 +- .../customize/types/ha-customize-icon.js | 34 +- .../customize/types/ha-customize-key-value.js | 41 +- .../customize/types/ha-customize-string.js | 10 +- .../config/dashboard/ha-config-navigation.js | 44 +- src/panels/config/ha-config-section.js | 102 ++-- src/panels/config/ha-entity-config.js | 126 ++-- src/panels/config/ha-panel-config.js | 200 ++++--- src/panels/config/script/ha-config-script.js | 50 +- src/panels/config/script/ha-script-editor.js | 148 ++--- src/panels/config/script/ha-script-picker.js | 126 ++-- src/panels/config/users/ha-config-users.js | 40 +- src/panels/config/users/ha-dialog-add-user.js | 117 ++-- src/panels/config/users/ha-user-editor.js | 95 +-- src/panels/config/users/ha-user-picker.js | 96 +-- src/panels/config/zwave/ha-config-zwave.js | 559 ++++++++++-------- src/panels/config/zwave/zwave-groups.js | 165 +++--- src/panels/config/zwave/zwave-network.js | 328 +++++----- src/panels/config/zwave/zwave-node-config.js | 239 +++++--- src/panels/config/zwave/zwave-usercodes.js | 71 ++- src/panels/config/zwave/zwave-values.js | 75 +-- src/panels/dev-event/events-list.js | 45 +- src/panels/dev-event/ha-panel-dev-event.js | 96 +-- src/panels/dev-info/ha-loaded-components.js | 32 +- src/panels/dev-mqtt/ha-panel-dev-mqtt.js | 104 ++-- .../dev-service/ha-panel-dev-service.js | 274 +++++---- src/panels/dev-state/ha-panel-dev-state.js | 256 ++++---- .../dev-template/ha-panel-dev-template.js | 171 +++--- src/panels/history/ha-panel-history.js | 132 +++-- src/panels/iframe/ha-panel-iframe.js | 37 +- src/panels/kiosk/ha-panel-kiosk.js | 14 +- src/panels/logbook/ha-logbook.js | 95 +-- src/panels/logbook/ha-panel-logbook.js | 199 ++++--- .../lovelace/cards/hui-entities-card.ts | 42 +- .../lovelace/cards/hui-entity-button-card.ts | 114 ++-- src/panels/lovelace/cards/hui-error-card.ts | 3 +- src/panels/lovelace/cards/hui-gauge-card.ts | 80 +-- src/panels/lovelace/cards/hui-glance-card.ts | 47 +- .../lovelace/cards/hui-history-graph-card.js | 2 +- src/panels/lovelace/cards/hui-iframe-card.ts | 11 +- src/panels/lovelace/cards/hui-light-card.ts | 115 ++-- src/panels/lovelace/cards/hui-map-card.js | 1 - .../lovelace/cards/hui-markdown-card.ts | 8 +- src/panels/lovelace/cards/hui-picture-card.js | 7 +- .../cards/hui-picture-elements-card.ts | 9 +- .../lovelace/cards/hui-picture-entity-card.js | 12 +- .../lovelace/cards/hui-picture-glance-card.js | 16 +- src/panels/lovelace/cards/hui-sensor-card.js | 34 +- .../lovelace/cards/hui-shopping-list-card.ts | 52 +- src/panels/lovelace/cards/hui-stack-card.ts | 4 +- .../lovelace/cards/hui-thermostat-card.ts | 309 +++++----- .../lovelace/components/hui-card-options.ts | 36 +- .../components/hui-entities-toggle.js | 35 +- .../components/hui-generic-entity-row.js | 21 +- src/panels/lovelace/components/hui-image.js | 15 +- .../hui-configurator-notification-item.js | 16 +- .../hui-notification-item-template.js | 50 +- .../notifications/hui-notifications-button.js | 46 +- .../hui-persistent-notification-item.js | 56 +- .../lovelace/editor/hui-dialog-edit-card.ts | 28 +- .../lovelace/editor/hui-glance-card-editor.ts | 15 +- src/panels/lovelace/editor/hui-yaml-editor.ts | 2 +- .../lovelace/elements/hui-image-element.ts | 10 +- .../elements/hui-service-button-element.ts | 3 +- .../elements/hui-state-badge-element.ts | 4 +- .../elements/hui-state-icon-element.ts | 2 +- .../elements/hui-state-label-element.ts | 10 +- .../entity-rows/hui-climate-entity-row.ts | 9 +- .../entity-rows/hui-cover-entity-row.ts | 40 +- .../entity-rows/hui-error-entity-row.ts | 3 +- .../entity-rows/hui-group-entity-row.js | 9 +- .../hui-input-number-entity-row.js | 14 +- .../hui-input-select-entity-row.js | 15 +- .../entity-rows/hui-input-text-entity-row.js | 5 +- .../entity-rows/hui-lock-entity-row.js | 7 +- .../hui-media-player-entity-row.js | 4 +- .../entity-rows/hui-scene-entity-row.js | 7 +- .../entity-rows/hui-script-entity-row.js | 16 +- .../entity-rows/hui-text-entity-row.js | 9 +- .../entity-rows/hui-timer-entity-row.js | 9 +- .../entity-rows/hui-toggle-entity-row.ts | 36 +- src/panels/lovelace/ha-panel-lovelace.js | 26 +- src/panels/lovelace/hui-view.js | 82 +-- .../special-rows/hui-call-service-row.ts | 12 +- .../lovelace/special-rows/hui-section-row.ts | 6 +- .../mailbox/ha-dialog-show-audio-message.js | 107 ++-- src/panels/mailbox/ha-panel-mailbox.js | 184 +++--- src/panels/map/ha-entity-marker.js | 63 +- src/panels/map/ha-panel-map.js | 27 +- src/panels/profile/ha-change-password-card.js | 127 ++-- .../ha-long-lived-access-tokens-card.js | 86 +-- .../profile/ha-mfa-module-setup-flow.js | 180 +++--- src/panels/profile/ha-mfa-modules-card.js | 87 +-- src/panels/profile/ha-panel-profile.js | 145 ++--- src/panels/profile/ha-pick-language-row.js | 51 +- src/panels/profile/ha-pick-theme-row.js | 54 +- .../profile/ha-push-notifications-row.js | 40 +- src/panels/profile/ha-refresh-tokens-card.js | 70 +-- src/panels/profile/ha-settings-row.js | 46 +- .../shopping-list/ha-panel-shopping-list.js | 204 +++---- src/resources/html-import/polyfill.js | 4 +- src/resources/jquery.roundslider.js | 6 +- src/state-summary/state-card-climate.js | 47 +- src/state-summary/state-card-configurator.js | 52 +- src/state-summary/state-card-cover.js | 46 +- src/state-summary/state-card-display.js | 65 +- src/state-summary/state-card-input_number.js | 105 ++-- src/state-summary/state-card-input_select.js | 61 +- src/state-summary/state-card-input_text.js | 47 +- src/state-summary/state-card-lock.js | 54 +- src/state-summary/state-card-media_player.js | 76 +-- src/state-summary/state-card-scene.js | 44 +- src/state-summary/state-card-script.js | 63 +- src/state-summary/state-card-timer.js | 42 +- src/state-summary/state-card-toggle.js | 39 +- src/state-summary/state-card-vacuum.js | 27 +- src/state-summary/state-card-water_heater.js | 47 +- src/state-summary/state-card-weblink.js | 38 +- yarn.lock | 6 +- 268 files changed, 10821 insertions(+), 8543 deletions(-) diff --git a/gallery/src/components/demo-card.js b/gallery/src/components/demo-card.js index 8b3c816480..6cbbad8067 100644 --- a/gallery/src/components/demo-card.js +++ b/gallery/src/components/demo-card.js @@ -38,9 +38,9 @@ class DemoCard extends PolymerElement { }

[[config.heading]]

-
+
-