From f786539f157e11541ca2710ff1a5a488e8e56409 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 23 Feb 2023 04:29:30 -0500 Subject: [PATCH 01/14] Re-add target option to zwave-js firmware upload (#15517) --- src/data/zwave_js.ts | 6 +++- .../dialog-zwave_js-update-firmware-node.ts | 32 ++++++++++++++++++- src/translations/en.json | 2 ++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 2b0b88d8cc..3cfbd367fc 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -757,10 +757,14 @@ export const fetchZwaveNodeFirmwareUpdateCapabilities = ( export const uploadFirmwareAndBeginUpdate = async ( hass: HomeAssistant, device_id: string, - file: File + file: File, + target?: number ) => { const fd = new FormData(); fd.append("file", file); + if (target !== undefined) { + fd.append("target", target.toString()); + } const resp = await hass.fetchWithAuth( `/api/zwave_js/firmware/upload/${device_id}`, { diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts index d58c1822b1..e6d5859efa 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts @@ -36,6 +36,15 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../../../dialogs/generic/show-dialog-box"; +import { HaFormSchema } from "../../../../../components/ha-form/types"; + +const firmwareTargetSchema: HaFormSchema[] = [ + { + name: "firmware_target", + type: "integer", + valueMin: 0, + }, +]; @customElement("dialog-zwave_js-update-firmware-node") class DialogZWaveJSUpdateFirmwareNode extends LitElement { @@ -59,6 +68,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { @state() private _nodeStatus?: ZWaveJSNodeStatus; + @state() private _firmwareTarget?: number; + private _subscribedNodeStatus?: Promise; private _subscribedNodeFirmwareUpdate?: Promise; @@ -80,6 +91,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { this._updateFinishedMessage = undefined; this._firmwareFile = undefined; this._nodeStatus = undefined; + this._firmwareTarget = undefined; this._uploading = this._updateInProgress = false; fireEvent(this, "dialog-closed", { dialog: this.localName }); @@ -104,6 +116,19 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { )} @file-picked=${this._uploadFile} > + ${this._nodeStatus.is_controller_node + ? html`` + : html`

+ ${this.hass.localize( + "ui.panel.config.zwave_js.update_firmware.firmware_target_intro" + )} +

+ `} Date: Thu, 23 Feb 2023 10:38:44 +0100 Subject: [PATCH 02/14] Allow energy dashboard to be cast (#15397) --- cast/src/receiver/layout/hc-main.ts | 16 ++++++++++++++++ src/panels/energy/ha-panel-energy.ts | 6 +++--- src/panels/energy/strategies/energy-strategy.ts | 2 +- .../cards/energy/hui-energy-distribution-card.ts | 5 +++++ .../config-elements/hui-entities-card-editor.ts | 2 +- src/panels/lovelace/entity-rows/types.ts | 2 +- src/panels/lovelace/special-rows/hui-cast-row.ts | 9 +++------ 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 298487bd0f..de31b9bce1 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -252,6 +252,22 @@ export class HcMain extends HassElement { msg.urlPath = null; } this._lovelacePath = msg.viewPath; + if (msg.urlPath === "energy") { + this._lovelaceConfig = { + views: [ + { + strategy: { + type: "energy", + options: { show_date_selection: true }, + }, + }, + ], + }; + this._urlPath = "energy"; + this._lovelacePath = 0; + this._sendStatus(); + return; + } if (!this._unsubLovelace || this._urlPath !== msg.urlPath) { this._urlPath = msg.urlPath; this._lovelaceConfig = undefined; diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts index a9090dc14d..a81a6afef2 100644 --- a/src/panels/energy/ha-panel-energy.ts +++ b/src/panels/energy/ha-panel-energy.ts @@ -18,7 +18,7 @@ import "../lovelace/components/hui-energy-period-selector"; import { Lovelace } from "../lovelace/types"; import "../lovelace/views/hui-view"; -const LOVELACE_CONFIG: LovelaceConfig = { +const ENERGY_LOVELACE_CONFIG: LovelaceConfig = { views: [ { strategy: { @@ -93,8 +93,8 @@ class PanelEnergy extends LitElement { private _setLovelace() { this._lovelace = { - config: LOVELACE_CONFIG, - rawConfig: LOVELACE_CONFIG, + config: ENERGY_LOVELACE_CONFIG, + rawConfig: ENERGY_LOVELACE_CONFIG, editMode: false, urlPath: "energy", mode: "generated", diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts index 6c6122c2af..de5d6aca44 100644 --- a/src/panels/energy/strategies/energy-strategy.ts +++ b/src/panels/energy/strategies/energy-strategy.ts @@ -56,7 +56,7 @@ export class EnergyStrategy { (source) => source.type === "water" ); - if (info.narrow) { + if (info.narrow || info.view.strategy?.options?.show_date_selection) { view.cards!.push({ type: "energy-date-selection", collection_key: "energy_dashboard", diff --git a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts index 9535c4a6c2..b57b718d59 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -884,6 +884,11 @@ class HuiEnergyDistrubutionCard color: var(--secondary-text-color); font-size: 12px; opacity: 1; + height: 20px; + overflow: hidden; + text-overflow: ellipsis; + max-width: 80px; + white-space: nowrap; } line, path { diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index 1b0e680850..3b847fa672 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -62,7 +62,7 @@ const buttonEntitiesRowConfigStruct = object({ const castEntitiesRowConfigStruct = object({ type: literal("cast"), - view: union([string(), number()]), + view: optional(union([string(), number()])), dashboard: optional(string()), name: optional(string()), icon: optional(string()), diff --git a/src/panels/lovelace/entity-rows/types.ts b/src/panels/lovelace/entity-rows/types.ts index 47131f09e6..f377c9631b 100644 --- a/src/panels/lovelace/entity-rows/types.ts +++ b/src/panels/lovelace/entity-rows/types.ts @@ -55,7 +55,7 @@ export interface CastConfig { type: "cast"; icon?: string; name?: string; - view: string | number; + view?: string | number; dashboard?: string; // Hide the row if either unsupported browser or no API available. hide_if_unavailable?: boolean; diff --git a/src/panels/lovelace/special-rows/hui-cast-row.ts b/src/panels/lovelace/special-rows/hui-cast-row.ts index c29edd95e8..19d563ad3c 100644 --- a/src/panels/lovelace/special-rows/hui-cast-row.ts +++ b/src/panels/lovelace/special-rows/hui-cast-row.ts @@ -29,13 +29,10 @@ class HuiCastRow extends LitElement implements LovelaceRow { @state() private _noHTTPS = false; public setConfig(config: CastConfig): void { - if (!config || config.view === undefined || config.view === null) { - throw new Error("View required"); - } - this._config = { - icon: "hass:television", + icon: "mdi:television", name: "Home Assistant Cast", + view: 0, ...config, }; } @@ -123,7 +120,7 @@ class HuiCastRow extends LitElement implements LovelaceRow { castSendShowLovelaceView( this._castManager!, this.hass.auth.data.hassUrl, - this._config!.view, + this._config!.view!, this._config!.dashboard ); } From ab231eec4fc97233b09bfe0c54feff472abbefc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Feb 2023 10:42:30 +0100 Subject: [PATCH 03/14] Bump magic-string from 0.29.0 to 0.30.0 (#15557) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 998f5eda7b..92dcb7d1ad 100644 --- a/package.json +++ b/package.json @@ -216,7 +216,7 @@ "lint-staged": "^13.1.2", "lit-analyzer": "^1.2.1", "lodash.template": "^4.5.0", - "magic-string": "^0.29.0", + "magic-string": "^0.30.0", "map-stream": "^0.0.7", "merge-stream": "^2.0.0", "mocha": "^10.2.0", diff --git a/yarn.lock b/yarn.lock index a9c6e3a82d..bc8c815b3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9663,7 +9663,7 @@ fsevents@~2.3.2: lit: ^2.6.1 lit-analyzer: ^1.2.1 lodash.template: ^4.5.0 - magic-string: ^0.29.0 + magic-string: ^0.30.0 map-stream: ^0.0.7 marked: ^4.2.12 memoize-one: ^6.0.0 @@ -11590,12 +11590,12 @@ fsevents@~2.3.2: languageName: node linkType: hard -"magic-string@npm:^0.29.0": - version: 0.29.0 - resolution: "magic-string@npm:0.29.0" +"magic-string@npm:^0.30.0": + version: 0.30.0 + resolution: "magic-string@npm:0.30.0" dependencies: "@jridgewell/sourcemap-codec": ^1.4.13 - checksum: 19e5398fcfc44804917127c72ad622c68a19a0a10cbdb8d4f9f9417584a087fe9e117140bfb2463d86743cf1ed9cf4182ae0b0ad1a7536f7fdda257ee4449ffb + checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d languageName: node linkType: hard From 03e3f161f7c7a3cf49e4067d9233dd7a055c7a06 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Thu, 23 Feb 2023 08:06:35 -0500 Subject: [PATCH 04/14] Prefer regex literals over constructors (#15553) --- .eslintrc.json | 1 - demo/src/stubs/history.ts | 2 +- src/common/string/has-template.ts | 2 +- src/entrypoints/service_worker.ts | 16 +++++++--------- src/fake_data/provide_hass.ts | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 985f1da4fd..54c0a23818 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -60,7 +60,6 @@ "no-restricted-globals": [2, "event"], "prefer-promise-reject-errors": "off", "no-unsafe-optional-chaining": "warn", - "prefer-regex-literals": ["warn"], "import/prefer-default-export": "off", "import/no-default-export": "off", "import/no-unresolved": "off", diff --git a/demo/src/stubs/history.ts b/demo/src/stubs/history.ts index 5b96b77ddf..0daccb4d80 100644 --- a/demo/src/stubs/history.ts +++ b/demo/src/stubs/history.ts @@ -66,7 +66,7 @@ const incrementalUnits = ["clients", "queries", "ads"]; export const mockHistory = (mockHass: MockHomeAssistant) => { mockHass.mockAPI( - new RegExp("history/period/.+"), + /history\/period\/.+/, (hass, _method, path, _parameters) => { const params = parseQuery(path.split("?")[1]); const entities = params.filter_entity_id.split(","); diff --git a/src/common/string/has-template.ts b/src/common/string/has-template.ts index c86d7f6e8e..189f10f635 100644 --- a/src/common/string/has-template.ts +++ b/src/common/string/has-template.ts @@ -1,4 +1,4 @@ -const isTemplateRegex = new RegExp("{%|{{"); +const isTemplateRegex = /{%|{{/; export const isTemplate = (value: string): boolean => isTemplateRegex.test(value); diff --git a/src/entrypoints/service_worker.ts b/src/entrypoints/service_worker.ts index d44ce684ba..da8fbd8471 100644 --- a/src/entrypoints/service_worker.ts +++ b/src/entrypoints/service_worker.ts @@ -13,10 +13,8 @@ import { StaleWhileRevalidate, } from "workbox-strategies"; -const noFallBackRegEx = new RegExp( - "/(api|static|auth|frontend_latest|frontend_es5|local)/.*" -); - +const noFallBackRegEx = + /\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/; // Clean up caches from older workboxes and old service workers. // Will help with cleaning up Workbox v4 stuff cleanupOutdatedCaches(); @@ -33,22 +31,22 @@ function initRouting() { // Cache static content (including translations) on first access. registerRoute( - new RegExp("/(static|frontend_latest|frontend_es5)/.+"), + /\/(static|frontend_latest|frontend_es5)\/.+/, new CacheFirst({ matchOptions: { ignoreSearch: true } }) ); // Get api from network. - registerRoute(new RegExp("/(api|auth)/.*"), new NetworkOnly()); + registerRoute(/\/(api|auth)\/.*/, new NetworkOnly()); // Get manifest, service worker, onboarding from network. registerRoute( - new RegExp("/(service_worker.js|manifest.json|onboarding.html)"), + /\/(service_worker.js|manifest.json|onboarding.html)/, new NetworkOnly() ); // For the root "/" we ignore search registerRoute( - new RegExp(/\/(\?.*)?$/), + /\/(\?.*)?$/, new StaleWhileRevalidate({ matchOptions: { ignoreSearch: true } }) ); @@ -57,7 +55,7 @@ function initRouting() { // First access might bring stale data from cache, but a single refresh will bring updated // file. registerRoute( - new RegExp(/\/.*/), + /\/.*/, new StaleWhileRevalidate({ cacheName: "file-cache", plugins: [ diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 2c717528ee..5263dadcaf 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -116,7 +116,7 @@ export const provideHass = ( } mockAPI( - new RegExp("states/.+"), + /states\/.+/, ( // @ts-ignore method, From 3b5fe7fd9f76b282bc26990e7baae9f24dcb6bbc Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Feb 2023 15:10:05 +0100 Subject: [PATCH 05/14] Center new more info controls (#15564) * Center new more info controls * Center change color view --- .../components/ha-more-info-control-style.ts | 24 ++ .../ha-more-info-view-light-color-picker.ts | 210 +++++++++--------- src/dialogs/more-info/const.ts | 4 +- .../more-info/controls/more-info-group.ts | 16 +- .../more-info/controls/more-info-light.ts | 80 +++---- .../more-info/controls/more-info-siren.ts | 37 +-- .../more-info/controls/more-info-switch.ts | 37 +-- src/dialogs/more-info/ha-more-info-dialog.ts | 48 ++-- src/dialogs/more-info/ha-more-info-info.ts | 131 +++++++---- 9 files changed, 304 insertions(+), 283 deletions(-) create mode 100644 src/dialogs/more-info/components/ha-more-info-control-style.ts diff --git a/src/dialogs/more-info/components/ha-more-info-control-style.ts b/src/dialogs/more-info/components/ha-more-info-control-style.ts new file mode 100644 index 0000000000..1c3fed35e2 --- /dev/null +++ b/src/dialogs/more-info/components/ha-more-info-control-style.ts @@ -0,0 +1,24 @@ +import { css } from "lit"; + +export const moreInfoControlStyle = css` + :host { + display: flex; + flex-direction: column; + flex: 1; + justify-content: space-between; + } + + .controls { + display: flex; + flex-direction: column; + align-items: center; + } + + .controls > *:not(:last-child) { + margin-bottom: 24px; + } + + ha-attributes { + width: 100%; + } +`; diff --git a/src/dialogs/more-info/components/lights/ha-more-info-view-light-color-picker.ts b/src/dialogs/more-info/components/lights/ha-more-info-view-light-color-picker.ts index 33058b76a7..f7ff640bcb 100644 --- a/src/dialogs/more-info/components/lights/ha-more-info-view-light-color-picker.ts +++ b/src/dialogs/more-info/components/lights/ha-more-info-view-light-color-picker.ts @@ -23,7 +23,6 @@ import { lightSupportsColor, lightSupportsColorMode, } from "../../../../data/light"; -import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { LightColorPickerViewParams } from "./show-view-light-color-picker"; @@ -78,117 +77,115 @@ class MoreInfoViewLightColorPicker extends LitElement { lightSupportsColorMode(this.stateObj, LightColorMode.RGBW); return html` -
- ${this._modes.length > 1 + ${this._modes.length > 1 + ? html` + + ${this._modes.map( + (value) => + html`` + )} + + ` + : ""} +
+ ${this._mode === LightColorMode.COLOR_TEMP ? html` - - ${this._modes.map( - (value) => - html`` - )} - + ` : ""} -
- ${this._mode === LightColorMode.COLOR_TEMP - ? html` - + - - ` - : ""} - ${this._mode === "color" - ? html` -
- - - -
+ + +
- ${supportsRgbw || supportsRgbww - ? html`` + : ""} + ${supportsRgbw + ? html` + ` - : ""} - ${supportsRgbw - ? html` - - ` - : ""} - ${supportsRgbww - ? html` - - - ` - : ""} - ` - : ""} -
+ > + ` + : ""} + ${supportsRgbww + ? html` + + + ` + : ""} + ` + : ""}
`; } @@ -482,13 +479,18 @@ class MoreInfoViewLightColorPicker extends LitElement { static get styles(): CSSResultGroup { return [ - haStyleDialog, css` + :host { + display: flex; + flex-direction: column; + } .content { display: flex; flex-direction: column; align-items: center; - padding: 16px; + justify-content: center; + padding: 24px; + flex: 1; } .segmentationContainer { diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts index 7a33ea20fa..fbcb330aec 100644 --- a/src/dialogs/more-info/const.ts +++ b/src/dialogs/more-info/const.ts @@ -94,12 +94,12 @@ export const computeShowLogBookComponent = ( return true; }; -export const computeShowNewMoreInfo = (stateObj: HassEntity) => { +export const computeShowNewMoreInfo = (stateObj: HassEntity): boolean => { const domain = computeDomain(stateObj.entity_id); if (domain === "group") { const groupDomain = computeGroupDomain(stateObj as GroupEntity); return ( - groupDomain && + groupDomain != null && groupDomain !== "group" && DOMAINS_WITH_NEW_MORE_INFO.includes(groupDomain) ); diff --git a/src/dialogs/more-info/controls/more-info-group.ts b/src/dialogs/more-info/controls/more-info-group.ts index bc5536541d..0d7086fb57 100644 --- a/src/dialogs/more-info/controls/more-info-group.ts +++ b/src/dialogs/more-info/controls/more-info-group.ts @@ -12,6 +12,7 @@ import { dynamicElement } from "../../../common/dom/dynamic-element-directive"; import { computeGroupDomain, GroupEntity } from "../../../data/group"; import "../../../state-summary/state-card-content"; import { HomeAssistant } from "../../../types"; +import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; import { domainMoreInfoType, importMoreInfoControl, @@ -94,12 +95,15 @@ class MoreInfoGroup extends LitElement { } static get styles(): CSSResultGroup { - return css` - state-card-content { - display: block; - margin-top: 8px; - } - `; + return [ + moreInfoControlStyle, + css` + state-card-content { + display: block; + margin-top: 8px; + } + `, + ]; } } diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 0204eb7687..173f014b90 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -32,6 +32,7 @@ import { lightSupportsColorMode, } from "../../../data/light"; import type { HomeAssistant } from "../../../types"; +import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; import "../components/ha-more-info-state-header"; import "../components/ha-more-info-toggle"; import "../components/lights/ha-more-info-light-brightness"; @@ -85,12 +86,12 @@ class MoreInfoLight extends LitElement { : undefined; return html` -
- + +
${supportsBrightness ? html` ` : null} -
+ + `; } @@ -230,41 +232,29 @@ class MoreInfoLight extends LitElement { } static get styles(): CSSResultGroup { - return css` - .content { - display: flex; - flex-direction: column; - align-items: center; - } + return [ + moreInfoControlStyle, + css` + .buttons { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 12px; + } + .buttons > * { + margin: 4px; + } - .buttons { - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 12px; - } - .buttons > * { - margin: 4px; - } - - ha-more-info-light-brightness, - ha-more-info-toggle { - margin-bottom: 24px; - } - - ha-attributes { - width: 100%; - } - - md-outlined-icon-button-toggle, - md-outlined-icon-button { - --ha-icon-display: block; - --md-sys-color-on-surface: var(--secondary-text-color); - --md-sys-color-on-surface-variant: var(--secondary-text-color); - --md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color); - --md-sys-color-outline: var(--secondary-text-color); - } - `; + md-outlined-icon-button-toggle, + md-outlined-icon-button { + --ha-icon-display: block; + --md-sys-color-on-surface: var(--secondary-text-color); + --md-sys-color-on-surface-variant: var(--secondary-text-color); + --md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color); + --md-sys-color-outline: var(--secondary-text-color); + } + `, + ]; } } diff --git a/src/dialogs/more-info/controls/more-info-siren.ts b/src/dialogs/more-info/controls/more-info-siren.ts index b57d58bf45..9689dcf0da 100644 --- a/src/dialogs/more-info/controls/more-info-siren.ts +++ b/src/dialogs/more-info/controls/more-info-siren.ts @@ -1,9 +1,10 @@ import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import "../../../components/ha-attributes"; import { LightEntity } from "../../../data/light"; import type { HomeAssistant } from "../../../types"; +import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; import "../components/ha-more-info-state-header"; import "../components/ha-more-info-toggle"; @@ -19,41 +20,27 @@ class MoreInfoSiren extends LitElement { } return html` -
- + +
-
+ `; } static get styles(): CSSResultGroup { - return css` - .content { - display: flex; - flex-direction: column; - align-items: center; - } - - ha-more-info-toggle { - margin-bottom: 24px; - } - - ha-attributes { - width: 100%; - } - `; + return moreInfoControlStyle; } } diff --git a/src/dialogs/more-info/controls/more-info-switch.ts b/src/dialogs/more-info/controls/more-info-switch.ts index 0849fa9bff..c51c6007b7 100644 --- a/src/dialogs/more-info/controls/more-info-switch.ts +++ b/src/dialogs/more-info/controls/more-info-switch.ts @@ -1,9 +1,10 @@ import { mdiPower, mdiPowerOff } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import "../../../components/ha-attributes"; import { LightEntity } from "../../../data/light"; import type { HomeAssistant } from "../../../types"; +import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; import "../components/ha-more-info-state-header"; import "../components/ha-more-info-toggle"; @@ -19,41 +20,27 @@ class MoreInfoSwitch extends LitElement { } return html` -
- + +
-
+ `; } static get styles(): CSSResultGroup { - return css` - .content { - display: flex; - flex-direction: column; - align-items: center; - } - - ha-more-info-toggle { - margin-bottom: 24px; - } - - ha-attributes { - width: 100%; - } - `; + return moreInfoControlStyle; } } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index a3cae303a4..2cbf2bffc3 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -203,13 +203,7 @@ export class MoreInfoDialog extends LitElement { const isInfoView = this._currView === "info" && !this._childView; return html` - +
${isInfoView @@ -335,10 +329,14 @@ export class MoreInfoDialog extends LitElement { @show-child-view=${this._showChildView} > ${this._childView - ? dynamicElement(this._childView.viewTag, { - hass: this.hass, - params: this._childView.viewParams, - }) + ? html` +
+ ${dynamicElement(this._childView.viewTag, { + hass: this.hass, + params: this._childView.viewParams, + })} +
+ ` : cache( this._currView === "info" ? html` @@ -385,12 +383,8 @@ export class MoreInfoDialog extends LitElement { protected updated(changedProps: PropertyValues) { super.updated(changedProps); if (changedProps.has("_currView")) { - this.setAttribute("view", this._currView); this._childView = undefined; } - if (changedProps.has("_childView")) { - this.toggleAttribute("has-child-view", !!this._childView); - } } private _enlarge() { @@ -407,7 +401,6 @@ export class MoreInfoDialog extends LitElement { --dialog-content-position: static; --vertical-align-dialog: flex-start; --dialog-content-padding: 0; - --content-padding: 24px; } ha-header-bar { @@ -417,6 +410,7 @@ export class MoreInfoDialog extends LitElement { display: block; border-bottom: none; } + .content { outline: none; } @@ -426,22 +420,16 @@ export class MoreInfoDialog extends LitElement { var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); } - :host([view="settings"]) ha-dialog { - --content-padding: 0; + ha-related-items, + ha-more-info-history-and-logbook { + padding: 24px; + display: block; } - :host([view="info"]) ha-dialog[data-domain="camera"] { - --content-padding: 0; - /* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */ - --video-max-height: calc(100vh - 65px - 72px); - } - - :host([has-child-view]) ha-dialog { - --content-padding: 0; - } - - .content { - padding: var(--content-padding); + @media all and (max-width: 450px) { + .child-view > * { + min-height: calc(100vh - 56px); + } } .main-title { diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts index 0688b4edf5..c7e9721ca7 100644 --- a/src/dialogs/more-info/ha-more-info-info.ts +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -16,6 +16,7 @@ import { } from "./const"; import "./ha-more-info-history"; import "./ha-more-info-logbook"; +import "./more-info-content"; @customElement("ha-more-info-info") export class MoreInfoInfo extends LitElement { @@ -29,52 +30,59 @@ export class MoreInfoInfo extends LitElement { const entityId = this.entityId; const stateObj = this.hass.states[entityId]; const domain = computeDomain(entityId); + const newMoreInfo = computeShowNewMoreInfo(stateObj); return html` - ${!stateObj - ? html` - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.unavailable" - )} - ` - : ""} - ${stateObj?.attributes.restored && this._entityEntry - ? html` - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.no_longer_provided", - { - integration: this._entityEntry.platform, - } - )} - ` - : ""} - ${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj) - ? "" - : html` - - `} - ${DOMAINS_WITH_MORE_INFO.includes(domain) || - !computeShowHistoryComponent(this.hass, entityId) - ? "" - : html` + ${!stateObj + ? html` + ${this.hass.localize( + "ui.dialogs.entity_registry.editor.unavailable" + )} + ` + : ""} + ${stateObj?.attributes.restored && this._entityEntry + ? html` + ${this.hass.localize( + "ui.dialogs.more_info_control.restored.no_longer_provided", + { + integration: this._entityEntry.platform, + } + )} + ` + : ""} +
+ ${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj) + ? "" + : html` + + `} + ${DOMAINS_WITH_MORE_INFO.includes(domain) || + !computeShowHistoryComponent(this.hass, entityId) + ? "" + : html``} + ${DOMAINS_WITH_MORE_INFO.includes(domain) || + !computeShowLogBookComponent(this.hass, entityId) + ? "" + : html``} + `} - ${DOMAINS_WITH_MORE_INFO.includes(domain) || - !computeShowLogBookComponent(this.hass, entityId) - ? "" - : html``} - + > +
+
+
`; } @@ -91,6 +99,40 @@ export class MoreInfoInfo extends LitElement { static get styles() { return css` + .container { + display: flex; + flex-direction: column; + } + + @media all and (max-width: 450px) { + .container { + min-height: calc(100vh - 56px); + } + } + + .content { + display: flex; + flex-direction: column; + flex: 1; + padding: 24px; + padding-bottom: max(env(safe-area-inset-bottom), 24px); + } + + [data-domain="camera"] .content { + padding: 0; + /* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */ + --video-max-height: calc(100vh - 65px - 72px); + } + + more-info-content { + position: relative; + display: flex; + flex-direction: column; + } + more-info-content[full-height] { + flex: 1; + } + state-card-content, ha-more-info-history, ha-more-info-logbook:not(:last-child) { @@ -100,9 +142,6 @@ export class MoreInfoInfo extends LitElement { ha-alert { display: block; - margin: calc(-1 * var(--content-padding, 24px)) - calc(-1 * var(--content-padding, 24px)) 16px - calc(-1 * var(--content-padding, 24px)); } `; } From 7e0c80ae24fa966690a0fa179fba914d267d2f98 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 23 Feb 2023 15:41:27 +0100 Subject: [PATCH 06/14] Use correct number format options in hui-gauge (#15565) --- src/components/ha-gauge.ts | 6 +++++- src/data/thread.ts | 7 ++----- src/panels/lovelace/cards/hui-gauge-card.ts | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 6aaf31e3d5..9519ec85df 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -26,6 +26,9 @@ export class Gauge extends LitElement { @property({ type: Number }) public value = 0; + @property({ attribute: false }) + public formatOptions?: Intl.NumberFormatOptions; + @property({ type: String }) public valueText?: string; @property() public locale!: FrontendLocaleData; @@ -132,7 +135,8 @@ export class Gauge extends LitElement { ${ this._segment_label ? this._segment_label - : this.valueText || formatNumber(this.value, this.locale) + : this.valueText || + formatNumber(this.value, this.locale, this.formatOptions) }${ this._segment_label ? "" diff --git a/src/data/thread.ts b/src/data/thread.ts index b760495654..e3016b9416 100644 --- a/src/data/thread.ts +++ b/src/data/thread.ts @@ -26,12 +26,9 @@ export interface ThreadRouterDiscoveryEvent { } class DiscoveryStream { - hass: HomeAssistant; - routers: { [key: string]: ThreadRouter }; - constructor(hass: HomeAssistant) { - this.hass = hass; + constructor() { this.routers = {}; } @@ -49,7 +46,7 @@ export const subscribeDiscoverThreadRouters = ( hass: HomeAssistant, callbackFunction: (routers: ThreadRouter[]) => void ) => { - const stream = new DiscoveryStream(hass); + const stream = new DiscoveryStream(); return hass.connection.subscribeMessage( (message) => callbackFunction(stream.processEvent(message)), { diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 801b70f09d..ec243f8846 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -13,6 +13,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { isValidEntityId } from "../../../common/entity/valid_entity_id"; +import { getNumberFormatOptions } from "../../../common/number/format_number"; import "../../../components/ha-card"; import "../../../components/ha-gauge"; import { UNAVAILABLE } from "../../../data/entity"; @@ -129,6 +130,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { .min=${this._config.min!} .max=${this._config.max!} .value=${stateObj.state} + .formatOptions=${getNumberFormatOptions(stateObj)} .locale=${this.hass!.locale} .label=${this._config!.unit || this.hass?.states[this._config!.entity].attributes From 2e86d739fcabf972cf831a1b55ab073043af2bc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:49:16 +0100 Subject: [PATCH 07/14] Bump @material/web from 1.0.0-pre.2 to 1.0.0-pre.3 (#15556) Bumps [@material/web](https://github.com/material-components/material-web) from 1.0.0-pre.2 to 1.0.0-pre.3. - [Release notes](https://github.com/material-components/material-web/releases) - [Changelog](https://github.com/material-components/material-web/blob/master/CHANGELOG.md) - [Commits](https://github.com/material-components/material-web/compare/v1.0.0-pre.2...v1.0.0-pre.3) --- updated-dependencies: - dependency-name: "@material/web" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 92dcb7d1ad..1d10ea6580 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@material/mwc-textfield": "^0.27.0", "@material/mwc-top-app-bar-fixed": "^0.27.0", "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", - "@material/web": "=1.0.0-pre.2", + "@material/web": "=1.0.0-pre.3", "@mdi/js": "7.1.96", "@mdi/svg": "7.1.96", "@polymer/app-layout": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index bc8c815b3d..b10991e3f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3095,13 +3095,13 @@ __metadata: languageName: node linkType: hard -"@material/web@npm:=1.0.0-pre.2": - version: 1.0.0-pre.2 - resolution: "@material/web@npm:1.0.0-pre.2" +"@material/web@npm:=1.0.0-pre.3": + version: 1.0.0-pre.3 + resolution: "@material/web@npm:1.0.0-pre.3" dependencies: lit: ^2.3.0 tslib: ^2.4.0 - checksum: 7c6733fae5fb67c43d7c49fab70f7893defd95e4fcbe996d06057882e47c0121760546cc5d1c407a9dbd11c5f02f3f278016c52922e6a9e97db0c0b52d7133f2 + checksum: d6286992cb0d63b094e638a3db484398195608b422bb2cb209102eaf87d220ed24bbe85d29933730054fd3cf99d318dbb3645ae9a2e271fe1a3c2833d829bf4c languageName: node linkType: hard @@ -9562,7 +9562,7 @@ fsevents@~2.3.2: "@material/mwc-textfield": ^0.27.0 "@material/mwc-top-app-bar-fixed": ^0.27.0 "@material/top-app-bar": =14.0.0-canary.53b3cad2f.0 - "@material/web": =1.0.0-pre.2 + "@material/web": =1.0.0-pre.3 "@mdi/js": 7.1.96 "@mdi/svg": 7.1.96 "@octokit/auth-oauth-device": ^4.0.4 From f92deb32253b01de74f1286b46bcc41a5fde455e Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Feb 2023 16:20:22 +0100 Subject: [PATCH 08/14] Add haptic support to light toggle button (#15569) --- src/dialogs/more-info/controls/more-info-light.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 173f014b90..e708e9b30f 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -23,6 +23,7 @@ import "../../../components/ha-attributes"; import "../../../components/ha-button-menu"; import "../../../components/ha-select"; import { UNAVAILABLE } from "../../../data/entity"; +import { forwardHaptic } from "../../../data/haptics"; import { LightColorMode, LightEntity, @@ -197,6 +198,7 @@ class MoreInfoLight extends LitElement { private _toggle = () => { const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on"; + forwardHaptic("light"); this.hass.callService("light", service, { entity_id: this.stateObj!.entity_id, }); From 7173b30716091c85380a4275ecf26512b5e3c8f3 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Feb 2023 16:23:24 +0100 Subject: [PATCH 09/14] Add margin between logbook and history (#15571) --- .../more-info/ha-more-info-history-and-logbook.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dialogs/more-info/ha-more-info-history-and-logbook.ts b/src/dialogs/more-info/ha-more-info-history-and-logbook.ts index c0ae54602f..5d01b4b0ae 100644 --- a/src/dialogs/more-info/ha-more-info-history-and-logbook.ts +++ b/src/dialogs/more-info/ha-more-info-history-and-logbook.ts @@ -1,4 +1,4 @@ -import { LitElement, html } from "lit"; +import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { HomeAssistant } from "../../types"; import { @@ -34,6 +34,18 @@ export class MoreInfoHistoryAndLogbook extends LitElement { : ""} `; } + + static get styles(): CSSResultGroup { + return css` + ha-more-info-history, + ha-more-info-logbook { + display: block; + } + ha-more-info-history + ha-more-info-logbook { + margin-top: 16px; + } + `; + } } declare global { From f69ae84cc606683bbbc626150720dcc4a6d8a11e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 23 Feb 2023 16:30:13 +0100 Subject: [PATCH 10/14] Use entity registry display api (#15549) --- .../src/pages/automation/describe-action.ts | 4 +- .../entity/compute_attribute_display.ts | 6 +- src/common/entity/compute_state_display.ts | 4 +- src/common/number/format_number.ts | 8 +- src/components/entity/ha-state-label-badge.ts | 6 +- src/components/ha-area-picker.ts | 21 ++--- .../ha-selector/ha-selector-area.ts | 29 ++----- .../ha-selector/ha-selector-attribute.ts | 3 +- .../ha-selector/ha-selector-device.ts | 29 ++----- src/components/ha-target-picker.ts | 10 +-- src/components/trace/hat-trace-timeline.ts | 25 +++++- src/data/device_registry.ts | 27 ++++++- src/data/entity_registry.ts | 80 ++++++++++++++++++- src/data/script_i18n.ts | 4 +- src/dialogs/more-info/ha-more-info-dialog.ts | 31 ++++++- .../more-info/ha-more-info-settings.ts | 51 ++++-------- .../action/ha-automation-action-row.ts | 21 ++++- .../config/automation/ha-automation-editor.ts | 4 +- src/panels/config/script/ha-config-script.ts | 22 ++++- src/panels/config/script/ha-script-editor.ts | 11 ++- src/panels/config/script/ha-script-picker.ts | 33 ++++++-- src/panels/config/script/ha-script-trace.ts | 7 +- src/panels/lovelace/cards/hui-gauge-card.ts | 5 +- .../common/generate-lovelace-config.ts | 4 +- src/state/connection-mixin.ts | 20 ++++- src/types.ts | 4 +- 26 files changed, 315 insertions(+), 154 deletions(-) diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 1a32ac0829..6465e9fc1e 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
${this._action - ? describeAction(this.hass, this._action) + ? describeAction(this.hass, [], this._action) : ""} html`
- ${describeAction(this.hass, conf as any)} + ${describeAction(this.hass, [], conf as any)}
${dump(conf)}
` diff --git a/src/common/entity/compute_attribute_display.ts b/src/common/entity/compute_attribute_display.ts index f969646265..7cba3466f7 100644 --- a/src/common/entity/compute_attribute_display.ts +++ b/src/common/entity/compute_attribute_display.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { HomeAssistant } from "../../types"; import { LocalizeFunc } from "../translations/localize"; import { computeDomain } from "./compute_domain"; @@ -15,7 +15,7 @@ export const computeAttributeValueDisplay = ( const attributeValue = value !== undefined ? value : stateObj.attributes[attribute]; const domain = computeDomain(entityId); - const entity = entities[entityId] as EntityRegistryEntry | undefined; + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; const translationKey = entity?.translation_key; return ( @@ -38,7 +38,7 @@ export const computeAttributeNameDisplay = ( ): string => { const entityId = stateObj.entity_id; const domain = computeDomain(entityId); - const entity = entities[entityId] as EntityRegistryEntry | undefined; + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; const translationKey = entity?.translation_key; return ( diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index c469585a9d..cb327710c2 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -1,6 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { FrontendLocaleData } from "../../data/translation"; import { updateIsInstallingFromAttributes, @@ -49,7 +49,7 @@ export const computeStateDisplayFromEntityAttributes = ( return localize(`state.default.${state}`); } - const entity = entities[entityId] as EntityRegistryEntry | undefined; + const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined; // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` if (isNumericFromAttributes(attributes)) { diff --git a/src/common/number/format_number.ts b/src/common/number/format_number.ts index 2e461ab822..3834ca1a65 100644 --- a/src/common/number/format_number.ts +++ b/src/common/number/format_number.ts @@ -2,7 +2,7 @@ import { HassEntity, HassEntityAttributeBase, } from "home-assistant-js-websocket"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { FrontendLocaleData, NumberFormat } from "../../data/translation"; import { round } from "./round"; @@ -92,11 +92,9 @@ export const formatNumber = ( */ export const getNumberFormatOptions = ( entityState: HassEntity, - entity?: EntityRegistryEntry + entity?: EntityRegistryDisplayEntry ): Intl.NumberFormatOptions | undefined => { - const precision = - entity?.options?.sensor?.display_precision ?? - entity?.options?.sensor?.suggested_display_precision; + const precision = entity?.display_precision; if (precision != null) { return { maximumFractionDigits: precision, diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index 03079a331f..3f9262e781 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -22,7 +22,7 @@ import { isNumericState, } from "../../common/number/format_number"; import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { timerTimeRemaining } from "../../data/timer"; import { HomeAssistant } from "../../types"; import "../ha-label-badge"; @@ -160,7 +160,7 @@ export class HaStateLabelBadge extends LitElement { private _computeValue( domain: string, entityState: HassEntity, - entry?: EntityRegistryEntry + entry?: EntityRegistryDisplayEntry ) { switch (domain) { case "alarm_control_panel": @@ -200,7 +200,7 @@ export class HaStateLabelBadge extends LitElement { private _computeShowIcon( domain: string, entityState: HassEntity, - entry?: EntityRegistryEntry + entry?: EntityRegistryDisplayEntry ): boolean { if (entityState.state === UNAVAILABLE) { return false; diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 6f016cc1f7..471ce71401 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -12,10 +12,11 @@ import { createAreaRegistryEntry, } from "../data/area_registry"; import { - DeviceEntityLookup, + DeviceEntityDisplayLookup, DeviceRegistryEntry, + getDeviceEntityDisplayLookup, } from "../data/device_registry"; -import { EntityRegistryEntry } from "../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../data/entity_registry"; import { showAlertDialog, showPromptDialog, @@ -113,7 +114,7 @@ export class HaAreaPicker extends LitElement { ( areas: AreaRegistryEntry[], devices: DeviceRegistryEntry[], - entities: EntityRegistryEntry[], + entities: EntityRegistryDisplayEntry[], includeDomains: this["includeDomains"], excludeDomains: this["excludeDomains"], includeDeviceClasses: this["includeDeviceClasses"], @@ -133,9 +134,9 @@ export class HaAreaPicker extends LitElement { ]; } - const deviceEntityLookup: DeviceEntityLookup = {}; + let deviceEntityLookup: DeviceEntityDisplayLookup = {}; let inputDevices: DeviceRegistryEntry[] | undefined; - let inputEntities: EntityRegistryEntry[] | undefined; + let inputEntities: EntityRegistryDisplayEntry[] | undefined; if ( includeDomains || @@ -143,15 +144,7 @@ export class HaAreaPicker extends LitElement { includeDeviceClasses || entityFilter ) { - 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); - } + deviceEntityLookup = getDeviceEntityDisplayLookup(entities); } inputDevices = devices; inputEntities = entities.filter((entity) => entity.area_id); diff --git a/src/components/ha-selector/ha-selector-area.ts b/src/components/ha-selector/ha-selector-area.ts index e8c2539295..54b91a662d 100644 --- a/src/components/ha-selector/ha-selector-area.ts +++ b/src/components/ha-selector/ha-selector-area.ts @@ -1,14 +1,10 @@ -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import type { DeviceRegistryEntry } from "../../data/device_registry"; import { getDeviceIntegrationLookup } from "../../data/device_registry"; -import { - EntityRegistryEntry, - subscribeEntityRegistry, -} from "../../data/entity_registry"; import { EntitySources, fetchEntitySourcesWithCache, @@ -18,13 +14,12 @@ import { filterSelectorDevices, filterSelectorEntities, } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../types"; import "../ha-area-picker"; import "../ha-areas-picker"; @customElement("ha-selector-area") -export class HaAreaSelector extends SubscribeMixin(LitElement) { +export class HaAreaSelector extends LitElement { @property() public hass!: HomeAssistant; @property() public selector!: AreaSelector; @@ -41,18 +36,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) { @state() private _entitySources?: EntitySources; - @state() private _entities?: EntityRegistryEntry[]; - private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entities = entities.filter((entity) => entity.device_id !== null); - }), - ]; - } - private _hasIntegration(selector: AreaSelector) { return ( (selector.area?.entity && @@ -127,10 +112,12 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) { return true; } - const deviceIntegrations = - this._entitySources && this._entities - ? this._deviceIntegrationLookup(this._entitySources, this._entities) - : undefined; + const deviceIntegrations = this._entitySources + ? this._deviceIntegrationLookup( + this._entitySources, + Object.values(this.hass.entities) + ) + : undefined; return ensureArray(this.selector.area.device).some((filter) => filterSelectorDevices(filter, device, deviceIntegrations) diff --git a/src/components/ha-selector/ha-selector-attribute.ts b/src/components/ha-selector/ha-selector-attribute.ts index da194bdf22..50e1367dbe 100644 --- a/src/components/ha-selector/ha-selector-attribute.ts +++ b/src/components/ha-selector/ha-selector-attribute.ts @@ -2,12 +2,11 @@ import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { AttributeSelector } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../types"; import "../entity/ha-entity-attribute-picker"; @customElement("ha-selector-attribute") -export class HaSelectorAttribute extends SubscribeMixin(LitElement) { +export class HaSelectorAttribute extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public selector!: AttributeSelector; diff --git a/src/components/ha-selector/ha-selector-device.ts b/src/components/ha-selector/ha-selector-device.ts index db080201c0..80eb481a9a 100644 --- a/src/components/ha-selector/ha-selector-device.ts +++ b/src/components/ha-selector/ha-selector-device.ts @@ -1,14 +1,10 @@ -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import type { DeviceRegistryEntry } from "../../data/device_registry"; import { getDeviceIntegrationLookup } from "../../data/device_registry"; -import { - EntityRegistryEntry, - subscribeEntityRegistry, -} from "../../data/entity_registry"; import { EntitySources, fetchEntitySourcesWithCache, @@ -18,21 +14,18 @@ import { filterSelectorDevices, filterSelectorEntities, } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../types"; import "../device/ha-device-picker"; import "../device/ha-devices-picker"; @customElement("ha-selector-device") -export class HaDeviceSelector extends SubscribeMixin(LitElement) { +export class HaDeviceSelector extends LitElement { @property() public hass!: HomeAssistant; @property() public selector!: DeviceSelector; @state() private _entitySources?: EntitySources; - @state() private _entities?: EntityRegistryEntry[]; - @property() public value?: any; @property() public label?: string; @@ -45,14 +38,6 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) { private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entities = entities.filter((entity) => entity.device_id !== null); - }), - ]; - } - private _hasIntegration(selector: DeviceSelector) { return ( (selector.device?.filter && @@ -118,10 +103,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) { if (!this.selector.device?.filter) { return true; } - const deviceIntegrations = - this._entitySources && this._entities - ? this._deviceIntegrationLookup(this._entitySources, this._entities) - : undefined; + const deviceIntegrations = this._entitySources + ? this._deviceIntegrationLookup( + this._entitySources, + Object.values(this.hass.entities) + ) + : undefined; return ensureArray(this.selector.device.filter).some((filter) => filterSelectorDevices(filter, device, deviceIntegrations) diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 200feca500..db1bd5705f 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -1,6 +1,7 @@ // @ts-ignore import chipStyles from "@material/chips/dist/mdc.chips.min.css"; import "@material/mwc-button/mwc-button"; +import "@material/mwc-menu/mwc-menu-surface"; import { mdiClose, mdiDevices, @@ -9,13 +10,14 @@ import { mdiUnfoldMoreVertical, } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; +import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light"; import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light"; import { ensureArray } from "../common/array/ensure-array"; import { fireEvent } from "../common/dom/fire_event"; +import { stopPropagation } from "../common/dom/stop_propagation"; import { computeDomain } from "../common/entity/compute_domain"; import { computeStateName } from "../common/entity/compute_state_name"; import { isValidEntityId } from "../common/entity/valid_entity_id"; @@ -23,7 +25,7 @@ import { computeDeviceName, DeviceRegistryEntry, } from "../data/device_registry"; -import { EntityRegistryEntry } from "../data/entity_registry"; +import { EntityRegistryDisplayEntry } from "../data/entity_registry"; import { HomeAssistant } from "../types"; import "./device/ha-device-picker"; import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; @@ -33,8 +35,6 @@ import "./ha-area-picker"; import "./ha-icon-button"; import "./ha-input-helper-text"; import "./ha-svg-icon"; -import { stopPropagation } from "../common/dom/stop_propagation"; -import "@material/mwc-menu/mwc-menu-surface"; @customElement("ha-target-picker") export class HaTargetPicker extends LitElement { @@ -551,7 +551,7 @@ export class HaTargetPicker extends LitElement { return true; } - private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean { + private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean { if (entity.entity_category) { return false; } diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 0174a12bf0..5c6cc532ec 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -6,6 +6,7 @@ import { mdiProgressWrench, mdiRecordCircleOutline, } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, @@ -14,12 +15,16 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time"; import { relativeTime } from "../../common/datetime/relative_time"; import { fireEvent } from "../../common/dom/fire_event"; import { toggleAttribute } from "../../common/dom/toggle_attribute"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../data/entity_registry"; import { LogbookEntry } from "../../data/logbook"; import { ChooseAction, @@ -193,6 +198,7 @@ class ActionRenderer { constructor( private hass: HomeAssistant, + private entityReg: EntityRegistryEntry[], private entries: TemplateResult[], private trace: AutomationTraceExtended, private logbookRenderer: LogbookRenderer, @@ -298,7 +304,7 @@ class ActionRenderer { this._renderEntry( path, - describeAction(this.hass, data, actionType), + describeAction(this.hass, this.entityReg, data, actionType), undefined, data.enabled === false ); @@ -441,7 +447,9 @@ class ActionRenderer { ) as RepeatAction; const disabled = repeatConfig.enabled === false; - const name = repeatConfig.alias || describeAction(this.hass, repeatConfig); + const name = + repeatConfig.alias || + describeAction(this.hass, this.entityReg, repeatConfig); this._renderEntry(repeatPath, name, undefined, disabled); @@ -577,6 +585,16 @@ export class HaAutomationTracer extends LitElement { @property({ type: Boolean }) public allowPick = false; + @state() private _entityReg: EntityRegistryEntry[] = []; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entityReg = entities; + }), + ]; + } + protected render(): TemplateResult { if (!this.trace) { return html``; @@ -592,6 +610,7 @@ export class HaAutomationTracer extends LitElement { ); const actionRenderer = new ActionRenderer( this.hass, + this._entityReg, entries, this.trace, logbookRenderer, diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index bc1437adfa..37685de007 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -4,7 +4,10 @@ import { computeStateName } from "../common/entity/compute_state_name"; import { caseInsensitiveStringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import type { HomeAssistant } from "../types"; -import type { EntityRegistryEntry } from "./entity_registry"; +import type { + EntityRegistryDisplayEntry, + EntityRegistryEntry, +} from "./entity_registry"; import type { EntitySources } from "./entity_sources"; export interface DeviceRegistryEntry { @@ -25,6 +28,10 @@ export interface DeviceRegistryEntry { configuration_url: string | null; } +export interface DeviceEntityDisplayLookup { + [deviceId: string]: EntityRegistryDisplayEntry[]; +} + export interface DeviceEntityLookup { [deviceId: string]: EntityRegistryEntry[]; } @@ -147,9 +154,25 @@ export const getDeviceEntityLookup = ( return deviceEntityLookup; }; +export const getDeviceEntityDisplayLookup = ( + entities: EntityRegistryDisplayEntry[] +): DeviceEntityDisplayLookup => { + const deviceEntityLookup: DeviceEntityDisplayLookup = {}; + 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); + } + return deviceEntityLookup; +}; + export const getDeviceIntegrationLookup = ( entitySources: EntitySources, - entities: EntityRegistryEntry[] + entities: EntityRegistryDisplayEntry[] ): Record => { const deviceIntegrations: Record = {}; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index ede328f7e1..d0e2afb2cd 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -6,6 +6,35 @@ import { caseInsensitiveStringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; +type entityCategory = "config" | "diagnostic"; + +export interface EntityRegistryDisplayEntry { + entity_id: string; + name?: string; + device_id?: string; + area_id?: string; + hidden?: boolean; + entity_category?: entityCategory; + translation_key?: string; + platform?: string; + display_precision?: number; +} + +interface EntityRegistryDisplayEntryResponse { + entities: { + ei: string; + di?: string; + ai?: string; + ec?: number; + en?: string; + pl?: string; + tk?: string; + hb?: boolean; + dp?: number; + }[]; + entity_categories: Record; +} + export interface EntityRegistryEntry { id: string; entity_id: string; @@ -17,7 +46,7 @@ export interface EntityRegistryEntry { area_id: string | null; disabled_by: "user" | "device" | "integration" | "config_entry" | null; hidden_by: Exclude; - entity_category: "config" | "diagnostic" | null; + entity_category: entityCategory | null; has_entity_name: boolean; original_name?: string; unique_id: string; @@ -154,6 +183,11 @@ export const fetchEntityRegistry = (conn: Connection) => type: "config/entity_registry/list", }); +export const fetchEntityRegistryDisplay = (conn: Connection) => + conn.sendMessagePromise({ + type: "config/entity_registry/list_for_display", + }); + const subscribeEntityRegistryUpdates = ( conn: Connection, store: Store @@ -182,6 +216,34 @@ export const subscribeEntityRegistry = ( onChange ); +const subscribeEntityRegistryDisplayUpdates = ( + conn: Connection, + store: Store +) => + conn.subscribeEvents( + debounce( + () => + fetchEntityRegistryDisplay(conn).then((entities) => + store.setState(entities, true) + ), + 500, + true + ), + "entity_registry_updated" + ); + +export const subscribeEntityRegistryDisplay = ( + conn: Connection, + onChange: (entities: EntityRegistryDisplayEntryResponse) => void +) => + createCollection( + "_entityRegistryDisplay", + fetchEntityRegistryDisplay, + subscribeEntityRegistryDisplayUpdates, + conn, + onChange + ); + export const sortEntityRegistryByName = ( entries: EntityRegistryEntry[], language: string @@ -190,10 +252,20 @@ export const sortEntityRegistryByName = ( caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) ); +export const entityRegistryByEntityId = memoizeOne( + (entries: EntityRegistryEntry[]) => { + const entities: Record = {}; + for (const entity of entries) { + entities[entity.entity_id] = entity; + } + return entities; + } +); + export const entityRegistryById = memoizeOne( - (entries: HomeAssistant["entities"]) => { - const entities: HomeAssistant["entities"] = {}; - for (const entity of Object.values(entries)) { + (entries: EntityRegistryEntry[]) => { + const entities: Record = {}; + for (const entity of entries) { entities[entity.id] = entity; } return entities; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 1d99a9c2d5..8d3c1f88b2 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -11,6 +11,7 @@ import { computeDeviceName } from "./device_registry"; import { computeEntityRegistryName, entityRegistryById, + EntityRegistryEntry, } from "./entity_registry"; import { domainToName } from "./integration"; import { @@ -33,6 +34,7 @@ import { export const describeAction = ( hass: HomeAssistant, + entityRegistry: EntityRegistryEntry[], action: ActionTypes[T], actionType?: T, ignoreAlias = false @@ -91,7 +93,7 @@ export const describeAction = ( targets.push(targetThing); } } else { - const entityReg = entityRegistryById(hass.entities)[targetThing]; + const entityReg = entityRegistryById(entityRegistry)[targetThing]; if (entityReg) { targets.push( computeEntityRegistryName(hass, entityReg) || targetThing diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 2cbf2bffc3..4f79d757c4 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -25,7 +25,11 @@ import "../../components/ha-icon-button"; import "../../components/ha-icon-button-prev"; import "../../components/ha-list-item"; import "../../components/ha-related-items"; -import { EntityRegistryEntry } from "../../data/entity_registry"; +import { + EntityRegistryEntry, + ExtEntityRegistryEntry, + getExtendedEntityRegistryEntry, +} from "../../data/entity_registry"; import { haStyleDialog } from "../../resources/styles"; import "../../state-summary/state-card-content"; import { HomeAssistant } from "../../types"; @@ -77,6 +81,8 @@ export class MoreInfoDialog extends LitElement { @state() private _childView?: ChildView; + @state() private _entry?: ExtEntityRegistryEntry; + public showDialog(params: MoreInfoDialogParams) { this._entityId = params.entityId; if (!this._entityId) { @@ -86,10 +92,22 @@ export class MoreInfoDialog extends LitElement { this._currView = params.view || "info"; this._childView = undefined; this.large = false; + this._loadEntityRegistryEntry(); + } + + private async _loadEntityRegistryEntry() { + if (!this._entityId) { + return; + } + this._entry = await getExtendedEntityRegistryEntry( + this.hass, + this._entityId + ); } public closeDialog() { this._entityId = undefined; + this._entry = undefined; this._childView = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -172,7 +190,10 @@ export class MoreInfoDialog extends LitElement { idToPassThroughUrl = stateObj.attributes.id; } if (EDITABLE_DOMAINS_WITH_UNIQUE_ID.includes(domain)) { - idToPassThroughUrl = this.hass.entities[this._entityId!].unique_id; + if (!this._entry) { + return; + } + idToPassThroughUrl = this._entry.unique_id; } navigate(`/config/${domain}/edit/${idToPassThroughUrl}`); @@ -358,6 +379,8 @@ export class MoreInfoDialog extends LitElement { ` : this._currView === "related" @@ -387,6 +410,10 @@ export class MoreInfoDialog extends LitElement { } } + private _entryUpdated(ev: CustomEvent) { + this._entry = ev.detail; + } + private _enlarge() { this.large = !this.large; } diff --git a/src/dialogs/more-info/ha-more-info-settings.ts b/src/dialogs/more-info/ha-more-info-settings.ts index 1f558305e1..54dbffcb4c 100644 --- a/src/dialogs/more-info/ha-more-info-settings.ts +++ b/src/dialogs/more-info/ha-more-info-settings.ts @@ -6,12 +6,11 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import { EntityRegistryEntry, ExtEntityRegistryEntry, - getExtendedEntityRegistryEntry, } from "../../data/entity_registry"; import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const"; +import "../../panels/config/entities/entity-registry-settings"; import type { HomeAssistant } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; -import "../../panels/config/entities/entity-registry-settings"; @customElement("ha-more-info-settings") export class HaMoreInfoSettings extends LitElement { @@ -19,18 +18,18 @@ export class HaMoreInfoSettings extends LitElement { @property({ attribute: false }) public entityId!: string; - @state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; + @state() private entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; @state() private _settingsElementTag?: string; protected render() { // loading. - if (this._entry === undefined) { + if (this.entry === undefined) { return html``; } // No unique ID - if (this._entry === null) { + if (this.entry === null) { return html`
${this.hass.localize( @@ -54,53 +53,31 @@ export class HaMoreInfoSettings extends LitElement { } return html` -
- ${dynamicElement(this._settingsElementTag, { - hass: this.hass, - entry: this._entry, - entityId: this.entityId, - })} -
+ ${dynamicElement(this._settingsElementTag, { + hass: this.hass, + entry: this.entry, + entityId: this.entityId, + })} `; } - protected willUpdate(changedProps: PropertyValues) { - super.willUpdate(changedProps); - if (changedProps.has("entityId")) { - this._entry = undefined; - if (this.entityId) { - this._getEntityReg(); - } - } - } - - private async _getEntityReg() { - try { - this._entry = await getExtendedEntityRegistryEntry( - this.hass, - this.entityId - ); + public willUpdate(changedProps: PropertyValues) { + if (changedProps.has("entry")) { this._loadPlatformSettingTabs(); - } catch { - this._entry = null; } } - private _entryUpdated(ev: CustomEvent) { - this._entry = ev.detail; - } - private async _loadPlatformSettingTabs(): Promise { - if (!this._entry) { + if (!this.entry) { return; } if ( - !Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform) + !Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this.entry.platform) ) { this._settingsElementTag = "entity-registry-settings"; return; } - const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform]; + const tag = PLATFORMS_WITH_SETTINGS_TAB[this.entry.platform]; await import(`../../panels/config/entities/editor-tabs/settings/${tag}`); this._settingsElementTag = tag; } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 71593afff5..b6474dc2ac 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -11,6 +11,7 @@ import { mdiStopCircleOutline, mdiSort, } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -26,6 +27,10 @@ import "../../../../components/ha-icon-button"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { ACTION_TYPES } from "../../../../data/action"; import { validateConfig } from "../../../../data/config"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../../data/entity_registry"; import { Action, getActionType } from "../../../../data/script"; import { describeAction } from "../../../../data/script_i18n"; import { callExecuteScript } from "../../../../data/service"; @@ -107,6 +112,8 @@ export default class HaAutomationActionRow extends LitElement { @property({ type: Boolean }) public reOrderMode = false; + @state() private _entityReg: EntityRegistryEntry[] = []; + @state() private _warnings?: string[]; @state() private _uiModeAvailable = true; @@ -115,6 +122,14 @@ export default class HaAutomationActionRow extends LitElement { @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entityReg = entities; + }), + ]; + } + protected willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("action")) { return; @@ -156,7 +171,9 @@ export default class HaAutomationActionRow extends LitElement { class="action-icon" .path=${ACTION_TYPES[type!]} > - ${capitalizeFirstLetter(describeAction(this.hass, this.action))} + ${capitalizeFirstLetter( + describeAction(this.hass, this._entityReg, this.action) + )} @@ -465,7 +482,7 @@ export default class HaAutomationActionRow extends LitElement { ), inputType: "string", placeholder: capitalizeFirstLetter( - describeAction(this.hass, this.action, undefined, true) + describeAction(this.hass, this._entityReg, this.action, undefined, true) ), defaultValue: this.action.alias, confirmText: this.hass.localize("ui.common.submit"), diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index c32dbdfb94..3000c3ba91 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -49,6 +49,7 @@ import { showAutomationEditor, triggerAutomationActions, } from "../../../data/automation"; +import { fetchEntityRegistry } from "../../../data/entity_registry"; import { showAlertDialog, showConfirmationDialog, @@ -479,7 +480,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { this._readOnly = false; this._config = this._normalizeConfig(config); } catch (err: any) { - const entity = Object.values(this.hass.entities).find( + const entityRegistry = await fetchEntityRegistry(this.hass.connection); + const entity = entityRegistry.find( (ent) => ent.platform === "automation" && ent.unique_id === this.automationId ); diff --git a/src/panels/config/script/ha-config-script.ts b/src/panels/config/script/ha-config-script.ts index 386b798506..5584075dea 100644 --- a/src/panels/config/script/ha-config-script.ts +++ b/src/panels/config/script/ha-config-script.ts @@ -1,14 +1,19 @@ -import { HassEntities } from "home-assistant-js-websocket"; +import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket"; import { PropertyValues } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { debounce } from "../../../common/util/debounce"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; import { ScriptEntity } from "../../../data/script"; import { HassRouterPage, RouterOptions, } from "../../../layouts/hass-router-page"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../types"; import "./ha-script-editor"; import "./ha-script-picker"; @@ -21,7 +26,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => { }; @customElement("ha-config-script") -class HaConfigScript extends HassRouterPage { +class HaConfigScript extends SubscribeMixin(HassRouterPage) { @property({ attribute: false }) public hass!: HomeAssistant; @property() public narrow!: boolean; @@ -32,6 +37,16 @@ class HaConfigScript extends HassRouterPage { @property() public scripts: ScriptEntity[] = []; + @state() private _entityReg: EntityRegistryEntry[] = []; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entityReg = entities; + }), + ]; + } + protected routerOptions: RouterOptions = { defaultPage: "dashboard", routes: { @@ -78,6 +93,7 @@ class HaConfigScript extends HassRouterPage { pageEl.isWide = this.isWide; pageEl.route = this.routeTail; pageEl.showAdvanced = this.showAdvanced; + pageEl.entityRegistry = this._entityReg; if (this.hass) { if (!pageEl.scripts || !changedProps) { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 40def1a659..9329a88aa6 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -39,6 +39,7 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import "../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../components/ha-yaml-editor"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; import { deleteScript, getScriptStateConfig, @@ -75,6 +76,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { @property({ type: Boolean }) public narrow!: boolean; + @property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[]; + @state() private _config?: ScriptConfig; @state() private _entityId?: string; @@ -431,7 +434,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { this._config = this._normalizeConfig(config); }, (resp) => { - const entity = Object.values(this.hass.entities).find( + const entity = this.entityRegistry.find( (ent) => ent.platform === "script" && ent.unique_id === this.scriptId ); @@ -477,7 +480,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { getScriptStateConfig(this.hass, this.entityId).then((c) => { this._config = this._normalizeConfig(c.config); }); - const regEntry = this.hass.entities[this.entityId]; + const regEntry = this.entityRegistry.find( + (ent) => ent.entity_id === this.entityId + ); if (regEntry?.unique_id) { this.scriptId = regEntry.unique_id; } @@ -544,7 +549,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { if (!this.scriptId) { return; } - const entity = Object.values(this.hass.entities).find( + const entity = this.entityRegistry.find( (entry) => entry.unique_id === this.scriptId ); if (!entity) { diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 98ee9f9d70..7b8ee39922 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -44,6 +44,7 @@ import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import { configSections } from "../ha-panel-config"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; @customElement("ha-script-picker") class HaScriptPicker extends LitElement { @@ -57,7 +58,9 @@ class HaScriptPicker extends LitElement { @property() public route!: Route; - @property() private _activeFilters?: string[]; + @property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[]; + + @state() private _activeFilters?: string[]; @state() private _filteredScripts?: string[] | null; @@ -266,7 +269,7 @@ class HaScriptPicker extends LitElement { } private _handleRowClicked(ev: HASSDomEvent) { - const entry = this.hass.entities[ev.detail.id]; + const entry = this.entityRegistry.find((e) => e.entity_id === ev.detail.id); if (entry) { navigate(`/config/script/edit/${entry.unique_id}`); } else { @@ -275,7 +278,12 @@ class HaScriptPicker extends LitElement { } private _runScript = async (script: any) => { - const entry = this.hass.entities[script.entity_id]; + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); + if (!entry) { + return; + } await triggerScript(this.hass, entry.unique_id); showToast(this, { message: this.hass.localize( @@ -291,7 +299,9 @@ class HaScriptPicker extends LitElement { } private _showTrace(script: any) { - const entry = this.hass.entities[script.entity_id]; + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); if (entry) { navigate(`/config/script/trace/${entry.unique_id}`); } @@ -317,7 +327,12 @@ class HaScriptPicker extends LitElement { private async _duplicate(script: any) { try { - const entry = this.hass.entities[script.entity_id]; + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); + if (!entry) { + return; + } const config = await fetchScriptFileConfig(this.hass, entry.unique_id); showScriptEditor({ ...config, @@ -362,8 +377,12 @@ class HaScriptPicker extends LitElement { private async _delete(script: any) { try { - const entry = this.hass.entities[script.entity_id]; - await deleteScript(this.hass, entry.unique_id); + const entry = this.entityRegistry.find( + (e) => e.entity_id === script.entity_id + ); + if (entry) { + await deleteScript(this.hass, entry.unique_id); + } } catch (err: any) { await showAlertDialog(this, { text: diff --git a/src/panels/config/script/ha-script-trace.ts b/src/panels/config/script/ha-script-trace.ts index a8addf7bd8..c8082dace5 100644 --- a/src/panels/config/script/ha-script-trace.ts +++ b/src/panels/config/script/ha-script-trace.ts @@ -39,6 +39,7 @@ import { HomeAssistant, Route } from "../../../types"; import "../../../layouts/hass-subpage"; import "../../../components/ha-button-menu"; import { fireEvent } from "../../../common/dom/fire_event"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; @customElement("ha-script-trace") export class HaScriptTrace extends LitElement { @@ -54,6 +55,8 @@ export class HaScriptTrace extends LitElement { @property({ attribute: false }) public route!: Route; + @property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[]; + @state() private _entityId?: string; @state() private _traces?: ScriptTrace[]; @@ -318,7 +321,7 @@ export class HaScriptTrace extends LitElement { const params = new URLSearchParams(location.search); this._loadTraces(params.get("run_id") || undefined); - this._entityId = Object.values(this.hass.entities).find( + this._entityId = this.entityRegistry.find( (entry) => entry.unique_id === this.scriptId )?.entity_id; } @@ -335,7 +338,7 @@ export class HaScriptTrace extends LitElement { if (this.scriptId) { this._loadTraces(); - this._entityId = Object.values(this.hass.entities).find( + this._entityId = this.entityRegistry.find( (entry) => entry.unique_id === this.scriptId )?.entity_id; } diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index ec243f8846..975cea74d3 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -130,7 +130,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { .min=${this._config.min!} .max=${this._config.max!} .value=${stateObj.state} - .formatOptions=${getNumberFormatOptions(stateObj)} + .formatOptions=${getNumberFormatOptions( + stateObj, + this.hass.entities[stateObj.entity_id] + )} .locale=${this.hass!.locale} .label=${this._config!.unit || this.hass?.states[this._config!.entity].attributes diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index f1343842c0..759e63ae92 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -278,8 +278,8 @@ const computeDefaultViewStates = ( .filter( (entry) => entry.entity_category || - HIDE_PLATFORM.has(entry.platform) || - entry.hidden_by + (entry.platform && HIDE_PLATFORM.has(entry.platform)) || + entry.hidden ) .map((entry) => entry.entity_id) ); diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index ca44abf7d5..f490f4b3e2 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -14,7 +14,7 @@ import { polyfillsLoaded } from "../common/translations/localize"; import { subscribeAreaRegistry } from "../data/area_registry"; import { broadcastConnectionStatus } from "../data/connection-status"; import { subscribeDeviceRegistry } from "../data/device_registry"; -import { subscribeEntityRegistry } from "../data/entity_registry"; +import { subscribeEntityRegistryDisplay } from "../data/entity_registry"; import { subscribeFrontendUserData } from "../data/frontend"; import { forwardHaptic } from "../data/haptics"; import { DEFAULT_PANEL } from "../data/panel"; @@ -188,10 +188,22 @@ export const connectionMixin = >( }); subscribeEntities(conn, (states) => this._updateHass({ states })); - subscribeEntityRegistry(conn, (entityReg) => { + subscribeEntityRegistryDisplay(conn, (entityReg) => { const entities: HomeAssistant["entities"] = {}; - for (const entity of entityReg) { - entities[entity.entity_id] = entity; + for (const entity of entityReg.entities) { + entities[entity.ei] = { + entity_id: entity.ei, + device_id: entity.di, + area_id: entity.ai, + translation_key: entity.tk, + platform: entity.pl, + entity_category: entity.ec + ? entityReg.entity_categories[entity.ec] + : undefined, + name: entity.en, + hidden: entity.hb, + display_precision: entity.dp, + }; } this._updateHass({ entities }); }); diff --git a/src/types.ts b/src/types.ts index 30f9139310..c0f0823baf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ import { import { LocalizeFunc } from "./common/translations/localize"; import { AreaRegistryEntry } from "./data/area_registry"; import { DeviceRegistryEntry } from "./data/device_registry"; -import { EntityRegistryEntry } from "./data/entity_registry"; +import { EntityRegistryDisplayEntry } from "./data/entity_registry"; import { CoreFrontendUserData } from "./data/frontend"; import { FrontendLocaleData, getHassTranslations } from "./data/translation"; import { Themes } from "./data/ws-themes"; @@ -189,7 +189,7 @@ export interface HomeAssistant { connection: Connection; connected: boolean; states: HassEntities; - entities: { [id: string]: EntityRegistryEntry }; + entities: { [id: string]: EntityRegistryDisplayEntry }; devices: { [id: string]: DeviceRegistryEntry }; areas: { [id: string]: AreaRegistryEntry }; services: HassServices; From 43541f9754ccb4bb26e4d5beae777566cb293eb0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 23 Feb 2023 16:35:25 +0100 Subject: [PATCH 11/14] Fix area picker (#15566) --- src/components/ha-area-picker.ts | 132 ++++++++++++++++--------------- 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 471ce71401..71ce5abe9b 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -142,95 +142,99 @@ export class HaAreaPicker extends LitElement { includeDomains || excludeDomains || includeDeviceClasses || + deviceFilter || entityFilter ) { deviceEntityLookup = getDeviceEntityDisplayLookup(entities); - } - inputDevices = devices; - inputEntities = entities.filter((entity) => entity.area_id); + inputDevices = devices; + inputEntities = entities.filter((entity) => entity.area_id); - if (includeDomains) { - inputDevices = inputDevices!.filter((device) => { - const devEntities = deviceEntityLookup[device.id]; - if (!devEntities || !devEntities.length) { - return false; - } - return deviceEntityLookup[device.id].some((entity) => + 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)) + ); + }); + inputEntities = inputEntities!.filter((entity) => includeDomains.includes(computeDomain(entity.entity_id)) ); - }); - inputEntities = inputEntities!.filter((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( + 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)) + ); + }); + inputEntities = inputEntities!.filter( (entity) => !excludeDomains.includes(computeDomain(entity.entity_id)) ); - }); - inputEntities = inputEntities!.filter( - (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) { + 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) + ); + }); + }); + inputEntities = inputEntities!.filter((entity) => { + const stateObj = this.hass.states[entity.entity_id]; return ( stateObj.attributes.device_class && includeDeviceClasses.includes(stateObj.attributes.device_class) ); }); - }); - inputEntities = inputEntities!.filter((entity) => { - const stateObj = this.hass.states[entity.entity_id]; - return ( - stateObj.attributes.device_class && - includeDeviceClasses.includes(stateObj.attributes.device_class) + } + + if (deviceFilter) { + inputDevices = inputDevices!.filter((device) => + deviceFilter!(device) ); - }); - } + } - if (deviceFilter) { - inputDevices = inputDevices!.filter((device) => deviceFilter!(device)); - } - - if (entityFilter) { - inputDevices = inputDevices!.filter((device) => { - const devEntities = deviceEntityLookup[device.id]; - if (!devEntities || !devEntities.length) { - return false; - } - return deviceEntityLookup[device.id].some((entity) => { + if (entityFilter) { + 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 entityFilter(stateObj); + }); + }); + inputEntities = inputEntities!.filter((entity) => { const stateObj = this.hass.states[entity.entity_id]; if (!stateObj) { return false; } - return entityFilter(stateObj); + return entityFilter!(stateObj); }); - }); - inputEntities = inputEntities!.filter((entity) => { - const stateObj = this.hass.states[entity.entity_id]; - if (!stateObj) { - return false; - } - return entityFilter!(stateObj); - }); + } } let outputAreas = areas; From 1095088d42f1ce2de2bf82665af587c861370a63 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 23 Feb 2023 17:42:15 +0100 Subject: [PATCH 12/14] Bumped version to 20230223.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01692958c7..518a24ff18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20230222.0" +version = "20230223.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 96a261d8317c045c6dec23bb793ef7f2f51743ca Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Feb 2023 18:13:03 +0100 Subject: [PATCH 13/14] Add swipe gesture to control switch (#15567) * Add swipe gesture to control switch * Update src/components/ha-control-switch.ts Co-authored-by: Bram Kragten * Only add necessary listener --------- Co-authored-by: Bram Kragten --- src/components/ha-control-switch.ts | 76 +++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/src/components/ha-control-switch.ts b/src/components/ha-control-switch.ts index b2a05073ce..ae15ce98b7 100644 --- a/src/components/ha-control-switch.ts +++ b/src/components/ha-control-switch.ts @@ -1,3 +1,10 @@ +import { + DIRECTION_HORIZONTAL, + DIRECTION_VERTICAL, + Manager, + Swipe, + Tap, +} from "@egjs/hammerjs"; import { css, CSSResultGroup, @@ -6,7 +13,7 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import "./ha-svg-icon"; @@ -30,8 +37,11 @@ export class HaControlSwitch extends LitElement { // SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an in) @property({ type: String }) pathOff?: string; + private _mc?: HammerManager; + protected firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); + this.setupListeners(); this.setAttribute("role", "switch"); if (!this.hasAttribute("tabindex")) { this.setAttribute("tabindex", "0"); @@ -53,14 +63,70 @@ export class HaControlSwitch extends LitElement { connectedCallback(): void { super.connectedCallback(); - this.addEventListener("keydown", this._keydown); - this.addEventListener("click", this._toggle); + this.setupListeners(); } disconnectedCallback(): void { super.disconnectedCallback(); + this.destroyListeners(); + } + + @query("#switch") + private switch!: HTMLDivElement; + + setupListeners() { + if (this.switch && !this._mc) { + this._mc = new Manager(this.switch, { + touchAction: this.vertical ? "pan-x" : "pan-y", + }); + this._mc.add( + new Swipe({ + direction: this.vertical ? DIRECTION_VERTICAL : DIRECTION_HORIZONTAL, + }) + ); + + this._mc.add(new Tap({ event: "singletap" })); + + if (this.vertical) { + this._mc.on("swipeup", () => { + if (this.disabled) return; + this.checked = !!this.reversed; + fireEvent(this, "change"); + }); + + this._mc.on("swipedown", () => { + if (this.disabled) return; + this.checked = !this.reversed; + fireEvent(this, "change"); + }); + } else { + this._mc.on("swiperight", () => { + if (this.disabled) return; + this.checked = !this.reversed; + fireEvent(this, "change"); + }); + + this._mc.on("swipeleft", () => { + if (this.disabled) return; + this.checked = !!this.reversed; + fireEvent(this, "change"); + }); + } + + this._mc.on("singletap", () => { + if (this.disabled) return; + this._toggle(); + }); + this.addEventListener("keydown", this._keydown); + } + } + + destroyListeners() { + if (this._mc) { + this._mc.destroy(); + this._mc = undefined; + } this.removeEventListener("keydown", this._keydown); - this.removeEventListener("click", this._toggle); } private _keydown(ev: any) { @@ -73,7 +139,7 @@ export class HaControlSwitch extends LitElement { protected render(): TemplateResult { return html` -
+