diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2a6ed52bff..a293b99f4a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -41,7 +41,32 @@ Provide details about what browser (and version) you are seeing the issue in. An **Description of problem:** + +**Expected behaviour:** + + + +**Relevant config:** + + + +**Steps to reproduce this problem:** + + **Javascript errors shown in the web inspector (if applicable):** diff --git a/README.md b/README.md index e46baf12e3..8138804665 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ This is the repository for the official [Home Assistant](https://home-assistant.io) frontend. -[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://home-assistant.io/demo/) +[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://demo.home-assistant.io/) -- [View demo of the Polymer frontend](https://home-assistant.io/demo/) +- [View demo of Home Assistant](https://demo.home-assistant.io/) - [More information about Home Assistant](https://home-assistant.io) - [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html) @@ -31,3 +31,5 @@ It is possible to compile the project and/or run commands in the development env ## License Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects. + +We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variation of devices. diff --git a/build-scripts/babel.js b/build-scripts/babel.js index 67021b92c4..f17e55b584 100644 --- a/build-scripts/babel.js +++ b/build-scripts/babel.js @@ -33,6 +33,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => { pragma: "h", }, ], + "@babel/plugin-proposal-optional-chaining", [ require("@babel/plugin-proposal-decorators").default, { decoratorsBeforeExport: true }, diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index ccb20f5aa5..93d91f2375 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -175,9 +175,9 @@ export class HcMain extends HassElement { } catch (err) { // Generate a Lovelace config. this._unsubLovelace = () => undefined; - const { - generateLovelaceConfigFromHass, - } = await import("../../../../src/panels/lovelace/common/generate-lovelace-config"); + const { generateLovelaceConfigFromHass } = await import( + "../../../../src/panels/lovelace/common/generate-lovelace-config" + ); this._handleNewLovelaceConfig( await generateLovelaceConfigFromHass(this.hass!) ); diff --git a/demo/src/custom-cards/card-modder.js b/demo/src/custom-cards/card-modder.js index 82f0411115..aaec2b7322 100644 --- a/demo/src/custom-cards/card-modder.js +++ b/demo/src/custom-cards/card-modder.js @@ -53,7 +53,7 @@ class CardModder extends LitElement { for (var k in this._config.style) { if (window.cardTools.hasTemplate(this._config.style[k])) this.templated.push(k); - this.card.style.setProperty(k, ''); + this.card.style.setProperty(k, ""); target.style.setProperty( k, window.cardTools.parseTemplate(this._config.style[k]) diff --git a/demo/src/entrypoint.ts b/demo/src/entrypoint.ts index 456a9923ed..bacc0978a8 100644 --- a/demo/src/entrypoint.ts +++ b/demo/src/entrypoint.ts @@ -12,5 +12,7 @@ import "./resources/hademo-icons"; /* polyfill for paper-dropdown */ setTimeout(() => { - import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"); + import( + /* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min" + ); }, 1000); diff --git a/demo/src/stubs/history.ts b/demo/src/stubs/history.ts index c0364760a8..84ef96619a 100644 --- a/demo/src/stubs/history.ts +++ b/demo/src/stubs/history.ts @@ -65,74 +65,79 @@ const generateHistory = (state, deltas) => { const incrementalUnits = ["clients", "queries", "ads"]; export const mockHistory = (mockHass: MockHomeAssistant) => { - mockHass.mockAPI(new RegExp("history/period/.+"), ( - hass, - // @ts-ignore - method, - path, - // @ts-ignore - parameters - ) => { - const params = parseQuery(path.split("?")[1]); - const entities = params.filter_entity_id.split(","); + mockHass.mockAPI( + new RegExp("history/period/.+"), + ( + hass, + // @ts-ignore + method, + path, + // @ts-ignore + parameters + ) => { + const params = parseQuery(path.split("?")[1]); + const entities = params.filter_entity_id.split(","); - const results: HassEntity[][] = []; + const results: HassEntity[][] = []; - for (const entityId of entities) { - const state = hass.states[entityId]; + for (const entityId of entities) { + const state = hass.states[entityId]; - if (!state) { - continue; - } + if (!state) { + continue; + } - if (!state.attributes.unit_of_measurement) { - results.push(generateHistory(state, [state.state])); - continue; - } + if (!state.attributes.unit_of_measurement) { + results.push(generateHistory(state, [state.state])); + continue; + } - const numberState = Number(state.state); + const numberState = Number(state.state); - if (isNaN(numberState)) { - // tslint:disable-next-line - console.log( - "Ignoring state with unparsable state but with a unit", - entityId, - state + if (isNaN(numberState)) { + // tslint:disable-next-line + console.log( + "Ignoring state with unparsable state but with a unit", + entityId, + state + ); + continue; + } + + const statesToGenerate = 15; + let genFunc; + + if (incrementalUnits.includes(state.attributes.unit_of_measurement)) { + let initial = Math.floor( + numberState * 0.4 + numberState * Math.random() * 0.2 + ); + const diff = Math.max( + 1, + Math.floor((numberState - initial) / statesToGenerate) + ); + genFunc = () => { + initial += diff; + return Math.min(numberState, initial); + }; + } else { + const diff = Math.floor( + numberState * (numberState > 80 ? 0.05 : 0.5) + ); + genFunc = () => + numberState - diff + Math.floor(Math.random() * 2 * diff); + } + + results.push( + generateHistory( + { + entity_id: state.entity_id, + attributes: state.attributes, + }, + Array.from({ length: statesToGenerate }, genFunc) + ) ); - continue; } - - const statesToGenerate = 15; - let genFunc; - - if (incrementalUnits.includes(state.attributes.unit_of_measurement)) { - let initial = Math.floor( - numberState * 0.4 + numberState * Math.random() * 0.2 - ); - const diff = Math.max( - 1, - Math.floor((numberState - initial) / statesToGenerate) - ); - genFunc = () => { - initial += diff; - return Math.min(numberState, initial); - }; - } else { - const diff = Math.floor(numberState * (numberState > 80 ? 0.05 : 0.5)); - genFunc = () => - numberState - diff + Math.floor(Math.random() * 2 * diff); - } - - results.push( - generateHistory( - { - entity_id: state.entity_id, - attributes: state.attributes, - }, - Array.from({ length: statesToGenerate }, genFunc) - ) - ); + return results; } - return results; - }); + ); }; diff --git a/demo/src/stubs/lovelace.ts b/demo/src/stubs/lovelace.ts index 0170e272f2..138a15aff4 100644 --- a/demo/src/stubs/lovelace.ts +++ b/demo/src/stubs/lovelace.ts @@ -12,9 +12,10 @@ export const mockLovelace = ( localizePromise: Promise ) => { hass.mockWS("lovelace/config", () => - Promise.all([selectedDemoConfig, localizePromise]).then( - ([config, localize]) => config.lovelace(localize) - ) + Promise.all([ + selectedDemoConfig, + localizePromise, + ]).then(([config, localize]) => config.lovelace(localize)) ); hass.mockWS("lovelace/config/save", () => Promise.resolve()); diff --git a/hassio/src/addon-view/hassio-addon-audio.js b/hassio/src/addon-view/hassio-addon-audio.js index 96275cd107..84ec925d80 100644 --- a/hassio/src/addon-view/hassio-addon-audio.js +++ b/hassio/src/addon-view/hassio-addon-audio.js @@ -44,9 +44,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) { selected="{{selectedInput}}" > @@ -57,9 +55,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) { selected="{{selectedOutput}}" > diff --git a/hassio/src/addon-view/hassio-addon-info.js b/hassio/src/addon-view/hassio-addon-info.js index 10df924f6e..449b85a8cc 100644 --- a/hassio/src/addon-view/hassio-addon-info.js +++ b/hassio/src/addon-view/hassio-addon-info.js @@ -569,7 +569,10 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) { openChangelog() { this.hass .callApi("get", `hassio/addons/${this.addonSlug}/changelog`) - .then((resp) => resp, () => "Error getting changelog") + .then( + (resp) => resp, + () => "Error getting changelog" + ) .then((content) => { showHassioMarkdownDialog(this, { title: "Changelog", diff --git a/hassio/src/dashboard/hassio-update.ts b/hassio/src/dashboard/hassio-update.ts index 3414c5fdf7..78fc65cc32 100644 --- a/hassio/src/dashboard/hassio-update.ts +++ b/hassio/src/dashboard/hassio-update.ts @@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement { this.supervisorInfo.version, this.supervisorInfo.last_version, "hassio/supervisor/update", - `https://github.com//home-assistant/hassio/releases/tag/${ - this.supervisorInfo.last_version - }` + `https://github.com//home-assistant/hassio/releases/tag/${this.supervisorInfo.last_version}` )} ${this.hassOsInfo ? this._renderUpdateCard( @@ -84,9 +82,7 @@ export class HassioUpdate extends LitElement { this.hassOsInfo.version, this.hassOsInfo.version_latest, "hassio/hassos/update", - `https://github.com//home-assistant/hassos/releases/tag/${ - this.hassOsInfo.version_latest - }` + `https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}` ) : ""} diff --git a/hassio/src/dialogs/markdown/show-dialog-hassio-markdown.ts b/hassio/src/dialogs/markdown/show-dialog-hassio-markdown.ts index c3e9713689..80cadefbd2 100644 --- a/hassio/src/dialogs/markdown/show-dialog-hassio-markdown.ts +++ b/hassio/src/dialogs/markdown/show-dialog-hassio-markdown.ts @@ -12,7 +12,9 @@ export const showHassioMarkdownDialog = ( fireEvent(element, "show-dialog", { dialogTag: "dialog-hassio-markdown", dialogImport: () => - import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"), + import( + /* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown" + ), dialogParams, }); }; diff --git a/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts index 0823fb9f59..478f295b7d 100644 --- a/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts @@ -12,7 +12,9 @@ export const showHassioSnapshotDialog = ( fireEvent(element, "show-dialog", { dialogTag: "dialog-hassio-snapshot", dialogImport: () => - import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"), + import( + /* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot" + ), dialogParams, }); }; diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 33bf94e573..1b1257b06c 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -56,12 +56,16 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) { addon: { tag: "hassio-addon-view", load: () => - import(/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"), + import( + /* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view" + ), }, ingress: { tag: "hassio-ingress-view", load: () => - import(/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"), + import( + /* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view" + ), }, }, }; diff --git a/package.json b/package.json index 8a8f1244d7..9f111f9e4e 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "dependencies": { "@material/chips": "^3.2.0", "@material/data-table": "^3.2.0", - "@material/mwc-base": "^0.8.0", - "@material/mwc-button": "^0.8.0", - "@material/mwc-checkbox": "^0.8.0", - "@material/mwc-fab": "^0.8.0", - "@material/mwc-ripple": "^0.8.0", - "@material/mwc-switch": "^0.8.0", - "@mdi/svg": "4.5.95", + "@material/mwc-base": "^0.10.0", + "@material/mwc-button": "^0.10.0", + "@material/mwc-checkbox": "^0.10.0", + "@material/mwc-dialog": "^0.10.0", + "@material/mwc-fab": "^0.10.0", + "@material/mwc-ripple": "^0.10.0", + "@material/mwc-switch": "^0.10.0", + "@mdi/svg": "4.6.95", "@polymer/app-layout": "^3.0.2", "@polymer/app-localize-behavior": "^3.0.1", "@polymer/app-route": "^3.0.2", @@ -68,8 +69,8 @@ "@polymer/paper-tooltip": "^3.0.1", "@polymer/polymer": "3.1.0", "@thomasloven/round-slider": "0.3.7", - "@vaadin/vaadin-combo-box": "^4.2.8", - "@vaadin/vaadin-date-picker": "^3.3.3", + "@vaadin/vaadin-combo-box": "^5.0.6", + "@vaadin/vaadin-date-picker": "^4.0.3", "@webcomponents/shadycss": "^1.9.0", "@webcomponents/webcomponentsjs": "^2.2.7", "chart.js": "~2.8.0", @@ -98,21 +99,23 @@ "regenerator-runtime": "^0.13.2", "roboto-fontface": "^0.10.0", "superstruct": "^0.6.1", + "copy-to-clipboard": "^1.0.9", "tslib": "^1.10.0", "unfetch": "^4.1.0", "web-animations-js": "^2.3.1", "xss": "^1.0.6" }, "devDependencies": { - "@babel/core": "^7.4.0", - "@babel/plugin-external-helpers": "^7.2.0", - "@babel/plugin-proposal-class-properties": "^7.4.0", - "@babel/plugin-proposal-decorators": "^7.4.0", - "@babel/plugin-proposal-object-rest-spread": "^7.4.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@babel/preset-env": "^7.4.2", - "@babel/preset-typescript": "^7.4.0", + "@babel/core": "^7.7.4", + "@babel/plugin-external-helpers": "^7.7.4", + "@babel/plugin-proposal-class-properties": "^7.7.4", + "@babel/plugin-proposal-decorators": "^7.7.4", + "@babel/plugin-proposal-object-rest-spread": "^7.7.4", + "@babel/plugin-proposal-optional-chaining": "^7.7.4", + "@babel/plugin-syntax-dynamic-import": "^7.7.4", + "@babel/plugin-transform-react-jsx": "^7.7.4", + "@babel/preset-env": "^7.7.4", + "@babel/preset-typescript": "^7.7.4", "@types/chai": "^4.1.7", "@types/chromecast-caf-receiver": "^3.0.12", "@types/chromecast-caf-sender": "^1.0.1", @@ -154,18 +157,18 @@ "merge-stream": "^1.0.1", "mocha": "^6.0.2", "parse5": "^5.1.0", - "prettier": "^1.16.4", + "prettier": "^1.19.1", "raw-loader": "^2.0.0", "reify": "^0.18.1", "require-dir": "^1.2.0", "sinon": "^7.3.1", "terser-webpack-plugin": "^1.2.3", "ts-mocha": "^6.0.0", - "tslint": "^5.14.0", + "tslint": "^5.20.1", "tslint-config-prettier": "^1.18.0", "tslint-eslint-rules": "^5.4.0", "tslint-plugin-prettier": "^2.0.1", - "typescript": "^3.6.3", + "typescript": "^3.7.2", "web-component-tester": "^6.9.2", "webpack": "^4.40.2", "webpack-cli": "^3.3.9", @@ -178,7 +181,6 @@ "_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569", "resolutions": { "@webcomponents/webcomponentsjs": "^2.2.10", - "@vaadin/vaadin-lumo-styles": "^1.4.2", "@polymer/polymer": "3.1.0", "lit-html": "^1.1.2" }, diff --git a/setup.py b/setup.py index 10db22ecb7..b5f35ecfab 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20191119.6", + version="20191204.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts index afebe018be..c13df3d769 100644 --- a/src/auth/ha-auth-flow.ts +++ b/src/auth/ha-auth-flow.ts @@ -98,9 +98,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { `; @@ -229,9 +227,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { } private _computeStepDescription(step: DataEntryFlowStepForm) { - const resourceKey = `ui.panel.page-authorize.form.providers.${ - step.handler[0] - }.step.${step.step_id}.description`; + const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`; const args: string[] = []; const placeholders = step.description_placeholders || {}; Object.keys(placeholders).forEach((key) => { @@ -245,9 +241,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { // Returns a callback for ha-form to calculate labels per schema object return (schema) => this.localize( - `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${ - step.step_id - }.data.${schema.name}` + `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.data.${schema.name}` ); } @@ -255,9 +249,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { // Returns a callback for ha-form to calculate error messages return (error) => this.localize( - `ui.panel.page-authorize.form.providers.${ - step.handler[0] - }.error.${error}` + `ui.panel.page-authorize.form.providers.${step.handler[0]}.error.${error}` ); } diff --git a/src/auth/ha-authorize.ts b/src/auth/ha-authorize.ts index d5ba17aa2d..e026a69bf0 100644 --- a/src/auth/ha-authorize.ts +++ b/src/auth/ha-authorize.ts @@ -11,7 +11,9 @@ import "./ha-auth-flow"; import { AuthProvider, fetchAuthProviders } from "../data/auth"; import { registerServiceWorker } from "../util/register-service-worker"; -import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider"); +import( + /* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider" +); interface QueryParams { client_id?: string; diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index dc86b708cd..813500ff4a 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -10,11 +10,11 @@ function toLocaleDateStringSupportsOptions() { return false; } -export default (toLocaleDateStringSupportsOptions() +export default toLocaleDateStringSupportsOptions() ? (dateObj: Date, locales: string) => dateObj.toLocaleDateString(locales, { year: "numeric", month: "long", day: "numeric", }) - : (dateObj: Date) => fecha.format(dateObj, "mediumDate")); + : (dateObj: Date) => fecha.format(dateObj, "mediumDate"); diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts index bbbb5520a3..e8a02139d2 100644 --- a/src/common/datetime/format_date_time.ts +++ b/src/common/datetime/format_date_time.ts @@ -10,7 +10,7 @@ function toLocaleStringSupportsOptions() { return false; } -export default (toLocaleStringSupportsOptions() +export default toLocaleStringSupportsOptions() ? (dateObj: Date, locales: string) => dateObj.toLocaleString(locales, { year: "numeric", @@ -19,4 +19,4 @@ export default (toLocaleStringSupportsOptions() hour: "numeric", minute: "2-digit", }) - : (dateObj: Date) => fecha.format(dateObj, "haDateTime")); + : (dateObj: Date) => fecha.format(dateObj, "haDateTime"); diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index db38b0a558..2b747723a7 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -10,10 +10,10 @@ function toLocaleTimeStringSupportsOptions() { return false; } -export default (toLocaleTimeStringSupportsOptions() +export default toLocaleTimeStringSupportsOptions() ? (dateObj: Date, locales: string) => dateObj.toLocaleTimeString(locales, { hour: "numeric", minute: "2-digit", }) - : (dateObj: Date) => fecha.format(dateObj, "shortTime")); + : (dateObj: Date) => fecha.format(dateObj, "shortTime"); diff --git a/src/common/dom/apply_themes_on_element.ts b/src/common/dom/apply_themes_on_element.ts index 5d5222ca7c..0e43f30bb1 100644 --- a/src/common/dom/apply_themes_on_element.ts +++ b/src/common/dom/apply_themes_on_element.ts @@ -60,7 +60,7 @@ export const applyThemesOnElement = ( element.updateStyles(styles); } else if (window.ShadyCSS) { // implement updateStyles() method of Polymer elements - window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ (element), styles); + window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles); } if (!updateMeta) { diff --git a/src/common/dom/dynamic-content-directive.ts b/src/common/dom/dynamic-content-directive.ts new file mode 100644 index 0000000000..31a8a1ead7 --- /dev/null +++ b/src/common/dom/dynamic-content-directive.ts @@ -0,0 +1,29 @@ +import { directive, Part, NodePart } from "lit-html"; + +export const dynamicContentDirective = directive( + (tag: string, properties: { [key: string]: any }) => (part: Part): void => { + if (!(part instanceof NodePart)) { + throw new Error( + "dynamicContentDirective can only be used in content bindings" + ); + } + + let element = part.value as HTMLElement | undefined; + + if ( + element !== undefined && + tag.toUpperCase() === (element as HTMLElement).tagName + ) { + Object.entries(properties).forEach(([key, value]) => { + element![key] = value; + }); + return; + } + + element = document.createElement(tag); + Object.entries(properties).forEach(([key, value]) => { + element![key] = value; + }); + part.setValue(element); + } +); diff --git a/src/common/dom/setup-leaflet-map.ts b/src/common/dom/setup-leaflet-map.ts index d0532721a6..abf9008295 100644 --- a/src/common/dom/setup-leaflet-map.ts +++ b/src/common/dom/setup-leaflet-map.ts @@ -11,7 +11,9 @@ export const setupLeafletMap = async ( throw new Error("Cannot setup Leaflet map on disconnected element"); } // tslint:disable-next-line - const Leaflet = (await import(/* webpackChunkName: "leaflet" */ "leaflet")) as LeafletModuleType; + const Leaflet = (await import( + /* webpackChunkName: "leaflet" */ "leaflet" + )) as LeafletModuleType; Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/"; const map = Leaflet.map(mapElement); diff --git a/src/common/preact/event.ts b/src/common/preact/event.ts index 7e4ec871e9..a35d1969e4 100644 --- a/src/common/preact/event.ts +++ b/src/common/preact/event.ts @@ -7,8 +7,11 @@ // export function onChangeEvent(this: OnChangeComponent, prop, ev) { export function onChangeEvent(this: any, prop, ev) { - const origData = this.props[prop]; + if (!this.initialized) { + return; + } + const origData = this.props[prop]; if (ev.target.value === origData[ev.target.name]) { return; } diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 0fa519b4d9..ea7f0e59ac 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -6,8 +6,9 @@ import { MDCDataTableFoundation, } from "@material/data-table"; +import { classMap } from "lit-html/directives/class-map"; + import { - BaseElement, html, query, queryAll, @@ -15,10 +16,11 @@ import { css, customElement, property, - classMap, TemplateResult, PropertyValues, -} from "@material/mwc-base/base-element"; +} from "lit-element"; + +import { BaseElement } from "@material/mwc-base/base-element"; // eslint-disable-next-line import/no-webpack-loader-syntax // @ts-ignore diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts new file mode 100644 index 0000000000..c3a2396fa6 --- /dev/null +++ b/src/components/device/ha-area-devices-picker.ts @@ -0,0 +1,412 @@ +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-item-body"; +import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@polymer/paper-listbox/paper-listbox"; +import memoizeOne from "memoize-one"; +import { + LitElement, + TemplateResult, + html, + css, + CSSResult, + customElement, + property, + PropertyValues, +} from "lit-element"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { SubscribeMixin } from "../../mixins/subscribe-mixin"; +import "./ha-devices-picker"; + +import { HomeAssistant } from "../../types"; +import { fireEvent } from "../../common/dom/fire_event"; +import { + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../../data/device_registry"; +import { compare } from "../../common/string/compare"; +import { PolymerChangedEvent } from "../../polymer-types"; +import { + AreaRegistryEntry, + subscribeAreaRegistry, +} from "../../data/area_registry"; +import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../data/entity_registry"; +import { computeDomain } from "../../common/entity/compute_domain"; + +interface DevicesByArea { + [areaId: string]: AreaDevices; +} + +interface AreaDevices { + id?: string; + name: string; + devices: string[]; +} + +const rowRenderer = ( + root: HTMLElement, + _owner, + model: { item: AreaDevices } +) => { + if (!root.firstElementChild) { + root.innerHTML = ` + + + +
[[item.name]]
+
[[item.devices.length]] devices
+
+
+ `; + } + root.querySelector(".name")!.textContent = model.item.name!; + root.querySelector( + "[secondary]" + )!.textContent = `${model.item.devices.length.toString()} devices`; +}; + +@customElement("ha-area-devices-picker") +export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { + @property() public hass!: HomeAssistant; + @property() public label?: string; + @property() public value?: string; + @property() public area?: string; + @property() public devices?: string[]; + /** + * Show only devices with entities from specific domains. + * @type {Array} + * @attr include-domains + */ + @property({ type: Array, attribute: "include-domains" }) + public includeDomains?: string[]; + /** + * Show no devices with entities of these domains. + * @type {Array} + * @attr exclude-domains + */ + @property({ type: Array, attribute: "exclude-domains" }) + public excludeDomains?: string[]; + /** + * Show only deviced with entities of these device classes. + * @type {Array} + * @attr include-device-classes + */ + @property({ type: Array, attribute: "include-device-classes" }) + public includeDeviceClasses?: string[]; + @property({ type: Boolean }) + private _opened?: boolean; + @property() private _areaPicker = true; + @property() private _devices?: DeviceRegistryEntry[]; + @property() private _areas?: AreaRegistryEntry[]; + @property() private _entities?: EntityRegistryEntry[]; + private _selectedDevices: string[] = []; + private _filteredDevices: DeviceRegistryEntry[] = []; + + private _getDevices = memoizeOne( + ( + devices: DeviceRegistryEntry[], + areas: AreaRegistryEntry[], + entities: EntityRegistryEntry[], + includeDomains: this["includeDomains"], + excludeDomains: this["excludeDomains"], + includeDeviceClasses: this["includeDeviceClasses"] + ): AreaDevices[] => { + if (!devices.length) { + return []; + } + + const deviceEntityLookup: DeviceEntityLookup = {}; + for (const entity of entities) { + if (!entity.device_id) { + continue; + } + if (!(entity.device_id in deviceEntityLookup)) { + deviceEntityLookup[entity.device_id] = []; + } + deviceEntityLookup[entity.device_id].push(entity); + } + + let inputDevices = [...devices]; + + if (includeDomains) { + inputDevices = inputDevices.filter((device) => { + const devEntities = deviceEntityLookup[device.id]; + if (!devEntities || !devEntities.length) { + return false; + } + return deviceEntityLookup[device.id].some((entity) => + includeDomains.includes(computeDomain(entity.entity_id)) + ); + }); + } + + if (excludeDomains) { + inputDevices = inputDevices.filter((device) => { + const devEntities = deviceEntityLookup[device.id]; + if (!devEntities || !devEntities.length) { + return true; + } + return entities.every( + (entity) => + !excludeDomains.includes(computeDomain(entity.entity_id)) + ); + }); + } + + if (includeDeviceClasses) { + inputDevices = inputDevices.filter((device) => { + const devEntities = deviceEntityLookup[device.id]; + if (!devEntities || !devEntities.length) { + return false; + } + return deviceEntityLookup[device.id].some((entity) => { + const stateObj = this.hass.states[entity.entity_id]; + if (!stateObj) { + return false; + } + return ( + stateObj.attributes.device_class && + includeDeviceClasses.includes(stateObj.attributes.device_class) + ); + }); + }); + } + + this._filteredDevices = inputDevices; + + const areaLookup: { [areaId: string]: AreaRegistryEntry } = {}; + for (const area of areas) { + areaLookup[area.area_id] = area; + } + + const devicesByArea: DevicesByArea = {}; + + for (const device of inputDevices) { + const areaId = device.area_id; + if (areaId) { + if (!(areaId in devicesByArea)) { + devicesByArea[areaId] = { + id: areaId, + name: areaLookup[areaId].name, + devices: [], + }; + } + devicesByArea[areaId].devices.push(device.id); + } + } + + const sorted = Object.keys(devicesByArea) + .sort((a, b) => + compare(devicesByArea[a].name || "", devicesByArea[b].name || "") + ) + .map((key) => devicesByArea[key]); + + return sorted; + } + ); + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeDeviceRegistry(this.hass.connection!, (devices) => { + this._devices = devices; + }), + subscribeAreaRegistry(this.hass.connection!, (areas) => { + this._areas = areas; + }), + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entities = entities; + }), + ]; + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("area") && this.area) { + this._areaPicker = true; + this.value = this.area; + } else if (changedProps.has("devices") && this.devices) { + this._areaPicker = false; + const filteredDeviceIds = this._filteredDevices.map( + (device) => device.id + ); + const selectedDevices = this.devices.filter((device) => + filteredDeviceIds.includes(device) + ); + this._setValue(selectedDevices); + } + } + + protected render(): TemplateResult | void { + if (!this._devices || !this._areas || !this._entities) { + return; + } + const areas = this._getDevices( + this._devices, + this._areas, + this._entities, + this.includeDomains, + this.excludeDomains, + this.includeDeviceClasses + ); + if (!this._areaPicker || areas.length === 0) { + return html` + + ${areas.length > 0 + ? html` + Choose an area + ` + : ""} + `; + } + return html` + + + ${this.value + ? html` + + Clear + + ` + : ""} + ${areas.length > 0 + ? html` + + Toggle + + ` + : ""} + + + Choose individual devices + `; + } + + private _clearValue(ev: Event) { + ev.stopPropagation(); + this._setValue([]); + } + + private get _value() { + return this.value || []; + } + + private _openedChanged(ev: PolymerChangedEvent) { + this._opened = ev.detail.value; + } + + private async _switchPicker() { + this._areaPicker = !this._areaPicker; + } + + private async _areaPicked(ev: PolymerChangedEvent) { + const value = ev.detail.value; + let selectedDevices = []; + const target = ev.target as any; + if (target.selectedItem) { + selectedDevices = target.selectedItem.devices; + } + + if (value !== this._value || this._selectedDevices !== selectedDevices) { + this._setValue(selectedDevices, value); + } + } + + private _devicesPicked(ev: CustomEvent) { + ev.stopPropagation(); + const selectedDevices = ev.detail.value; + this._setValue(selectedDevices); + } + + private _setValue(selectedDevices: string[], value = "") { + this.value = value; + this._selectedDevices = selectedDevices; + setTimeout(() => { + fireEvent(this, "value-changed", { value: selectedDevices }); + fireEvent(this, "change"); + }, 0); + } + + static get styles(): CSSResult { + return css` + paper-input > paper-icon-button { + width: 24px; + height: 24px; + padding: 2px; + color: var(--secondary-text-color); + } + [hidden] { + display: none; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-area-devices-picker": HaAreaDevicesPicker; + } +} diff --git a/src/components/device/ha-device-automation-picker.ts b/src/components/device/ha-device-automation-picker.ts index 98c5ce492a..34eacfaa25 100644 --- a/src/components/device/ha-device-automation-picker.ts +++ b/src/components/device/ha-device-automation-picker.ts @@ -176,6 +176,7 @@ export abstract class HaDeviceAutomationPicker< this.value = automation; setTimeout(() => { fireEvent(this, "change"); + fireEvent(this, "value-changed", { value: automation }); }, 0); } diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index 10ebaecb07..d7e0c15073 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -1,7 +1,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; -import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; +import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; import "@polymer/paper-listbox/paper-listbox"; import memoizeOne from "memoize-one"; import { diff --git a/src/components/device/ha-devices-picker.ts b/src/components/device/ha-devices-picker.ts new file mode 100644 index 0000000000..638b29e27d --- /dev/null +++ b/src/components/device/ha-devices-picker.ts @@ -0,0 +1,127 @@ +import { + LitElement, + TemplateResult, + property, + html, + customElement, +} from "lit-element"; +import "@polymer/paper-icon-button/paper-icon-button-light"; + +import { HomeAssistant } from "../../types"; +import { PolymerChangedEvent } from "../../polymer-types"; +import { fireEvent } from "../../common/dom/fire_event"; + +import "./ha-device-picker"; + +@customElement("ha-devices-picker") +class HaDevicesPicker extends LitElement { + @property() public hass?: HomeAssistant; + @property() public value?: string[]; + /** + * Show entities from specific domains. + * @type {string} + * @attr include-domains + */ + @property({ type: Array, attribute: "include-domains" }) + public includeDomains?: string[]; + /** + * Show no entities of these domains. + * @type {Array} + * @attr exclude-domains + */ + @property({ type: Array, attribute: "exclude-domains" }) + public excludeDomains?: string[]; + @property({ attribute: "picked-device-label" }) + @property({ type: Array, attribute: "include-device-classes" }) + public includeDeviceClasses?: string[]; + public pickedDeviceLabel?: string; + @property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string; + + protected render(): TemplateResult | void { + if (!this.hass) { + return; + } + + const currentDevices = this._currentDevices; + return html` + ${currentDevices.map( + (entityId) => html` +
+ +
+ ` + )} +
+ +
+ `; + } + + private get _currentDevices() { + return this.value || []; + } + + private async _updateDevices(devices) { + fireEvent(this, "value-changed", { + value: devices, + }); + + this.value = devices; + } + + private _deviceChanged(event: PolymerChangedEvent) { + event.stopPropagation(); + const curValue = (event.currentTarget as any).curValue; + const newValue = event.detail.value; + if (newValue === curValue || newValue !== "") { + return; + } + if (newValue === "") { + this._updateDevices( + this._currentDevices.filter((dev) => dev !== curValue) + ); + } else { + this._updateDevices( + this._currentDevices.map((dev) => (dev === curValue ? newValue : dev)) + ); + } + } + + private async _addDevice(event: PolymerChangedEvent) { + event.stopPropagation(); + const toAdd = event.detail.value; + (event.currentTarget as any).value = ""; + if (!toAdd) { + return; + } + const currentDevices = this._currentDevices; + if (currentDevices.includes(toAdd)) { + return; + } + + this._updateDevices([...currentDevices, toAdd]); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-devices-picker": HaDevicesPicker; + } +} diff --git a/src/components/entity/ha-chart-base.js b/src/components/entity/ha-chart-base.js index 2621feee3e..37eea60bea 100644 --- a/src/components/entity/ha-chart-base.js +++ b/src/components/entity/ha-chart-base.js @@ -215,7 +215,9 @@ class HaChartBase extends mixinBehaviors( } if (scriptsLoaded === null) { - scriptsLoaded = import(/* webpackChunkName: "load_chart" */ "../../resources/ha-chart-scripts.js"); + scriptsLoaded = import( + /* webpackChunkName: "load_chart" */ "../../resources/ha-chart-scripts.js" + ); } scriptsLoaded.then((ChartModule) => { this.ChartClass = ChartModule.default; diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index c2f250887e..84dec91a87 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -2,7 +2,7 @@ import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; -import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; +import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; import memoizeOne from "memoize-one"; import "./state-badge"; diff --git a/src/components/ha-camera-stream.ts b/src/components/ha-camera-stream.ts index e91b63cf8b..1faea13c46 100644 --- a/src/components/ha-camera-stream.ts +++ b/src/components/ha-camera-stream.ts @@ -122,8 +122,9 @@ class HaCameraStream extends LitElement { private async _startHls(): Promise { // tslint:disable-next-line - const Hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any) - .default as HLSModule; + const Hls = ((await import( + /* webpackChunkName: "hls.js" */ "hls.js" + )) as any).default as HLSModule; let hlsSupported = Hls.isSupported(); const videoEl = this._videoEl; diff --git a/src/components/ha-climate-state.js b/src/components/ha-climate-state.js index 569014fcc8..9586157d48 100644 --- a/src/components/ha-climate-state.js +++ b/src/components/ha-climate-state.js @@ -72,9 +72,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) { computeCurrentStatus(hass, stateObj) { if (!hass || !stateObj) return null; if (stateObj.attributes.current_temperature != null) { - return `${stateObj.attributes.current_temperature} ${ - hass.config.unit_system.temperature - }`; + return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`; } if (stateObj.attributes.current_humidity != null) { return `${stateObj.attributes.current_humidity} %`; @@ -89,22 +87,16 @@ class HaClimateState extends LocalizeMixin(PolymerElement) { stateObj.attributes.target_temp_low != null && stateObj.attributes.target_temp_high != null ) { - return `${stateObj.attributes.target_temp_low}-${ - stateObj.attributes.target_temp_high - } ${hass.config.unit_system.temperature}`; + return `${stateObj.attributes.target_temp_low}-${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`; } if (stateObj.attributes.temperature != null) { - return `${stateObj.attributes.temperature} ${ - hass.config.unit_system.temperature - }`; + return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`; } if ( stateObj.attributes.target_humidity_low != null && stateObj.attributes.target_humidity_high != null ) { - return `${stateObj.attributes.target_humidity_low}-${ - stateObj.attributes.target_humidity_high - }%`; + return `${stateObj.attributes.target_humidity_low}-${stateObj.attributes.target_humidity_high}%`; } if (stateObj.attributes.humidity != null) { return `${stateObj.attributes.humidity} %`; @@ -121,9 +113,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) { const stateString = localize(`state.climate.${stateObj.state}`); return stateObj.attributes.hvac_action ? `${localize( - `state_attributes.climate.hvac_action.${ - stateObj.attributes.hvac_action - }` + `state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}` )} (${stateString})` : stateString; } diff --git a/src/components/ha-combo-box.js b/src/components/ha-combo-box.js index 8c554c470e..c2a7e34e42 100644 --- a/src/components/ha-combo-box.js +++ b/src/components/ha-combo-box.js @@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; +import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; import { EventsMixin } from "../mixins/events-mixin"; diff --git a/src/components/ha-fab.ts b/src/components/ha-fab.ts index 2003ac0c41..a2d0103b7f 100644 --- a/src/components/ha-fab.ts +++ b/src/components/ha-fab.ts @@ -1,4 +1,5 @@ -import { classMap, html, customElement } from "@material/mwc-base/base-element"; +import { classMap } from "lit-html/directives/class-map"; +import { html, customElement } from "lit-element"; import { ripple } from "@material/mwc-ripple/ripple-directive.js"; import "@material/mwc-fab"; diff --git a/src/components/ha-water_heater-state.js b/src/components/ha-water_heater-state.js index 42e8f09669..82c523614e 100644 --- a/src/components/ha-water_heater-state.js +++ b/src/components/ha-water_heater-state.js @@ -58,14 +58,10 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) { stateObj.attributes.target_temp_low != null && stateObj.attributes.target_temp_high != null ) { - return `${stateObj.attributes.target_temp_low} - ${ - stateObj.attributes.target_temp_high - } ${hass.config.unit_system.temperature}`; + return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`; } if (stateObj.attributes.temperature != null) { - return `${stateObj.attributes.temperature} ${ - hass.config.unit_system.temperature - }`; + return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`; } return ""; diff --git a/src/components/ha-yaml-editor.ts b/src/components/ha-yaml-editor.ts new file mode 100644 index 0000000000..dc3a0cb08e --- /dev/null +++ b/src/components/ha-yaml-editor.ts @@ -0,0 +1,81 @@ +import { safeDump, safeLoad } from "js-yaml"; +import "./ha-code-editor"; +import { LitElement, property, customElement, html } from "lit-element"; +import { fireEvent } from "../common/dom/fire_event"; + +const isEmpty = (obj: object) => { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + return false; + } + } + return true; +}; + +@customElement("ha-yaml-editor") +export class HaYamlEditor extends LitElement { + @property() public value?: any; + @property() public isValid = true; + @property() public label?: string; + @property() private _yaml?: string; + + protected firstUpdated() { + try { + this._yaml = + this.value && !isEmpty(this.value) ? safeDump(this.value) : ""; + } catch (err) { + alert(`There was an error converting to YAML: ${err}`); + } + } + + protected render() { + if (this._yaml === undefined) { + return; + } + return html` + ${this.label + ? html` +

${this.label}

+ ` + : ""} + + `; + } + + private _onChange(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value; + let parsed; + let isValid = true; + + if (value) { + try { + parsed = safeLoad(value); + isValid = true; + } catch (err) { + // Invalid YAML + isValid = false; + } + } else { + parsed = {}; + } + + this.value = parsed; + this.isValid = isValid; + + if (isValid) { + fireEvent(this, "value-changed", { value: parsed }); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-yaml-editor": HaYamlEditor; + } +} diff --git a/src/data/camera.ts b/src/data/camera.ts index 94c5853ee5..6118745cf6 100644 --- a/src/data/camera.ts +++ b/src/data/camera.ts @@ -19,9 +19,7 @@ export interface Stream { } export const computeMJPEGStreamUrl = (entity: CameraEntity) => - `/api/camera_proxy_stream/${entity.entity_id}?token=${ - entity.attributes.access_token - }`; + `/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`; export const fetchThumbnailUrlWithCache = ( hass: HomeAssistant, diff --git a/src/data/conversation.ts b/src/data/conversation.ts index 4a4c629d92..6f57fe5cb2 100644 --- a/src/data/conversation.ts +++ b/src/data/conversation.ts @@ -3,7 +3,7 @@ import { HomeAssistant } from "../types"; interface ProcessResults { card: { [key: string]: { [key: string]: string } }; speech: { - [SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string } + [SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string }; }; } diff --git a/src/data/device_automation.ts b/src/data/device_automation.ts index 58be904d62..bc58f1b00a 100644 --- a/src/data/device_automation.ts +++ b/src/data/device_automation.ts @@ -18,7 +18,7 @@ export interface DeviceCondition extends DeviceAutomation { } export interface DeviceTrigger extends DeviceAutomation { - platform: string; + platform: "device"; } export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) => @@ -107,9 +107,7 @@ export const localizeDeviceAutomationAction = ( state ? computeStateName(state) : "", "subtype", hass.localize( - `component.${action.domain}.device_automation.action_subtype.${ - action.subtype - }` + `component.${action.domain}.device_automation.action_subtype.${action.subtype}` ) ); }; @@ -122,16 +120,12 @@ export const localizeDeviceAutomationCondition = ( ? hass.states[condition.entity_id] : undefined; return hass.localize( - `component.${condition.domain}.device_automation.condition_type.${ - condition.type - }`, + `component.${condition.domain}.device_automation.condition_type.${condition.type}`, "entity_name", state ? computeStateName(state) : "", "subtype", hass.localize( - `component.${condition.domain}.device_automation.condition_subtype.${ - condition.subtype - }` + `component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}` ) ); }; @@ -142,16 +136,12 @@ export const localizeDeviceAutomationTrigger = ( ) => { const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined; return hass.localize( - `component.${trigger.domain}.device_automation.trigger_type.${ - trigger.type - }`, + `component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`, "entity_name", state ? computeStateName(state) : "", "subtype", hass.localize( - `component.${trigger.domain}.device_automation.trigger_subtype.${ - trigger.subtype - }` + `component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}` ) ); }; diff --git a/src/data/hassio.ts b/src/data/hassio.ts index f041fc992a..46cbf64515 100644 --- a/src/data/hassio.ts +++ b/src/data/hassio.ts @@ -149,9 +149,7 @@ export const createHassioSession = async (hass: HomeAssistant) => { "POST", "hassio/ingress/session" ); - document.cookie = `ingress_session=${ - response.data.session - };path=/api/hassio_ingress/`; + document.cookie = `ingress_session=${response.data.session};path=/api/hassio_ingress/`; }; export const reloadHassioAddons = (hass: HomeAssistant) => diff --git a/src/data/scene.ts b/src/data/scene.ts index 26ac840144..177b8dfbd8 100644 --- a/src/data/scene.ts +++ b/src/data/scene.ts @@ -18,38 +18,6 @@ export const SCENE_IGNORED_DOMAINS = [ "zone", ]; -export const SCENE_SAVED_ATTRIBUTES = { - light: [ - "brightness", - "color_temp", - "effect", - "rgb_color", - "xy_color", - "hs_color", - ], - media_player: [ - "is_volume_muted", - "volume_level", - "sound_mode", - "source", - "media_content_id", - "media_content_type", - ], - climate: [ - "target_temperature", - "target_temperature_high", - "target_temperature_low", - "target_humidity", - "fan_mode", - "swing_mode", - "hvac_mode", - "preset_mode", - ], - vacuum: ["cleaning_mode"], - fan: ["speed", "current_direction"], - water_heather: ["temperature", "operation_mode"], -}; - export interface SceneEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { id?: string }; } diff --git a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts index 2950d4c61a..a8c987d216 100644 --- a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts +++ b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts @@ -98,9 +98,7 @@ class DialogConfigEntrySystemOptions extends LitElement { "ui.dialogs.config_entry_system_options.enable_new_entities_description", "integration", this.hass.localize( - `component.${ - this._params.entry.domain - }.config.title` + `component.${this._params.entry.domain}.config.title` ) || this._params.entry.domain )}

diff --git a/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts index f21b46644c..fc83490d53 100644 --- a/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts +++ b/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts @@ -10,7 +10,9 @@ export interface ConfigEntrySystemOptionsDialogParams { } export const loadConfigEntrySystemOptionsDialog = () => - import(/* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options"); + import( + /* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options" + ); export const showConfigEntrySystemOptionsDialog = ( element: HTMLElement, diff --git a/src/dialogs/config-flow/show-dialog-config-flow.ts b/src/dialogs/config-flow/show-dialog-config-flow.ts index 6f54f010ac..333f9bdab3 100644 --- a/src/dialogs/config-flow/show-dialog-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-config-flow.ts @@ -71,9 +71,7 @@ export const showConfigFlowDialog = ( renderShowFormStepFieldLabel(hass, step, field) { return hass.localize( - `component.${step.handler}.config.step.${step.step_id}.data.${ - field.name - }` + `component.${step.handler}.config.step.${step.step_id}.data.${field.name}` ); }, diff --git a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts index 3d34834335..623732856a 100644 --- a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts @@ -79,7 +79,9 @@ export interface DataEntryFlowDialogParams { } export const loadDataEntryFlowDialog = () => - import(/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow"); + import( + /* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow" + ); export const showFlowDialog = ( element: HTMLElement, diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index 7720e1cea6..b1e22475e8 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -54,9 +54,7 @@ export const showOptionsFlowDialog = ( renderShowFormStepFieldLabel(hass, step, field) { return hass.localize( - `component.${configEntry.domain}.options.step.${step.step_id}.data.${ - field.name - }` + `component.${configEntry.domain}.options.step.${step.step_id}.data.${field.name}` ); }, diff --git a/src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts b/src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts index 564a32aa60..bfc62b5998 100644 --- a/src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts +++ b/src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts @@ -12,7 +12,9 @@ export interface DeviceRegistryDetailDialogParams { } export const loadDeviceRegistryDetailDialog = () => - import(/* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail"); + import( + /* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail" + ); export const showDeviceRegistryDetailDialog = ( element: HTMLElement, diff --git a/src/dialogs/domain-toggler/show-dialog-domain-toggler.ts b/src/dialogs/domain-toggler/show-dialog-domain-toggler.ts index e97c54354e..509075c4d5 100644 --- a/src/dialogs/domain-toggler/show-dialog-domain-toggler.ts +++ b/src/dialogs/domain-toggler/show-dialog-domain-toggler.ts @@ -6,7 +6,9 @@ export interface HaDomainTogglerDialogParams { } export const loadDomainTogglerDialog = () => - import(/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler"); + import( + /* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler" + ); export const showDomainTogglerDialog = ( element: HTMLElement, diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts index 57a9541a1b..abad9c84c2 100644 --- a/src/dialogs/more-info/controls/more-info-climate.ts +++ b/src/dialogs/more-info/controls/more-info-climate.ts @@ -100,7 +100,7 @@ class MoreInfoClimate extends LitElement { ` : ""} - ${stateObj.attributes.temperature + ${stateObj.attributes.temperature !== undefined ? html` ` : ""} - ${stateObj.attributes.target_temp_low || - stateObj.attributes.target_temp_high + ${stateObj.attributes.target_temp_low !== undefined || + stateObj.attributes.target_temp_high !== undefined ? html` @@ -68,7 +69,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) { @@ -77,6 +78,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {