From 297c72122999d92edb4b73098d3fc8c334d3c865 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 7 Feb 2024 15:24:14 +0100 Subject: [PATCH 01/12] Matter cleanup on close dialog (#19714) --- .../integration-panels/matter/dialog-matter-ping-node.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts index 738523d16d..63aec257bf 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts @@ -146,6 +146,7 @@ class DialogMatterPingNode extends LitElement { public closeDialog(): void { this.device_id = undefined; this._status = undefined; + this._pingResult = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } From 6671d24fa697ecd6c7bde0e8ddf3383875b140f7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 7 Feb 2024 15:52:04 +0100 Subject: [PATCH 02/12] Improve matter ping dialog (#19715) --- .../matter/dialog-matter-ping-node.ts | 64 ++++++++----------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts index 63aec257bf..77ec91ee24 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-ping-node.ts @@ -1,12 +1,12 @@ import "@material/mwc-button/mwc-button"; -import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js"; +import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-circular-progress"; import { createCloseHeading } from "../../../../../components/ha-dialog"; import { pingMatterNode, MatterPingResult } from "../../../../../data/matter"; -import { haStyleDialog } from "../../../../../resources/styles"; +import { haStyle, haStyleDialog } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import { MatterPingNodeDialogParams } from "./show-dialog-matter-ping-node"; @@ -40,33 +40,24 @@ class DialogMatterPingNode extends LitElement { > ${this._pingResult ? html` -
- -
-

- ${this.hass.localize( - "ui.panel.config.matter.ping_node.ping_complete" - )} -

-
-
-
- - ${Object.entries(this._pingResult).map( - ([ip, success]) => - html`${ip} - - ` - )} - -
+

+ ${this.hass.localize( + "ui.panel.config.matter.ping_node.ping_complete" + )} +

+ + ${Object.entries(this._pingResult).map( + ([ip, success]) => + html`${ip} + + ` + )} + ${this.hass.localize("ui.common.close")} @@ -146,12 +137,12 @@ class DialogMatterPingNode extends LitElement { public closeDialog(): void { this.device_id = undefined; this._status = undefined; - this._pingResult = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } static get styles(): CSSResultGroup { return [ + haStyle, haStyleDialog, css` .success { @@ -171,23 +162,22 @@ class DialogMatterPingNode extends LitElement { margin-top: 16px; } - .stage ha-svg-icon { - width: 16px; - height: 16px; - } .stage { padding: 8px; } - ha-svg-icon { - width: 68px; - height: 48px; + mwc-list { + --mdc-list-side-padding: 0; } .flex-container ha-circular-progress, .flex-container ha-svg-icon { margin-right: 20px; } + .flex-container ha-svg-icon { + width: 68px; + height: 48px; + } `, ]; } From 989057d947178ba4fac1eec76af5bd58c1e52469 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 7 Feb 2024 19:42:39 +0100 Subject: [PATCH 03/12] Show icon of disabled entities (#19717) --- .../entity-registry-settings-editor.ts | 15 +++++----- .../config/entities/ha-config-entities.ts | 28 +++++++++++++------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/panels/config/entities/entity-registry-settings-editor.ts b/src/panels/config/entities/entity-registry-settings-editor.ts index 41b70f4875..03102930af 100644 --- a/src/panels/config/entities/entity-registry-settings-editor.ts +++ b/src/panels/config/entities/entity-registry-settings-editor.ts @@ -31,11 +31,11 @@ import "../../../components/ha-area-picker"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button-next"; import "../../../components/ha-icon-picker"; -import "../../../components/ha-state-icon"; import "../../../components/ha-list-item"; import "../../../components/ha-radio"; import "../../../components/ha-select"; import "../../../components/ha-settings-row"; +import "../../../components/ha-state-icon"; import "../../../components/ha-switch"; import type { HaSwitch } from "../../../components/ha-switch"; import "../../../components/ha-textfield"; @@ -52,10 +52,6 @@ import { createConfigFlow, handleConfigFlowStep, } from "../../../data/config_flow"; -import { - createOptionsFlow, - handleOptionsFlowStep, -} from "../../../data/options_flow"; import { DataEntryFlowStepCreateEntry } from "../../../data/data_entry_flow"; import { DeviceRegistryEntry, @@ -70,9 +66,13 @@ import { subscribeEntityRegistry, updateEntityRegistryEntry, } from "../../../data/entity_registry"; -import { entityIcon } from "../../../data/icons"; +import { entityIcon, entryIcon } from "../../../data/icons"; import { domainToName } from "../../../data/integration"; import { getNumberDeviceClassConvertibleUnits } from "../../../data/number"; +import { + createOptionsFlow, + handleOptionsFlowStep, +} from "../../../data/options_flow"; import { getSensorDeviceClassConvertibleUnits, getSensorNumericDeviceClasses, @@ -392,7 +392,8 @@ export class EntityRegistrySettingsEditor extends LitElement { )} .placeholder=${this.entry.original_icon || stateObj?.attributes.icon || - (stateObj && until(entityIcon(this.hass, stateObj)))} + (stateObj && until(entityIcon(this.hass, stateObj))) || + until(entryIcon(this.hass, this.entry))} .disabled=${this.disabled} > ${!this._icon && !stateObj?.attributes.icon && stateObj diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 0599d1e75e..6ca9b07bcc 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -25,6 +25,7 @@ import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; +import { until } from "lit/directives/until"; import memoize from "memoize-one"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -42,6 +43,7 @@ import type { } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-button-menu"; import "../../../components/ha-check-list-item"; +import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; @@ -53,6 +55,7 @@ import { removeEntityRegistryEntry, updateEntityRegistryEntry, } from "../../../data/entity_registry"; +import { entryIcon } from "../../../data/icons"; import { domainToName } from "../../../data/integration"; import { showAlertDialog, @@ -207,14 +210,23 @@ export class HaConfigEntities extends LitElement { title: "", label: localize("ui.panel.config.entities.picker.headers.state_icon"), type: "icon", - template: (entry) => html` - - `, + template: (entry) => + entry.icon + ? html` + + ` + : html` + + `, }, name: { main: true, From 4b768f06354e66c46e0890c0ba119284be24c1ba Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 8 Feb 2024 12:04:23 +0100 Subject: [PATCH 04/12] Use rgb theme variables for qrcode (#19726) --- src/components/ha-qr-code.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/components/ha-qr-code.ts b/src/components/ha-qr-code.ts index 47190c180c..ce9109e1ed 100644 --- a/src/components/ha-qr-code.ts +++ b/src/components/ha-qr-code.ts @@ -2,6 +2,7 @@ import { LitElement, PropertyValues, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import QRCode from "qrcode"; import "./ha-alert"; +import { rgb2hex } from "../common/color/convert-color"; @customElement("ha-qr-code") export class HaQrCode extends LitElement { @@ -65,6 +66,26 @@ export class HaQrCode extends LitElement { changedProperties.has("centerImage")) ) { const computedStyles = getComputedStyle(this); + const textRgb = computedStyles.getPropertyValue( + "--rgb-primary-text-color" + ); + const backgroundRgb = computedStyles.getPropertyValue( + "--rgb-card-background-color" + ); + const textHex = rgb2hex( + textRgb.split(",").map((a) => parseInt(a, 10)) as [ + number, + number, + number, + ] + ); + const backgroundHex = rgb2hex( + backgroundRgb.split(",").map((a) => parseInt(a, 10)) as [ + number, + number, + number, + ] + ); QRCode.toCanvas(canvas, this.data, { errorCorrectionLevel: @@ -74,8 +95,8 @@ export class HaQrCode extends LitElement { margin: this.margin, maskPattern: this.maskPattern, color: { - light: computedStyles.getPropertyValue("--card-background-color"), - dark: computedStyles.getPropertyValue("--primary-text-color"), + light: backgroundHex, + dark: textHex, }, }).catch((err) => { this._error = err.message; From cc1658cbab1721faa061843162264b85d2061b1f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Feb 2024 13:33:10 +0100 Subject: [PATCH 05/12] Add service icons to traces (again :D) (#19728) * Add service icons to traces (again :D) * Update hat-graph-node.ts --- .../src/pages/automation/trace-timeline.ts | 1 - src/components/trace/hat-graph-node.ts | 17 ++++++++++---- src/components/trace/hat-script-graph.ts | 22 ++++++++++++++----- .../config/automation/ha-automation-trace.ts | 1 + src/panels/config/script/ha-script-trace.ts | 1 + 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/gallery/src/pages/automation/trace-timeline.ts b/gallery/src/pages/automation/trace-timeline.ts index 638da986e0..a9a0e1a9d5 100644 --- a/gallery/src/pages/automation/trace-timeline.ts +++ b/gallery/src/pages/automation/trace-timeline.ts @@ -3,7 +3,6 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import "../../../../src/components/ha-card"; -import "../../../../src/components/trace/hat-script-graph"; import "../../../../src/components/trace/hat-trace-timeline"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import { HomeAssistant } from "../../../../src/types"; diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index b9b6a499f0..4da3f7b7c3 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -5,6 +5,7 @@ import { html, TemplateResult, svg, + nothing, } from "lit"; import { customElement, property } from "lit/decorators"; import { NODE_SIZE, SPACING } from "./hat-graph-const"; @@ -51,7 +52,7 @@ export class HatGraphNode extends LitElement { : Math.ceil((NODE_SIZE + SPACING * 2) / 2)} ${width} ${height}" > ${this.graphStart - ? `` + ? nothing : svg` - } ${this.badge ? svg` @@ -81,9 +81,11 @@ export class HatGraphNode extends LitElement { >${this.badge > 9 ? "9+" : this.badge} ` - : ""} + : nothing} - ${this.iconPath ? svg`` : ""} + ${this.iconPath + ? svg`` + : svg``} @@ -152,6 +154,13 @@ export class HatGraphNode extends LitElement { path.icon { fill: var(--icon-clr); } + foreignObject { + width: 24px; + height: 24px; + } + .icon { + color: var(--icon-clr); + } `; } } diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 29aad80564..7bc0cbc0d9 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -17,11 +17,10 @@ import { mdiRoomService, mdiShuffleDisabled, } from "@mdi/js"; -import { LitElement, PropertyValues, css, html } from "lit"; +import { LitElement, PropertyValues, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { ensureArray } from "../../common/array/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; -import { ACTION_ICONS } from "../../data/action"; import { Condition, Trigger } from "../../data/automation"; import { Action, @@ -41,11 +40,14 @@ import { IfActionTraceStep, TraceExtended, } from "../../data/trace"; +import { HomeAssistant } from "../../types"; import "../ha-icon-button"; +import "../ha-service-icon"; import "./hat-graph-branch"; import { BRANCH_HEIGHT, NODE_SIZE, SPACING } from "./hat-graph-const"; import "./hat-graph-node"; import "./hat-graph-spacer"; +import { ACTION_ICONS } from "../../data/action"; export interface NodeInfo { path: string; @@ -64,6 +66,8 @@ export class HatScriptGraph extends LitElement { @property({ attribute: false }) public selected?: string; + public hass!: HomeAssistant; + public renderedNodes: Record = {}; public trackedNodes: Record = {}; @@ -415,13 +419,21 @@ export class HatScriptGraph extends LitElement { return html` + > + ${node.service + ? html`` + : nothing} + `; } @@ -667,8 +679,6 @@ export class HatScriptGraph extends LitElement { } .parent { margin-left: 8px; - margin-inline-start: 8px; - margin-inline-end: initial; margin-top: 16px; } .error { diff --git a/src/panels/config/automation/ha-automation-trace.ts b/src/panels/config/automation/ha-automation-trace.ts index c8188bdcaa..c752e562ed 100644 --- a/src/panels/config/automation/ha-automation-trace.ts +++ b/src/panels/config/automation/ha-automation-trace.ts @@ -232,6 +232,7 @@ export class HaAutomationTrace extends LitElement {
Date: Thu, 8 Feb 2024 15:38:55 +0100 Subject: [PATCH 06/12] Fix suggest card dialog (#19735) --- .../lovelace/editor/card-editor/hui-dialog-create-card.ts | 7 +++++++ .../editor/card-editor/show-suggest-card-dialog.ts | 8 ++++---- .../editor/unused-entities/hui-unused-entities.ts | 8 ++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index 1752a24174..4ce93735d4 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -21,6 +21,7 @@ import "./hui-entity-picker-table"; import { CreateCardDialogParams } from "./show-create-card-dialog"; import { showEditCardDialog } from "./show-edit-card-dialog"; import { showSuggestCardDialog } from "./show-suggest-card-dialog"; +import { computeCards } from "../../common/generate-lovelace-config"; declare global { interface HASSDomEvents { @@ -242,11 +243,17 @@ export class HuiCreateDialogCard } private _suggestCards(): void { + const cardConfig = computeCards( + this.hass.states, + this._selectedEntities, + {} + ); showSuggestCardDialog(this, { lovelaceConfig: this._params!.lovelaceConfig, saveConfig: this._params!.saveConfig, path: this._params!.path as [number], entities: this._selectedEntities, + cardConfig, }); this.closeDialog(); diff --git a/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts index de8021649d..ea43443d06 100644 --- a/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-suggest-card-dialog.ts @@ -7,11 +7,11 @@ export interface SuggestCardDialogParams { yaml?: boolean; saveConfig?: (config: LovelaceConfig) => void; path?: [number]; - entities?: string[]; // Entities used to generate the card config. We pass this to create dialog when user chooses "Pick own" - cardConfig?: LovelaceCardConfig[]; // We can pass a suggested config + entities?: string[]; // We pass this to create dialog when user chooses "Pick own" + cardConfig: LovelaceCardConfig[]; // We can pass a suggested config } -const importsuggestCardDialog = () => import("./hui-dialog-suggest-card"); +const importSuggestCardDialog = () => import("./hui-dialog-suggest-card"); export const showSuggestCardDialog = ( element: HTMLElement, @@ -19,7 +19,7 @@ export const showSuggestCardDialog = ( ): void => { fireEvent(element, "show-dialog", { dialogTag: "hui-dialog-suggest-card", - dialogImport: importsuggestCardDialog, + dialogImport: importSuggestCardDialog, dialogParams: suggestCardDialogParams, }); }; diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index 18ce15b683..98cd3f3163 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -21,6 +21,7 @@ import "../card-editor/hui-entity-picker-table"; import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog"; import { showSelectViewDialog } from "../select-view/show-select-view-dialog"; import { LovelaceConfig } from "../../../../data/lovelace/config/types"; +import { computeCards } from "../../common/generate-lovelace-config"; @customElement("hui-unused-entities") export class HuiUnusedEntities extends LitElement { @@ -126,12 +127,18 @@ export class HuiUnusedEntities extends LitElement { } private _addToLovelaceView(): void { + const cardConfig = computeCards( + this.hass.states, + this._selectedEntities, + {} + ); if (this.lovelace.config.views.length === 1) { showSuggestCardDialog(this, { lovelaceConfig: this.lovelace.config!, saveConfig: this.lovelace.saveConfig, path: [0], entities: this._selectedEntities, + cardConfig, }); return; } @@ -144,6 +151,7 @@ export class HuiUnusedEntities extends LitElement { saveConfig: this.lovelace.saveConfig, path: [viewIndex], entities: this._selectedEntities, + cardConfig, }); }, }); From e26c7c491a4dcdf3ac69ac5032bce77cfd2b60c8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 8 Feb 2024 15:40:03 +0100 Subject: [PATCH 07/12] Fix demo dashboard (#19734) --- demo/src/ha-demo.ts | 2 + demo/src/stubs/sensor.ts | 58 +++++++++++++++++++ demo/src/stubs/todo.ts | 1 + .../hui-input-select-entity-row.ts | 16 +++-- 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 demo/src/stubs/sensor.ts diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index 17550a6af6..0a9b131556 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -23,6 +23,7 @@ import { mockMediaPlayer } from "./stubs/media_player"; import { mockPersistentNotification } from "./stubs/persistent_notification"; import { mockRecorder } from "./stubs/recorder"; import { mockTodo } from "./stubs/todo"; +import { mockSensor } from "./stubs/sensor"; import { mockSystemLog } from "./stubs/system_log"; import { mockTemplate } from "./stubs/template"; import { mockTranslations } from "./stubs/translations"; @@ -50,6 +51,7 @@ export class HaDemo extends HomeAssistantAppEl { mockHistory(hass); mockRecorder(hass); mockTodo(hass); + mockSensor(hass); mockSystemLog(hass); mockTemplate(hass); mockEvents(hass); diff --git a/demo/src/stubs/sensor.ts b/demo/src/stubs/sensor.ts new file mode 100644 index 0000000000..19c9f2e344 --- /dev/null +++ b/demo/src/stubs/sensor.ts @@ -0,0 +1,58 @@ +import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; + +export const mockSensor = (hass: MockHomeAssistant) => { + hass.mockWS("sensor/numeric_device_classes", () => [ + { + numeric_device_classes: [ + "volume_storage", + "gas", + "data_size", + "irradiance", + "wind_speed", + "volatile_organic_compounds", + "volatile_organic_compounds_parts", + "voltage", + "frequency", + "precipitation_intensity", + "volume", + "precipitation", + "battery", + "nitrogen_dioxide", + "speed", + "signal_strength", + "pm1", + "nitrous_oxide", + "atmospheric_pressure", + "data_rate", + "temperature", + "power_factor", + "aqi", + "current", + "volume_flow_rate", + "humidity", + "duration", + "ozone", + "distance", + "pressure", + "pm25", + "weight", + "energy", + "carbon_monoxide", + "apparent_power", + "illuminance", + "energy_storage", + "moisture", + "power", + "water", + "carbon_dioxide", + "ph", + "reactive_power", + "monetary", + "nitrogen_monoxide", + "pm10", + "sound_pressure", + "sulphur_dioxide", + ], + }, + ]); +}; diff --git a/demo/src/stubs/todo.ts b/demo/src/stubs/todo.ts index b0393f6c88..71d49cdede 100644 --- a/demo/src/stubs/todo.ts +++ b/demo/src/stubs/todo.ts @@ -21,4 +21,5 @@ export const mockTodo = (hass: MockHomeAssistant) => { }, ] as TodoItem[], })); + hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {}); }; diff --git a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts index 4dbfd6f6bf..2d4f6e5bf3 100644 --- a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts @@ -40,14 +40,20 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { protected updated(changedProps: PropertyValues) { super.updated(changedProps); + if (!this._config) { + return; + } if (changedProps.has("hass")) { const oldHass = changedProps.get("hass"); + const stateObj = this.hass?.states[this._config.entity] as + | InputSelectEntity + | undefined; + const oldStateObj = oldHass?.states[this._config.entity] as + | InputSelectEntity + | undefined; if ( - this.hass && - oldHass && - this._config?.entity && - this.hass.states[this._config.entity].attributes.options !== - oldHass.states[this._config.entity].attributes.options + stateObj && + stateObj.attributes.options !== oldStateObj?.attributes.options ) { this._haSelect.layoutOptions(); } From bad18da658889ba68c4682349dfdc75a192296a0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Feb 2024 17:04:05 +0100 Subject: [PATCH 08/12] Fix icons in gallery, demo and cast (#19732) * Fix icons in gallery and demo * Add to lovelace gallery * Update icons.ts * add BACKWARDS_COMPAT support for icons * Update entity-state.ts * Update icons.ts * Update icons.ts --- demo/src/ha-demo.ts | 2 + demo/src/stubs/icons.ts | 33 + .../src/pages/lovelace/alarm-panel-card.ts | 2 + gallery/src/pages/lovelace/area-card.ts | 2 + .../src/pages/lovelace/conditional-card.ts | 2 + gallery/src/pages/lovelace/entities-card.ts | 2 + .../src/pages/lovelace/entity-button-card.ts | 2 + .../src/pages/lovelace/entity-filter-card.ts | 2 + gallery/src/pages/lovelace/gauge-card.ts | 2 + gallery/src/pages/lovelace/glance-card.ts | 2 + .../src/pages/lovelace/grid-and-stack-card.ts | 2 + gallery/src/pages/lovelace/light-card.ts | 2 + .../pages/lovelace/picture-elements-card.ts | 2 + .../src/pages/lovelace/picture-entity-card.ts | 2 + .../src/pages/lovelace/picture-glance-card.ts | 2 + gallery/src/pages/lovelace/plant-card.ts | 2 + gallery/src/pages/lovelace/thermostat-card.ts | 2 + gallery/src/pages/lovelace/tile-card.ts | 2 + gallery/src/pages/lovelace/todo-list-card.ts | 2 + gallery/src/pages/misc/entity-state.ts | 12 + src/data/icons.ts | 23 +- src/fake_data/demo_config.ts | 48 +- src/fake_data/entity_component_icons.ts | 962 ++++++++++++++++++ 23 files changed, 1109 insertions(+), 5 deletions(-) create mode 100644 demo/src/stubs/icons.ts create mode 100644 src/fake_data/entity_component_icons.ts diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index 0a9b131556..4b4d919d2b 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -17,6 +17,7 @@ import { energyEntities } from "./stubs/entities"; import { mockEntityRegistry } from "./stubs/entity_registry"; import { mockEvents } from "./stubs/events"; import { mockFrontend } from "./stubs/frontend"; +import { mockIcons } from "./stubs/icons"; import { mockHistory } from "./stubs/history"; import { mockLovelace } from "./stubs/lovelace"; import { mockMediaPlayer } from "./stubs/media_player"; @@ -57,6 +58,7 @@ export class HaDemo extends HomeAssistantAppEl { mockEvents(hass); mockMediaPlayer(hass); mockFrontend(hass); + mockIcons(hass); mockEnergy(hass); mockPersistentNotification(hass); mockConfigEntries(hass); diff --git a/demo/src/stubs/icons.ts b/demo/src/stubs/icons.ts new file mode 100644 index 0000000000..930a245d52 --- /dev/null +++ b/demo/src/stubs/icons.ts @@ -0,0 +1,33 @@ +import { IconCategory } from "../../../src/data/icons"; +import { ENTITY_COMPONENT_ICONS } from "../../../src/fake_data/entity_component_icons"; +import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; + +export const mockIcons = (hass: MockHomeAssistant) => { + hass.mockWS( + "frontend/get_icons", + async ({ + category, + integration, + }: { + category: IconCategory; + integration?: string; + }) => { + if (integration) { + try { + const response = await fetch( + `https://raw.githubusercontent.com/home-assistant/core/dev/homeassistant/components/${integration}/icons.json` + ).then((resp) => resp.json()); + return { resources: { [integration]: response[category] || {} } }; + } catch { + return { resources: {} }; + } + } + if (category === "entity_component") { + return { + resources: ENTITY_COMPONENT_ICONS, + }; + } + return { resources: {} }; + } + ); +}; diff --git a/gallery/src/pages/lovelace/alarm-panel-card.ts b/gallery/src/pages/lovelace/alarm-panel-card.ts index b55b1ca5d1..45a4c20b68 100644 --- a/gallery/src/pages/lovelace/alarm-panel-card.ts +++ b/gallery/src/pages/lovelace/alarm-panel-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("alarm_control_panel", "alarm", "disarmed", { @@ -84,6 +85,7 @@ class DemoAlarmPanelEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/area-card.ts b/gallery/src/pages/lovelace/area-card.ts index 30f98b07de..ea7afd7db9 100644 --- a/gallery/src/pages/lovelace/area-card.ts +++ b/gallery/src/pages/lovelace/area-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -146,6 +147,7 @@ class DemoArea extends LitElement { entity_id: "binary_sensor.kitchen_door", }, ]); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/conditional-card.ts b/gallery/src/pages/lovelace/conditional-card.ts index bdaf093ed3..b59d5b3bad 100644 --- a/gallery/src/pages/lovelace/conditional-card.ts +++ b/gallery/src/pages/lovelace/conditional-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "controller_1", "on", { @@ -66,6 +67,7 @@ class DemoConditional extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/entities-card.ts b/gallery/src/pages/lovelace/entities-card.ts index aa6368878d..a7131557e6 100644 --- a/gallery/src/pages/lovelace/entities-card.ts +++ b/gallery/src/pages/lovelace/entities-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -323,6 +324,7 @@ class DemoEntities extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/entity-button-card.ts b/gallery/src/pages/lovelace/entity-button-card.ts index a90f7c776d..daa6071571 100644 --- a/gallery/src/pages/lovelace/entity-button-card.ts +++ b/gallery/src/pages/lovelace/entity-button-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -82,6 +83,7 @@ class DemoButtonEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/entity-filter-card.ts b/gallery/src/pages/lovelace/entity-filter-card.ts index 879cf3bb71..86b0b9cb93 100644 --- a/gallery/src/pages/lovelace/entity-filter-card.ts +++ b/gallery/src/pages/lovelace/entity-filter-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("device_tracker", "demo_paulus", "work", { @@ -123,6 +124,7 @@ class DemoEntityFilter extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/gauge-card.ts b/gallery/src/pages/lovelace/gauge-card.ts index ae99d1b1be..43d3e60d19 100644 --- a/gallery/src/pages/lovelace/gauge-card.ts +++ b/gallery/src/pages/lovelace/gauge-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("sensor", "brightness", "12", {}), @@ -128,6 +129,7 @@ class DemoGaugeEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/glance-card.ts b/gallery/src/pages/lovelace/glance-card.ts index c2a7c22ece..ab2f29d974 100644 --- a/gallery/src/pages/lovelace/glance-card.ts +++ b/gallery/src/pages/lovelace/glance-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("device_tracker", "demo_paulus", "home", { @@ -238,6 +239,7 @@ class DemoGlanceEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/grid-and-stack-card.ts b/gallery/src/pages/lovelace/grid-and-stack-card.ts index 9fc7d6f5e7..bac8a6f157 100644 --- a/gallery/src/pages/lovelace/grid-and-stack-card.ts +++ b/gallery/src/pages/lovelace/grid-and-stack-card.ts @@ -4,6 +4,7 @@ import { mockHistory } from "../../../../demo/src/stubs/history"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "kitchen_lights", "on", { @@ -214,6 +215,7 @@ class DemoStack extends LitElement { hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); mockHistory(hass); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/light-card.ts b/gallery/src/pages/lovelace/light-card.ts index 3b0fc01011..1590edce0f 100644 --- a/gallery/src/pages/lovelace/light-card.ts +++ b/gallery/src/pages/lovelace/light-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -76,6 +77,7 @@ class DemoLightEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/picture-elements-card.ts b/gallery/src/pages/lovelace/picture-elements-card.ts index 588d6be9bf..7f6b0c99cb 100644 --- a/gallery/src/pages/lovelace/picture-elements-card.ts +++ b/gallery/src/pages/lovelace/picture-elements-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -138,6 +139,7 @@ class DemoPictureElements extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/picture-entity-card.ts b/gallery/src/pages/lovelace/picture-entity-card.ts index b88d97cbce..1573f0dbec 100644 --- a/gallery/src/pages/lovelace/picture-entity-card.ts +++ b/gallery/src/pages/lovelace/picture-entity-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("light", "kitchen_lights", "on", { @@ -93,6 +94,7 @@ class DemoPictureEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/picture-glance-card.ts b/gallery/src/pages/lovelace/picture-glance-card.ts index f698754ea6..dccc05e09b 100644 --- a/gallery/src/pages/lovelace/picture-glance-card.ts +++ b/gallery/src/pages/lovelace/picture-glance-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("switch", "decorative_lights", "on", { @@ -134,6 +135,7 @@ class DemoPictureGlance extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/plant-card.ts b/gallery/src/pages/lovelace/plant-card.ts index 99b6780aee..6f04b474b3 100644 --- a/gallery/src/pages/lovelace/plant-card.ts +++ b/gallery/src/pages/lovelace/plant-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; import { createPlantEntities } from "../../data/plants"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const CONFIGS = [ { @@ -43,6 +44,7 @@ export class DemoPlantEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(createPlantEntities()); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/thermostat-card.ts b/gallery/src/pages/lovelace/thermostat-card.ts index 623adf5e34..1775a826e2 100644 --- a/gallery/src/pages/lovelace/thermostat-card.ts +++ b/gallery/src/pages/lovelace/thermostat-card.ts @@ -3,6 +3,7 @@ import { customElement, query } from "lit/decorators"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("climate", "ecobee", "auto", { @@ -116,6 +117,7 @@ class DemoThermostatEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/tile-card.ts b/gallery/src/pages/lovelace/tile-card.ts index 7113f4d048..62ab5abd23 100644 --- a/gallery/src/pages/lovelace/tile-card.ts +++ b/gallery/src/pages/lovelace/tile-card.ts @@ -6,6 +6,7 @@ import { VacuumEntityFeature } from "../../../../src/data/vacuum"; import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("switch", "tv_outlet", "on", { @@ -184,6 +185,7 @@ class DemoTile extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); } } diff --git a/gallery/src/pages/lovelace/todo-list-card.ts b/gallery/src/pages/lovelace/todo-list-card.ts index 49a61a61a1..b03ffd1a93 100644 --- a/gallery/src/pages/lovelace/todo-list-card.ts +++ b/gallery/src/pages/lovelace/todo-list-card.ts @@ -4,6 +4,7 @@ import { provideHass } from "../../../../src/fake_data/provide_hass"; import "../../components/demo-cards"; import { getEntity } from "../../../../src/fake_data/entity"; import { mockTodo } from "../../../../demo/src/stubs/todo"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; const ENTITIES = [ getEntity("todo", "shopping_list", "2", { @@ -47,6 +48,7 @@ class DemoTodoListEntity extends LitElement { hass.updateTranslations(null, "en"); hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); + mockIcons(hass); mockTodo(hass); } diff --git a/gallery/src/pages/misc/entity-state.ts b/gallery/src/pages/misc/entity-state.ts index 4c2194c95c..e454553a4b 100644 --- a/gallery/src/pages/misc/entity-state.ts +++ b/gallery/src/pages/misc/entity-state.ts @@ -11,6 +11,7 @@ import "../../../../src/components/data-table/ha-data-table"; import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table"; import "../../../../src/components/entity/state-badge"; import { provideHass } from "../../../../src/fake_data/provide_hass"; +import { mockIcons } from "../../../../demo/src/stubs/icons"; import { HomeAssistant } from "../../../../src/types"; const SENSOR_DEVICE_CLASSES = [ @@ -291,6 +292,7 @@ const ENTITIES: HassEntity[] = [ createEntity("water_heater.high_demand", "high_demand"), createEntity("water_heater.heat_pump", "heat_pump"), createEntity("water_heater.gas", "gas"), + createEntity("select.speed", "ridiculous_speed"), ]; function createEntity( @@ -397,6 +399,16 @@ export class DemoEntityState extends LitElement { protected firstUpdated(changedProps) { super.firstUpdated(changedProps); const hass = provideHass(this); + mockIcons(hass); + hass.updateHass({ + entities: { + "select.speed": { + entity_id: "select.speed", + translation_key: "speed", + platform: "demo", + }, + }, + }); hass.updateTranslations(null, "en"); hass.updateTranslations("config", "en"); } diff --git a/src/data/icons.ts b/src/data/icons.ts index dbb1a16515..dcb3952f55 100644 --- a/src/data/icons.ts +++ b/src/data/icons.ts @@ -9,6 +9,7 @@ import { EntityRegistryEntry, } from "./entity_registry"; import { isComponentLoaded } from "../common/config/is_component_loaded"; +import { atLeastVersion } from "../common/config/version"; const resources: { entity: Record>; @@ -46,10 +47,10 @@ interface PlatformIcons { }; } -interface ComponentIcons { +export interface ComponentIcons { [device_class: string]: { - state: Record; - state_attributes: Record< + state?: Record; + state_attributes?: Record< string, { state: Record; @@ -91,7 +92,10 @@ export const getPlatformIcons = async ( if (!force && integration in resources.entity) { return resources.entity[integration]; } - if (!isComponentLoaded(hass, integration)) { + if ( + !isComponentLoaded(hass, integration) || + !atLeastVersion(hass.connection.haVersion, 2024, 2) + ) { return undefined; } const result = getHassIcons(hass, "entity", integration).then( @@ -106,6 +110,16 @@ export const getComponentIcons = async ( domain: string, force = false ): Promise => { + // For Cast, old instances can connect to it. + if ( + __BACKWARDS_COMPAT__ && + !atLeastVersion(hass.connection.haVersion, 2024, 2) + ) { + return import("../fake_data/entity_component_icons") + .then((mod) => mod.ENTITY_COMPONENT_ICONS) + .then((res) => res[domain]); + } + if ( !force && resources.entity_component.resources && @@ -113,6 +127,7 @@ export const getComponentIcons = async ( ) { return resources.entity_component.resources.then((res) => res[domain]); } + if (!isComponentLoaded(hass, domain)) { return undefined; } diff --git a/src/fake_data/demo_config.ts b/src/fake_data/demo_config.ts index caaf1bb558..2d6a6fda15 100644 --- a/src/fake_data/demo_config.ts +++ b/src/fake_data/demo_config.ts @@ -14,7 +14,53 @@ export const demoConfig: HassConfig = { wind_speed: "m/s", accumulated_precipitation: "mm", }, - components: ["notify.html5", "history", "todo", "forecast_solar", "energy"], + components: [ + "notify.html5", + "history", + "forecast_solar", + "energy", + "person", + "number", + "select", + "tts", + "datetime", + "vacuum", + "wake_word", + "light", + "alarm_control_panel", + "text", + "lawn_mower", + "siren", + "input_boolean", + "lock", + "calendar", + "image", + "device_tracker", + "scene", + "script", + "todo", + "cover", + "switch", + "button", + "water_heater", + "binary_sensor", + "sensor", + "humidifier", + "valve", + "time", + "media_player", + "air_quality", + "camera", + "date", + "fan", + "automation", + "weather", + "climate", + "stt", + "update", + "event", + "demo", + ], time_zone: "America/Los_Angeles", config_dir: "/config", version: "DEMO", diff --git a/src/fake_data/entity_component_icons.ts b/src/fake_data/entity_component_icons.ts new file mode 100644 index 0000000000..595c01cd0f --- /dev/null +++ b/src/fake_data/entity_component_icons.ts @@ -0,0 +1,962 @@ +import { ComponentIcons } from "../data/icons"; + +export const ENTITY_COMPONENT_ICONS: Record = { + person: { + _: { + default: "mdi:account", + state: { + not_home: "mdi:account-arrow-right", + }, + }, + }, + number: { + _: { + default: "mdi:ray-vertex", + }, + apparent_power: { + default: "mdi:flash", + }, + aqi: { + default: "mdi:air-filter", + }, + atmospheric_pressure: { + default: "mdi:thermometer-lines", + }, + battery: { + default: "mdi:battery", + }, + carbon_dioxide: { + default: "mdi:molecule-co2", + }, + carbon_monoxide: { + default: "mdi:molecule-co", + }, + current: { + default: "mdi:current-ac", + }, + data_rate: { + default: "mdi:transmission-tower", + }, + data_size: { + default: "mdi:database", + }, + distance: { + default: "mdi:arrow-left-right", + }, + duration: { + default: "mdi:progress-clock", + }, + energy: { + default: "mdi:lightning-bolt", + }, + energy_storage: { + default: "mdi:car-battery", + }, + frequency: { + default: "mdi:sine-wave", + }, + gas: { + default: "mdi:meter-gas", + }, + humidity: { + default: "mdi:water-percent", + }, + illuminance: { + default: "mdi:brightness-5", + }, + irradiance: { + default: "mdi:sun-wireless", + }, + moisture: { + default: "mdi:water-percent", + }, + monetary: { + default: "mdi:cash", + }, + nitrogen_dioxide: { + default: "mdi:molecule", + }, + nitrogen_monoxide: { + default: "mdi:molecule", + }, + nitrous_oxide: { + default: "mdi:molecule", + }, + ozone: { + default: "mdi:molecule", + }, + ph: { + default: "mdi:ph", + }, + pm1: { + default: "mdi:molecule", + }, + pm10: { + default: "mdi:molecule", + }, + pm25: { + default: "mdi:molecule", + }, + power: { + default: "mdi:flash", + }, + power_factor: { + default: "mdi:angle-acute", + }, + precipitation: { + default: "mdi:weather-rainy", + }, + precipitation_intensity: { + default: "mdi:weather-pouring", + }, + pressure: { + default: "mdi:gauge", + }, + reactive_power: { + default: "mdi:flash", + }, + signal_strength: { + default: "mdi:wifi", + }, + sound_pressure: { + default: "mdi:ear-hearing", + }, + speed: { + default: "mdi:speedometer", + }, + sulfur_dioxide: { + default: "mdi:molecule", + }, + temperature: { + default: "mdi:thermometer", + }, + volatile_organic_compounds: { + default: "mdi:molecule", + }, + volatile_organic_compounds_parts: { + default: "mdi:molecule", + }, + voltage: { + default: "mdi:sine-wave", + }, + volume: { + default: "mdi:car-coolant-level", + }, + volume_storage: { + default: "mdi:storage-tank", + }, + water: { + default: "mdi:water", + }, + weight: { + default: "mdi:weight", + }, + wind_speed: { + default: "mdi:weather-windy", + }, + }, + select: { + _: { + default: "mdi:format-list-bulleted", + }, + }, + tts: { + _: { + default: "mdi:speaker-message", + }, + }, + datetime: { + _: { + default: "mdi:calendar-clock", + }, + }, + vacuum: { + _: { + default: "mdi:robot-vacuum", + }, + }, + wake_word: { + _: { + default: "mdi:chat-sleep", + }, + }, + light: { + _: { + default: "mdi:lightbulb", + }, + }, + alarm_control_panel: { + _: { + default: "mdi:shield", + state: { + armed_away: "mdi:shield-lock", + armed_custom_bypass: "mdi:security", + armed_home: "mdi:shield-home", + armed_night: "mdi:shield-moon", + armed_vacation: "mdi:shield-airplane", + disarmed: "mdi:shield-off", + pending: "mdi:shield-outline", + triggered: "mdi:bell-ring", + }, + }, + }, + text: { + _: { + default: "mdi:form-textbox", + }, + }, + lawn_mower: { + _: { + default: "mdi:robot-mower", + }, + }, + siren: { + _: { + default: "mdi:bullhorn", + }, + }, + input_boolean: { + _: { + default: "mdi:check-circle-outline", + state: { + off: "mdi:close-circle-outline", + }, + }, + }, + lock: { + _: { + default: "mdi:lock", + state: { + jammed: "mdi:lock-alert", + locking: "mdi:lock-clock", + unlocked: "mdi:lock-open", + unlocking: "mdi:lock-clock", + }, + }, + }, + calendar: { + _: { + default: "mdi:calendar", + state: { + on: "mdi:calendar-check", + off: "mdi:calendar-blank", + }, + }, + }, + image: { + _: { + default: "mdi:image", + }, + }, + device_tracker: { + _: { + default: "mdi:account", + state: { + not_home: "mdi:account-arrow-right", + }, + }, + }, + scene: { + _: { + default: "mdi:palette", + }, + }, + script: { + _: { + default: "mdi:script-text", + state: { + on: "mdi:script-text-play", + }, + }, + }, + todo: { + _: { + default: "mdi:clipboard-list", + }, + }, + cover: { + _: { + default: "mdi:window-open", + state: { + closed: "mdi:window-closed", + closing: "mdi:arrow-down-box", + opening: "mdi:arrow-up-box", + }, + }, + blind: { + default: "mdi:blinds-horizontal", + state: { + closed: "mdi:blinds-horizontal-closed", + closing: "mdi:arrow-down-box", + opening: "mdi:arrow-up-box", + }, + }, + curtain: { + default: "mdi:curtains", + state: { + closed: "mdi:curtains-closed", + closing: "mdi:arrow-collapse-horizontal", + opening: "mdi:arrow-split-vertical", + }, + }, + damper: { + default: "mdi:circle", + state: { + closed: "mdi:circle-slice-8", + }, + }, + door: { + default: "mdi:door-open", + state: { + closed: "mdi:door-closed", + }, + }, + garage: { + default: "mdi:garage-open", + state: { + closed: "mdi:garage", + closing: "mdi:arrow-down-box", + opening: "mdi:arrow-up-box", + }, + }, + gate: { + default: "mdi:gate-open", + state: { + closed: "mdi:gate", + closing: "mdi:arrow-right", + opening: "mdi:arrow-right", + }, + }, + shade: { + default: "mdi:roller-shade", + state: { + closed: "mdi:roller-shade-closed", + closing: "mdi:arrow-down-box", + opening: "mdi:arrow-up-box", + }, + }, + shutter: { + default: "mdi:window-shutter-open", + state: { + closed: "mdi:window-shutter", + closing: "mdi:arrow-down-box", + opening: "mdi:arrow-up-box", + }, + }, + window: { + default: "mdi:window-open", + state: { + closed: "mdi:window-closed", + closing: "mdi:arrow-down-box", + opening: "mdi:arrow-up-box", + }, + }, + }, + switch: { + _: { + default: "mdi:toggle-switch-variant", + }, + switch: { + default: "mdi:toggle-switch-variant", + state: { + off: "mdi:toggle-switch-variant-off", + }, + }, + outlet: { + default: "mdi:power-plug", + state: { + off: "mdi:power-plug-off", + }, + }, + }, + button: { + _: { + default: "mdi:button-pointer", + }, + restart: { + default: "mdi:restart", + }, + identify: { + default: "mdi:crosshairs-question", + }, + update: { + default: "mdi:package-up", + }, + }, + water_heater: { + _: { + default: "mdi:water-boiler", + state: { + off: "mdi:water-boiler-off", + }, + state_attributes: { + operation_mode: { + default: "mdi:circle-medium", + state: { + eco: "mdi:leaf", + electric: "mdi:lightning-bolt", + gas: "mdi:fire-circle", + heat_pump: "mdi:heat-wave", + high_demand: "mdi:finance", + off: "mdi:power", + performance: "mdi:rocket-launch", + }, + }, + }, + }, + }, + binary_sensor: { + _: { + default: "mdi:radiobox-blank", + state: { + on: "mdi:checkbox-marked-circle", + }, + }, + battery: { + default: "mdi:battery", + state: { + on: "mdi:battery-outline", + }, + }, + battery_charging: { + default: "mdi:battery", + state: { + on: "mdi:battery-charging", + }, + }, + carbon_monoxide: { + default: "mdi:smoke-detector", + state: { + on: "mdi:smoke-detector-alert", + }, + }, + cold: { + default: "mdi:thermometer", + state: { + on: "mdi:snowflake", + }, + }, + connectivity: { + default: "mdi:close-network-outline", + state: { + on: "mdi:check-network-outline", + }, + }, + door: { + default: "mdi:door-closed", + state: { + on: "mdi:door-open", + }, + }, + garage_door: { + default: "mdi:garage", + state: { + on: "mdi:garage-open", + }, + }, + gas: { + default: "mdi:check-circle", + state: { + on: "mdi:alert-circle", + }, + }, + heat: { + default: "mdi:thermometer", + state: { + on: "mdi:fire", + }, + }, + light: { + default: "mdi:brightness-5", + state: { + on: "mdi:brightness-7", + }, + }, + lock: { + default: "mdi:lock", + state: { + on: "mdi:lock-open", + }, + }, + moisture: { + default: "mdi:water-off", + state: { + on: "mdi:water", + }, + }, + motion: { + default: "mdi:motion-sensor-off", + state: { + on: "mdi:motion-sensor", + }, + }, + moving: { + default: "mdi:arrow-right", + state: { + on: "mdi:octagon", + }, + }, + occupancy: { + default: "mdi:home-outline", + state: { + on: "mdi:home", + }, + }, + opening: { + default: "mdi:square", + state: { + on: "mdi:square-outline", + }, + }, + plug: { + default: "mdi:power-plug-off", + state: { + on: "mdi:power-plug", + }, + }, + power: { + default: "mdi:power-plug-off", + state: { + on: "mdi:power-plug", + }, + }, + presence: { + default: "mdi:home-outline", + state: { + on: "mdi:home", + }, + }, + problem: { + default: "mdi:check-circle", + state: { + on: "mdi:alert-circle", + }, + }, + running: { + default: "mdi:stop", + state: { + on: "mdi:play", + }, + }, + safety: { + default: "mdi:check-circle", + state: { + on: "mdi:alert-circle", + }, + }, + smoke: { + default: "mdi:smoke-detector-variant", + state: { + on: "mdi:smoke-detector-variant-alert", + }, + }, + sound: { + default: "mdi:music-note-off", + state: { + on: "mdi:music-note", + }, + }, + tamper: { + default: "mdi:check-circle", + state: { + on: "mdi:alert-circle", + }, + }, + update: { + default: "mdi:package", + state: { + on: "mdi:package-up", + }, + }, + vibration: { + default: "mdi:crop-portrait", + state: { + on: "mdi:vibrate", + }, + }, + window: { + default: "mdi:window-closed", + state: { + on: "mdi:window-open", + }, + }, + }, + sensor: { + _: { + default: "mdi:eye", + }, + apparent_power: { + default: "mdi:flash", + }, + aqi: { + default: "mdi:air-filter", + }, + atmospheric_pressure: { + default: "mdi:thermometer-lines", + }, + carbon_dioxide: { + default: "mdi:molecule-co2", + }, + carbon_monoxide: { + default: "mdi:molecule-co", + }, + current: { + default: "mdi:current-ac", + }, + data_rate: { + default: "mdi:transmission-tower", + }, + data_size: { + default: "mdi:database", + }, + date: { + default: "mdi:calendar", + }, + distance: { + default: "mdi:arrow-left-right", + }, + duration: { + default: "mdi:progress-clock", + }, + energy: { + default: "mdi:lightning-bolt", + }, + energy_storage: { + default: "mdi:car-battery", + }, + enum: { + default: "mdi:eye", + }, + frequency: { + default: "mdi:sine-wave", + }, + gas: { + default: "mdi:meter-gas", + }, + humidity: { + default: "mdi:water-percent", + }, + illuminance: { + default: "mdi:brightness-5", + }, + irradiance: { + default: "mdi:sun-wireless", + }, + moisture: { + default: "mdi:water-percent", + }, + monetary: { + default: "mdi:cash", + }, + nitrogen_dioxide: { + default: "mdi:molecule", + }, + nitrogen_monoxide: { + default: "mdi:molecule", + }, + nitrous_oxide: { + default: "mdi:molecule", + }, + ozone: { + default: "mdi:molecule", + }, + ph: { + default: "mdi:ph", + }, + pm1: { + default: "mdi:molecule", + }, + pm10: { + default: "mdi:molecule", + }, + pm25: { + default: "mdi:molecule", + }, + power: { + default: "mdi:flash", + }, + power_factor: { + default: "mdi:angle-acute", + }, + precipitation: { + default: "mdi:weather-rainy", + }, + precipitation_intensity: { + default: "mdi:weather-pouring", + }, + pressure: { + default: "mdi:gauge", + }, + reactive_power: { + default: "mdi:flash", + }, + signal_strength: { + default: "mdi:wifi", + }, + sound_pressure: { + default: "mdi:ear-hearing", + }, + speed: { + default: "mdi:speedometer", + }, + sulfur_dioxide: { + default: "mdi:molecule", + }, + temperature: { + default: "mdi:thermometer", + }, + timestamp: { + default: "mdi:clock", + }, + volatile_organic_compounds: { + default: "mdi:molecule", + }, + volatile_organic_compounds_parts: { + default: "mdi:molecule", + }, + voltage: { + default: "mdi:sine-wave", + }, + volume: { + default: "mdi:car-coolant-level", + }, + volume_storage: { + default: "mdi:storage-tank", + }, + water: { + default: "mdi:water", + }, + weight: { + default: "mdi:weight", + }, + wind_speed: { + default: "mdi:weather-windy", + }, + }, + humidifier: { + _: { + default: "mdi:air-humidifier", + state: { + off: "mdi:air-humidifier-off", + }, + state_attributes: { + action: { + default: "mdi:circle-medium", + state: { + drying: "mdi:arrow-down-bold", + humidifying: "mdi:arrow-up-bold", + idle: "mdi:clock-outline", + off: "mdi:power", + }, + }, + mode: { + default: "mdi:circle-medium", + state: { + auto: "mdi:refresh-auto", + away: "mdi:account-arrow-right", + baby: "mdi:baby-carriage", + boost: "mdi:rocket-launch", + comfort: "mdi:sofa", + eco: "mdi:leaf", + home: "mdi:home", + normal: "mdi:water-percent", + sleep: "mdi:power-sleep", + }, + }, + }, + }, + }, + valve: { + _: { + default: "mdi:pipe-valve", + }, + gas: { + default: "mdi:meter-gas", + }, + water: { + default: "mdi:pipe-valve", + }, + }, + time: { + _: { + default: "mdi:clock", + }, + }, + media_player: { + _: { + default: "mdi:cast", + state: { + off: "mdi:cast-off", + paused: "mdi:cast-connected", + playing: "mdi:cast-connected", + }, + }, + receiver: { + default: "mdi:audio-video", + state: { + off: "mdi:audio-video-off", + }, + }, + speaker: { + default: "mdi:speaker", + state: { + off: "mdi:speaker-off", + paused: "mdi:speaker-pause", + playing: "mdi:speaker-play", + }, + }, + tv: { + default: "mdi:television", + state: { + off: "mdi:television-off", + paused: "mdi:television-pause", + playing: "mdi:television-play", + }, + }, + }, + air_quality: { + _: { + default: "mdi:air-filter", + }, + }, + camera: { + _: { + default: "mdi:video", + state: { + off: "mdi:video-off", + }, + }, + }, + date: { + _: { + default: "mdi:calendar", + }, + }, + fan: { + _: { + default: "mdi:fan", + state: { + off: "mdi:fan-off", + }, + state_attributes: { + direction: { + default: "mdi:rotate-right", + state: { + reverse: "mdi:rotate-left", + }, + }, + }, + }, + }, + automation: { + _: { + default: "mdi:robot", + state: { + off: "mdi:robot-off", + unavailable: "mdi:robot-confused", + }, + }, + }, + weather: { + _: { + default: "mdi:weather-partly-cloudy", + state: { + "clear-night": "mdi:weather-night", + cloudy: "mdi:weather-cloudy", + exceptional: "mdi:alert-circle-outline", + fog: "mdi:weather-fog", + hail: "mdi:weather-hail", + lightning: "mdi:weather-lightning", + "lightning-rainy": "mdi:weather-lightning-rainy", + pouring: "mdi:weather-pouring", + rainy: "mdi:weather-rainy", + snowy: "mdi:weather-snowy", + "snowy-rainy": "mdi:weather-snowy-rainy", + sunny: "mdi:weather-sunny", + windy: "mdi:weather-windy", + "windy-variant": "mdi:weather-windy-variant", + }, + }, + }, + climate: { + _: { + default: "mdi:thermostat", + state_attributes: { + fan_mode: { + default: "mdi:circle-medium", + state: { + diffuse: "mdi:weather-windy", + focus: "mdi:target", + high: "mdi:speedometer", + low: "mdi:speedometer-slow", + medium: "mdi:speedometer-medium", + middle: "mdi:speedometer-medium", + off: "mdi:fan-off", + on: "mdi:fan", + }, + }, + hvac_action: { + default: "mdi:circle-medium", + state: { + cooling: "mdi:snowflake", + drying: "mdi:water-percent", + fan: "mdi:fan", + heating: "mdi:fire", + idle: "mdi:clock-outline", + off: "mdi:power", + preheating: "mdi:heat-wave", + }, + }, + preset_mode: { + default: "mdi:circle-medium", + state: { + activity: "mdi:motion-sensor", + away: "mdi:account-arrow-right", + boost: "mdi:rocket-launch", + comfort: "mdi:sofa", + eco: "mdi:leaf", + home: "mdi:home", + sleep: "mdi:bed", + }, + }, + swing_mode: { + default: "mdi:circle-medium", + state: { + both: "mdi:arrow-all", + horizontal: "mdi:arrow-left-right", + off: "mdi:arrow-oscillating-off", + on: "mdi:arrow-oscillating", + vertical: "mdi:arrow-up-down", + }, + }, + }, + }, + }, + stt: { + _: { + default: "mdi:microphone-message", + }, + }, + update: { + _: { + default: "mdi:package-up", + state: { + off: "mdi:package", + }, + }, + }, + event: { + _: { + default: "mdi:eye-check", + }, + button: { + default: "mdi:gesture-tap-button", + }, + doorbell: { + default: "mdi:doorbell", + }, + motion: { + default: "mdi:motion-sensor", + }, + }, +}; From 20560fb8474919187cf20cfc7a28ea1ed85f46af Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Feb 2024 17:40:43 +0100 Subject: [PATCH 09/12] Fix cast launch screen (#19738) --- cast/src/receiver/layout/hc-launch-screen.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cast/src/receiver/layout/hc-launch-screen.ts b/cast/src/receiver/layout/hc-launch-screen.ts index 2b212860e8..49be9d0700 100644 --- a/cast/src/receiver/layout/hc-launch-screen.ts +++ b/cast/src/receiver/layout/hc-launch-screen.ts @@ -28,7 +28,6 @@ class HcLaunchScreen extends LitElement { :host { display: block; height: 100vh; - padding-top: 64px; background-color: white; font-size: 24px; } @@ -36,12 +35,13 @@ class HcLaunchScreen extends LitElement { display: flex; flex-direction: column; text-align: center; + align-items: center; + height: 100%; + justify-content: space-evenly; } img { - width: 717px; - height: 376px; - display: block; - margin: 0 auto; + max-width: 80%; + object-fit: cover; } .status { padding-right: 54px; From 03486e4125835241404cc3dbe64b05c6f5475891 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Feb 2024 17:43:50 +0100 Subject: [PATCH 10/12] cast allow empty view, pick first (#19739) --- cast/src/receiver/layout/hc-lovelace.ts | 5 ++++- cast/src/receiver/layout/hc-main.ts | 14 +++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index 0de33aa69d..01b6f03fb0 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -17,7 +17,7 @@ class HcLovelace extends LitElement { @property({ attribute: false }) public lovelaceConfig!: LovelaceConfig; - @property() public viewPath?: string | number; + @property() public viewPath?: string | number | null; @property() public urlPath: string | null = null; @@ -93,6 +93,9 @@ class HcLovelace extends LitElement { } private get _viewIndex() { + if (this.viewPath === null) { + return 0; + } const selectedView = this.viewPath; const selectedViewInt = parseInt(selectedView as string, 10); for (let i = 0; i < this.lovelaceConfig.views.length; i++) { diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index dcf7a80eec..64b821817a 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -51,10 +51,10 @@ export class HcMain extends HassElement { @state() private _lovelacePath: string | number | null = null; - @state() private _error?: string; - @state() private _urlPath?: string | null; + @state() private _error?: string; + private _hassUUID?: string; private _unsubLovelace?: UnsubscribeFunc; @@ -81,7 +81,7 @@ export class HcMain extends HassElement { if ( !this._lovelaceConfig || - this._lovelacePath === null || + this._urlPath === undefined || // Guard against part of HA not being loaded yet. !this.hass || !this.hass.states || @@ -99,8 +99,8 @@ export class HcMain extends HassElement { `; @@ -226,9 +226,9 @@ export class HcMain extends HassElement { this.initializeHass(auth, connection); if (this._hassUUID !== msg.hassUUID) { this._hassUUID = msg.hassUUID; - this._lovelacePath = null; - this._urlPath = undefined; this._lovelaceConfig = undefined; + this._urlPath = undefined; + this._lovelacePath = null; if (this._unsubLovelace) { this._unsubLovelace(); this._unsubLovelace = undefined; @@ -285,7 +285,7 @@ export class HcMain extends HassElement { ], }; this._urlPath = "energy"; - this._lovelacePath = 0; + this._lovelacePath = null; this._sendStatus(); return; } From 3469013f1a5cf1686634c41cdfa125aad65f9ba2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Feb 2024 18:07:15 +0100 Subject: [PATCH 11/12] Bumped version to 20240207.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 058f7ffd6f..f0eef3c035 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240207.0" +version = "20240207.1" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From f0a9185e4ac191a00ee5a5ca3c08d8ec7dae9ac7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Feb 2024 18:31:23 +0100 Subject: [PATCH 12/12] handle undefined and null in cast --- cast/src/receiver/layout/hc-lovelace.ts | 2 +- cast/src/receiver/layout/hc-main.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index 01b6f03fb0..d748b8184e 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -93,7 +93,7 @@ class HcLovelace extends LitElement { } private get _viewIndex() { - if (this.viewPath === null) { + if (this.viewPath === null || this.viewPath === undefined) { return 0; } const selectedView = this.viewPath; diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 64b821817a..cc0f3bc48f 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -270,7 +270,7 @@ export class HcMain extends HassElement { } this._error = undefined; - if (msg.urlPath === "lovelace") { + if (msg.urlPath === "lovelace" || msg.urlPath === undefined) { msg.urlPath = null; } this._lovelacePath = msg.viewPath;