diff --git a/src/common/entity/attribute_icon_path.ts b/src/common/entity/attribute_icon_path.ts deleted file mode 100644 index f3855a5232..0000000000 --- a/src/common/entity/attribute_icon_path.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** Return an icon representing a attribute. */ -import { mdiCircleMedium, mdiCreation } from "@mdi/js"; -import { HassEntity } from "home-assistant-js-websocket"; -import { - computeFanModeIcon, - computeHvacModeIcon, - computePresetModeIcon, - computeSwingModeIcon, -} from "../../data/climate"; -import { computeHumidiferModeIcon } from "../../data/humidifier"; -import { computeDomain } from "./compute_domain"; - -const iconGenerators: Record string>> = { - climate: { - fan_mode: computeFanModeIcon, - hvac_mode: computeHvacModeIcon, - preset_mode: computePresetModeIcon, - swing_mode: computeSwingModeIcon, - }, - humidifier: { - mode: computeHumidiferModeIcon, - }, - light: { - effect: () => mdiCreation, - }, - fan: { - preset_mode: () => mdiCircleMedium, - }, -}; - -export const attributeIconPath = ( - state: HassEntity | undefined, - attribute: string, - attributeValue?: string -) => { - if (!state) { - return undefined; - } - const domain = computeDomain(state.entity_id); - if (iconGenerators[domain]?.[attribute]) { - return iconGenerators[domain]?.[attribute]( - attributeValue || state.attributes[attribute] - ); - } - return undefined; -}; diff --git a/src/components/ha-attribute-icon.ts b/src/components/ha-attribute-icon.ts index 682befceda..ae84900ac5 100644 --- a/src/components/ha-attribute-icon.ts +++ b/src/components/ha-attribute-icon.ts @@ -6,7 +6,6 @@ import { attributeIcon } from "../data/icons"; import { HomeAssistant } from "../types"; import "./ha-icon"; import "./ha-svg-icon"; -import { attributeIconPath } from "../common/entity/attribute_icon_path"; @customElement("ha-attribute-icon") export class HaAttributeIcon extends LitElement { @@ -30,7 +29,7 @@ export class HaAttributeIcon extends LitElement { } if (!this.hass) { - return this._renderFallback(); + return nothing; } const icon = attributeIcon( @@ -42,23 +41,11 @@ export class HaAttributeIcon extends LitElement { if (icn) { return html``; } - return this._renderFallback(); + return nothing; }); return html`${until(icon)}`; } - - private _renderFallback() { - return html` - - `; - } } declare global { diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 6a37c6d4c5..d651675f04 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -84,8 +84,6 @@ export class HaIconPicker extends LitElement { @property() public placeholder?: string; - @property() public fallbackPath?: string; - @property({ attribute: "error-message" }) public errorMessage?: string; @property({ type: Boolean }) public disabled = false; @@ -120,12 +118,7 @@ export class HaIconPicker extends LitElement { ` - : this.fallbackPath - ? html`` - : ""} + : html``} `; } diff --git a/src/components/ha-selector/ha-selector-icon.ts b/src/components/ha-selector/ha-selector-icon.ts index c922f99a5d..3e517d61e4 100644 --- a/src/components/ha-selector/ha-selector-icon.ts +++ b/src/components/ha-selector/ha-selector-icon.ts @@ -1,13 +1,12 @@ -import { html, LitElement } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { until } from "lit/directives/until"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeDomain } from "../../common/entity/compute_domain"; -import { domainIcon } from "../../common/entity/domain_icon"; import { entityIcon } from "../../data/icons"; import { IconSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../ha-icon-picker"; +import "../ha-state-icon"; @customElement("ha-selector-icon") export class HaIconSelector extends LitElement { @@ -39,11 +38,6 @@ export class HaIconSelector extends LitElement { stateObj?.attributes.icon || (stateObj && until(entityIcon(this.hass, stateObj))); - const fallbackPath = - !placeholder && stateObj - ? domainIcon(computeDomain(iconEntity!), stateObj) - : undefined; - return html` + > + ${!placeholder && stateObj + ? html` + + ` + : nothing} + `; } diff --git a/src/data/climate.ts b/src/data/climate.ts index 2e4a38b6fa..090e78c72c 100644 --- a/src/data/climate.ts +++ b/src/data/climate.ts @@ -1,33 +1,12 @@ import { - mdiAccountArrowRight, - mdiArrowAll, - mdiArrowLeftRight, - mdiArrowOscillating, - mdiArrowOscillatingOff, - mdiArrowUpDown, - mdiBed, - mdiCircleMedium, - mdiClockOutline, mdiFan, - mdiFanAuto, - mdiFanOff, mdiFire, - mdiHeatWave, - mdiHome, - mdiLeaf, - mdiMotionSensor, mdiPower, - mdiRocketLaunch, mdiSnowflake, - mdiSofa, - mdiSpeedometer, - mdiSpeedometerMedium, - mdiSpeedometerSlow, mdiSunSnowflakeVariant, - mdiTarget, + mdiThermostat, mdiThermostatAuto, mdiWaterPercent, - mdiWeatherWindy, } from "@mdi/js"; import { HassEntityAttributeBase, @@ -116,16 +95,6 @@ export const CLIMATE_HVAC_ACTION_TO_MODE: Record = { off: "off", }; -export const CLIMATE_HVAC_ACTION_ICONS: Record = { - cooling: mdiSnowflake, - drying: mdiWaterPercent, - fan: mdiFan, - heating: mdiFire, - idle: mdiClockOutline, - off: mdiPower, - preheating: mdiHeatWave, -}; - export const CLIMATE_HVAC_MODE_ICONS: Record = { cool: mdiSnowflake, dry: mdiWaterPercent, @@ -136,81 +105,5 @@ export const CLIMATE_HVAC_MODE_ICONS: Record = { heat_cool: mdiSunSnowflakeVariant, }; -export const computeHvacModeIcon = (mode: HvacMode) => - CLIMATE_HVAC_MODE_ICONS[mode]; - -type ClimateBuiltInPresetMode = - | "eco" - | "away" - | "boost" - | "comfort" - | "home" - | "sleep" - | "activity"; - -export const CLIMATE_PRESET_MODE_ICONS: Record< - ClimateBuiltInPresetMode, - string -> = { - away: mdiAccountArrowRight, - boost: mdiRocketLaunch, - comfort: mdiSofa, - eco: mdiLeaf, - home: mdiHome, - sleep: mdiBed, - activity: mdiMotionSensor, -}; - -export const computePresetModeIcon = (mode: string) => - mode in CLIMATE_PRESET_MODE_ICONS - ? CLIMATE_PRESET_MODE_ICONS[mode] - : mdiCircleMedium; - -type ClimateBuiltInFanMode = - | "on" - | "off" - | "auto" - | "low" - | "medium" - | "high" - | "middle" - | "focus" - | "diffuse"; - -export const CLIMATE_FAN_MODE_ICONS: Record = { - on: mdiFan, - off: mdiFanOff, - auto: mdiFanAuto, - low: mdiSpeedometerSlow, - medium: mdiSpeedometerMedium, - high: mdiSpeedometer, - middle: mdiSpeedometerMedium, - focus: mdiTarget, - diffuse: mdiWeatherWindy, -}; - -export const computeFanModeIcon = (mode: string) => - mode in CLIMATE_FAN_MODE_ICONS - ? CLIMATE_FAN_MODE_ICONS[mode] - : mdiCircleMedium; - -type ClimateBuiltInSwingMode = - | "off" - | "on" - | "vertical" - | "horizontal" - | "both"; - -export const CLIMATE_SWING_MODE_ICONS: Record = - { - on: mdiArrowOscillating, - off: mdiArrowOscillatingOff, - vertical: mdiArrowUpDown, - horizontal: mdiArrowLeftRight, - both: mdiArrowAll, - }; - -export const computeSwingModeIcon = (mode: string) => - mode in CLIMATE_SWING_MODE_ICONS - ? CLIMATE_SWING_MODE_ICONS[mode] - : mdiCircleMedium; +export const climateHvacModeIcon = (mode: string) => + CLIMATE_HVAC_MODE_ICONS[mode] || mdiThermostat; diff --git a/src/data/humidifier.ts b/src/data/humidifier.ts index bd94bf59d0..b0598ea798 100644 --- a/src/data/humidifier.ts +++ b/src/data/humidifier.ts @@ -1,19 +1,3 @@ -import { - mdiAccountArrowRight, - mdiArrowDownBold, - mdiArrowUpBold, - mdiBabyCarriage, - mdiCircleMedium, - mdiClockOutline, - mdiHome, - mdiLeaf, - mdiPower, - mdiPowerSleep, - mdiRefreshAuto, - mdiRocketLaunch, - mdiSofa, - mdiWaterPercent, -} from "@mdi/js"; import { HassEntityAttributeBase, HassEntityBase, @@ -44,39 +28,6 @@ export const enum HumidifierEntityDeviceClass { DEHUMIDIFIER = "dehumidifier", } -type HumidifierBuiltInMode = - | "normal" - | "eco" - | "away" - | "boost" - | "comfort" - | "home" - | "sleep" - | "auto" - | "baby"; - -export const HUMIDIFIER_MODE_ICONS: Record = { - auto: mdiRefreshAuto, - away: mdiAccountArrowRight, - baby: mdiBabyCarriage, - boost: mdiRocketLaunch, - comfort: mdiSofa, - eco: mdiLeaf, - home: mdiHome, - normal: mdiWaterPercent, - sleep: mdiPowerSleep, -}; - -export const computeHumidiferModeIcon = (mode?: string) => - HUMIDIFIER_MODE_ICONS[mode as HumidifierBuiltInMode] ?? mdiCircleMedium; - -export const HUMIDIFIER_ACTION_ICONS: Record = { - drying: mdiArrowDownBold, - humidifying: mdiArrowUpBold, - idle: mdiClockOutline, - off: mdiPower, -}; - export const HUMIDIFIER_ACTION_MODE: Record = { drying: "on", diff --git a/src/data/icons.ts b/src/data/icons.ts index 009723d67f..f9cbf40623 100644 --- a/src/data/icons.ts +++ b/src/data/icons.ts @@ -1,6 +1,11 @@ import { HassEntity } from "home-assistant-js-websocket"; import { computeStateDomain } from "../common/entity/compute_state_domain"; import { HomeAssistant } from "../types"; +import { + EntityRegistryDisplayEntry, + EntityRegistryEntry, +} from "./entity_registry"; +import { computeDomain } from "../common/entity/compute_domain"; const resources: Record = { entity: {}, @@ -15,7 +20,13 @@ interface PlatformIcons { [domain: string]: { [translation_key: string]: { state: Record; - state_attributes: Record }>; + state_attributes: Record< + string, + { + state: Record; + default: string; + } + >; default: string; }; }; @@ -24,7 +35,13 @@ interface PlatformIcons { interface ComponentIcons { [device_class: string]: { state: Record; - state_attributes: Record }>; + state_attributes: Record< + string, + { + state: Record; + default: string; + } + >; default: string; }; } @@ -76,32 +93,67 @@ export const entityIcon = async ( state: HassEntity, stateValue?: string ) => { - let icon: string | undefined; - const domain = computeStateDomain(state); - const entity = hass.entities?.[state.entity_id]; - const value = stateValue ?? state.state; + const entity = hass.entities?.[state.entity_id] as + | EntityRegistryDisplayEntry + | undefined; + if (entity?.icon) { return entity.icon; } - if (entity?.translation_key && entity.platform) { - const platformIcons = await getPlatformIcons(hass, entity.platform); + const domain = computeStateDomain(state); + const deviceClass = state.attributes.device_class; + + return getEntityIcon( + hass, + domain, + deviceClass, + stateValue ?? state.state, + entity?.platform, + entity?.translation_key + ); +}; + +export const entryIcon = async ( + hass: HomeAssistant, + entry: EntityRegistryEntry | EntityRegistryDisplayEntry +) => { + if (entry.icon) { + return entry.icon; + } + const domain = computeDomain(entry.entity_id); + return getEntityIcon( + hass, + domain, + undefined, + undefined, + entry.platform, + entry.translation_key + ); +}; + +const getEntityIcon = async ( + hass: HomeAssistant, + domain: string, + deviceClass?: string, + value?: string, + platform?: string, + translation_key?: string +) => { + let icon: string | undefined; + if (translation_key && platform) { + const platformIcons = await getPlatformIcons(hass, platform); if (platformIcons) { - icon = - platformIcons[domain]?.[entity.translation_key]?.state?.[value] || - platformIcons[domain]?.[entity.translation_key]?.default; + const translations = platformIcons[domain]?.[translation_key]; + icon = (value && translations?.state?.[value]) || translations?.default; } } if (!icon) { const entityComponentIcons = await getComponentIcons(hass, domain); - if (entityComponentIcons) { - icon = - entityComponentIcons[state.attributes.device_class || "_"]?.state?.[ - value - ] || - entityComponentIcons._?.state?.[value] || - entityComponentIcons[state.attributes.device_class || "_"]?.default || - entityComponentIcons._?.default; + const translations = + (deviceClass && entityComponentIcons[deviceClass]) || + entityComponentIcons._; + icon = (value && translations?.state?.[value]) || translations?.default; } } return icon; @@ -115,24 +167,32 @@ export const attributeIcon = async ( ) => { let icon: string | undefined; const domain = computeStateDomain(state); - const entity = hass.entities?.[state.entity_id]; - const value = attributeValue ?? state.attributes[attribute]; - if (entity?.translation_key && entity.platform) { - const platformIcons = await getPlatformIcons(hass, entity.platform); + const deviceClass = state.attributes.device_class; + const entity = hass.entities?.[state.entity_id] as + | EntityRegistryDisplayEntry + | undefined; + const platform = entity?.platform; + const translation_key = entity?.translation_key; + const value = + attributeValue ?? + (state.attributes[attribute] as string | number | undefined); + + if (translation_key && platform) { + const platformIcons = await getPlatformIcons(hass, platform); if (platformIcons) { - icon = - platformIcons[domain]?.[entity.translation_key]?.state_attributes?.[ - attribute - ]?.state?.[value]; + const translations = + platformIcons[domain]?.[translation_key]?.state_attributes?.[attribute]; + icon = (value && translations?.state?.[value]) || translations?.default; } } if (!icon) { const entityComponentIcons = await getComponentIcons(hass, domain); if (entityComponentIcons) { - icon = - entityComponentIcons[state.attributes.device_class || "_"] - .state_attributes?.[attribute]?.state?.[value] || - entityComponentIcons._.state_attributes?.[attribute]?.state?.[value]; + const translations = + (deviceClass && + entityComponentIcons[deviceClass]?.state_attributes?.[attribute]) || + entityComponentIcons._?.state_attributes?.[attribute]; + icon = (value && translations?.state?.[value]) || translations?.default; } } return icon; diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts index 496953f07d..086bb7fa78 100644 --- a/src/dialogs/more-info/controls/more-info-climate.ts +++ b/src/dialogs/more-info/controls/more-info-climate.ts @@ -3,7 +3,6 @@ import { mdiArrowOscillating, mdiFan, mdiThermometer, - mdiThermostat, mdiTuneVariant, mdiWaterPercent, } from "@mdi/js"; @@ -11,19 +10,20 @@ import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; import "../../../components/ha-control-select-menu"; import "../../../components/ha-icon-button-group"; import "../../../components/ha-icon-button-toggle"; import "../../../components/ha-list-item"; import "../../../components/ha-select"; import "../../../components/ha-switch"; -import "../../../components/ha-attribute-icon"; import { ClimateEntity, ClimateEntityFeature, + climateHvacModeIcon, compareClimateHvacModes, } from "../../../data/climate"; -import { UNAVAILABLE, isUnavailableState } from "../../../data/entity"; +import { UNAVAILABLE } from "../../../data/entity"; import "../../../state-control/climate/ha-state-control-climate-humidity"; import "../../../state-control/climate/ha-state-control-climate-temperature"; import { HomeAssistant } from "../../../types"; @@ -161,31 +161,22 @@ class MoreInfoClimate extends LitElement { @selected=${this._handleOperationModeChanged} @closed=${stopPropagation} > - ${!isUnavailableState(this.stateObj.state) - ? html`` - : html``} + ${html` + + `} ${stateObj.attributes.hvac_modes .concat() .sort(compareClimateHvacModes) .map( (mode) => html` - + .path=${climateHvacModeIcon(mode)} + > ${this.hass.formatEntityState(stateObj, mode)} ` @@ -206,13 +197,15 @@ class MoreInfoClimate extends LitElement { @closed=${stopPropagation} > ${stateObj.attributes.preset_mode - ? html`` + ? html` + + ` : html` ${stateObj.attributes.fan_mode - ? html`` + ? html` + + ` : html` `} @@ -301,13 +296,15 @@ class MoreInfoClimate extends LitElement { @closed=${stopPropagation} > ${stateObj.attributes.swing_mode - ? html`` + ? html` + + ` : html` - + - + .hass=${this.hass} + .stateObj=${this.stateObj} + attribute="direction" + attributeValue="forward" + > ${this.hass.formatEntityAttributeValue( this.stateObj, "direction", @@ -250,10 +257,13 @@ class MoreInfoFan extends LitElement { )} - + .hass=${this.hass} + .stateObj=${this.stateObj} + attribute="direction" + attributeValue="reverse" + > ${this.hass.formatEntityAttributeValue( this.stateObj, "direction", diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index d9ae9ca08c..28d2ab3de0 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -110,17 +110,21 @@ class MoreInfoHumidifier extends LitElement { @closed=${stopPropagation} > ${stateObj.attributes.mode - ? html`` - : html``} + ? html` + + ` + : html` + + `} ${stateObj.attributes.available_modes!.map( (mode) => html` diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 6054f0b695..5754e27c71 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -285,19 +285,19 @@ class MoreInfoLight extends LitElement { .path=${mdiCreation} >`} ${this.stateObj.attributes.effect_list?.map( - (mode) => html` - + (effect) => html` + ${this.hass.formatEntityAttributeValue( this.stateObj!, "effect", - mode + effect )} ` diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 2dd3dacae0..ae4a7c892a 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -8,9 +8,8 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { computeDomain } from "../../../../common/entity/compute_domain"; +import { until } from "lit/directives/until"; import { computeStateName } from "../../../../common/entity/compute_state_name"; -import { domainIcon } from "../../../../common/entity/domain_icon"; import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name"; import "../../../../components/ha-card"; import "../../../../components/ha-icon"; @@ -19,6 +18,7 @@ import { ExtEntityRegistryEntry, getExtendedEntityRegistryEntry, } from "../../../../data/entity_registry"; +import { entryIcon } from "../../../../data/icons"; import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog"; import type { HomeAssistant } from "../../../../types"; import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card"; @@ -198,6 +198,8 @@ export class HaDeviceEntitiesCard extends LitElement { entry.name || (entry as ExtEntityRegistryEntry).original_name; + const icon = until(entryIcon(this.hass, entry)); + return html` - +
${name ? stripPrefixFromEntityName(name, this.deviceName.toLowerCase()) || diff --git a/src/panels/config/entities/entity-registry-settings-editor.ts b/src/panels/config/entities/entity-registry-settings-editor.ts index e7da877192..d5d24967bf 100644 --- a/src/panels/config/entities/entity-registry-settings-editor.ts +++ b/src/panels/config/entities/entity-registry-settings-editor.ts @@ -18,7 +18,6 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeObjectId } from "../../../common/entity/compute_object_id"; -import { domainIcon } from "../../../common/entity/domain_icon"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { formatNumber } from "../../../common/number/format_number"; import { stringCompare } from "../../../common/string/compare"; @@ -32,6 +31,7 @@ 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"; @@ -373,29 +373,30 @@ export class EntityRegistrySettingsEditor extends LitElement { >`} ${this.hideIcon ? nothing - : html``} + : html` + + ${!this._icon && !stateObj?.attributes.icon && stateObj + ? html` + + ` + : nothing} + + `} ${domain === "switch" ? html`((mode) => ({ value: mode, label: this.hass!.formatEntityState(this.stateObj!, mode), - icon: html``, + icon: html` + + `, })); if (this._config.style === "dropdown") { @@ -154,17 +153,15 @@ class HuiClimateHvacModesCardFeature @closed=${stopPropagation} > ${this._currentHvacMode - ? html`` - : html``} + ? html` + + ` + : html` + + `} ${options.map( (option) => html` diff --git a/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts b/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts index 4ca246c02c..d4186ce0be 100644 --- a/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts +++ b/src/panels/lovelace/cards/tile/badges/tile-badge-humidifier.ts @@ -10,9 +10,9 @@ import { import { RenderBadgeFunction } from "./tile-badge"; export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => { - const hvacAction = (stateObj as HumidifierEntity).attributes.action; + const action = (stateObj as HumidifierEntity).attributes.action; - if (!hvacAction || hvacAction === "off") { + if (!action || action === "off") { return nothing; } @@ -21,7 +21,7 @@ export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => { style=${styleMap({ "--tile-badge-background-color": stateColorCss( stateObj, - HUMIDIFIER_ACTION_MODE[hvacAction] + HUMIDIFIER_ACTION_MODE[action] ), })} >