Fix localize key type errors for states (#14691)

* Replace unavailable state checks with type predicate

* Remove localize exceptions related to state

* Use literal types for climate attributes

* Add fan action to climate tile badge

* Use literal types for truncated states in badges

* Use literal type for humidifier state

* Replace unavailable state checks in calendar and tile card

* Avoid string split for truncated key
This commit is contained in:
Steve Repsher 2022-12-14 13:39:10 -05:00 committed by GitHub
parent b4d6fc3c20
commit e8e4733fc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 153 additions and 112 deletions

View File

@ -3,6 +3,7 @@ import { HvacAction } from "../../../data/climate";
export const CLIMATE_HVAC_ACTION_COLORS: Record<HvacAction, string> = {
cooling: "var(--rgb-state-climate-cool-color)",
drying: "var(--rgb-state-climate-dry-color)",
fan: "var(--rgb-state-climate-fan-only-color)",
heating: "var(--rgb-state-climate-heat-color)",
idle: "var(--rgb-state-climate-idle-color)",
off: "var(--rgb-state-climate-off-color)",

View File

@ -2,7 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE_STATES } from "../../data/entity";
const FIXED_DOMAIN_STATES = {
export const FIXED_DOMAIN_STATES = {
alarm_control_panel: [
"armed_away",
"armed_custom_bypass",
@ -57,7 +57,7 @@ const FIXED_DOMAIN_STATES = {
"windy-variant",
"windy",
],
};
} as const;
const FIXED_DOMAIN_ATTRIBUTE_STATES = {
alarm_control_panel: {

View File

@ -1,5 +1,5 @@
import { HassEntity } from "home-assistant-js-websocket";
import { OFF, UNAVAILABLE, UNAVAILABLE_STATES } from "../../data/entity";
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity";
import { computeDomain } from "./compute_domain";
export function stateActive(stateObj: HassEntity, state?: string): boolean {
@ -10,7 +10,7 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
return compareState !== UNAVAILABLE;
}
if (UNAVAILABLE_STATES.includes(compareState)) {
if (isUnavailableState(compareState)) {
return false;
}

View File

@ -12,9 +12,6 @@ import { getLocalLanguage } from "../../util/common-translation";
export type LocalizeKeys =
| FlattenObjectKeys<Omit<TranslationDict, "supervisor">>
| `panel.${string}`
| `state.${string}`
| `state_attributes.${string}`
| `state_badge.${string}`
| `ui.card.alarm_control_panel.${string}`
| `ui.card.weather.attributes.${string}`
| `ui.card.weather.cardinal_direction.${string}`

View File

@ -12,7 +12,7 @@ import { property, state } from "lit/decorators";
import { STATES_OFF } from "../../common/const";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../data/entity";
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { forwardHaptic } from "../../data/haptics";
import { HomeAssistant } from "../../types";
import "../ha-formfield";
@ -22,7 +22,7 @@ import "../ha-switch";
const isOn = (stateObj?: HassEntity) =>
stateObj !== undefined &&
!STATES_OFF.includes(stateObj.state) &&
!UNAVAILABLE_STATES.includes(stateObj.state);
!isUnavailableState(stateObj.state);
export class HaEntityToggle extends LitElement {
// hass is not a property so that we only re-render on stateObj changes

View File

@ -10,21 +10,45 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { arrayLiteralIncludes } from "../../common/array/literal-includes";
import secondsToDuration from "../../common/datetime/seconds_to_duration";
import { computeStateDisplay } from "../../common/entity/compute_state_display";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
import {
formatNumber,
getNumberFormatOptions,
isNumericState,
} from "../../common/number/format_number";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { timerTimeRemaining } from "../../data/timer";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
import "../ha-state-icon";
// Define the domains whose states have special truncated strings
const TRUNCATED_DOMAINS = [
"alarm_control_panel",
"device_tracker",
"person",
] as const satisfies ReadonlyArray<keyof typeof FIXED_DOMAIN_STATES>;
type TruncatedDomain = typeof TRUNCATED_DOMAINS[number];
type TruncatedKey = {
[T in TruncatedDomain]: `${T}.${typeof FIXED_DOMAIN_STATES[T][number]}`;
}[TruncatedDomain];
const getTruncatedKey = (domainKey: string, stateKey: string) => {
if (
arrayLiteralIncludes(TRUNCATED_DOMAINS)(domainKey) &&
arrayLiteralIncludes(FIXED_DOMAIN_STATES[domainKey])(stateKey)
) {
return `${domainKey}.${stateKey}` as TruncatedKey;
}
return null;
};
@customElement("ha-state-label-badge")
export class HaStateLabelBadge extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@ -186,19 +210,18 @@ export class HaStateLabelBadge extends LitElement {
}
}
private _computeLabel(domain, entityState, _timerTimeRemaining) {
if (
entityState.state === UNAVAILABLE ||
["device_tracker", "alarm_control_panel", "person"].includes(domain)
) {
// Localize the state with a special state_badge namespace, which has variations of
// the state translations that are truncated to fit within the badge label. Translations
// are only added for device_tracker, alarm_control_panel and person.
return (
this.hass!.localize(`state_badge.${domain}.${entityState.state}`) ||
this.hass!.localize(`state_badge.default.${entityState.state}`) ||
entityState.state
);
private _computeLabel(
domain: string,
entityState: HassEntity,
_timerTimeRemaining = 0
) {
// For unavailable states or certain domains, use a special translation that is truncated to fit within the badge label
if (isUnavailableState(entityState.state)) {
return this.hass!.localize(`state_badge.default.${entityState.state}`);
}
const domainStateKey = getTruncatedKey(domain, entityState.state);
if (domainStateKey) {
return this.hass!.localize(`state_badge.${domainStateKey}`);
}
if (domain === "timer") {
return secondsToDuration(_timerTimeRemaining);

View File

@ -1,22 +1,21 @@
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { formatNumber } from "../common/number/format_number";
import { CLIMATE_PRESET_NONE } from "../data/climate";
import { UNAVAILABLE_STATES } from "../data/entity";
import { ClimateEntity, CLIMATE_PRESET_NONE } from "../data/climate";
import { isUnavailableState } from "../data/entity";
import type { HomeAssistant } from "../types";
@customElement("ha-climate-state")
class HaClimateState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ attribute: false }) public stateObj!: ClimateEntity;
protected render(): TemplateResult {
const currentStatus = this._computeCurrentStatus();
return html`<div class="target">
${!UNAVAILABLE_STATES.includes(this.stateObj.state)
${!isUnavailableState(this.stateObj.state)
? html`<span class="state-label">
${this._localizeState()}
${this.stateObj.attributes.preset_mode &&
@ -31,7 +30,7 @@ class HaClimateState extends LitElement {
: this._localizeState()}
</div>
${currentStatus && !UNAVAILABLE_STATES.includes(this.stateObj.state)
${currentStatus && !isUnavailableState(this.stateObj.state)
? html`<div class="current">
${this.hass.localize("ui.card.climate.currently")}:
<div class="unit">${currentStatus}</div>
@ -109,7 +108,7 @@ class HaClimateState extends LitElement {
}
private _localizeState(): string {
if (UNAVAILABLE_STATES.includes(this.stateObj.state)) {
if (isUnavailableState(this.stateObj.state)) {
return this.hass.localize(`state.default.${this.stateObj.state}`);
}

View File

@ -27,7 +27,7 @@ import { until } from "lit/directives/until";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce";
import { UNAVAILABLE_STATES } from "../../data/entity";
import { isUnavailableState } from "../../data/entity";
import type { MediaPlayerItem } from "../../data/media-player";
import {
browseMediaPlayer,
@ -247,7 +247,7 @@ export class HaMediaPlayerBrowse extends LitElement {
});
} else if (
err.code === "entity_not_found" &&
UNAVAILABLE_STATES.includes(this.hass.states[this.entityId]?.state)
isUnavailableState(this.hass.states[this.entityId]?.state)
) {
this._setError({
message: this.hass.localize(

View File

@ -2,7 +2,7 @@ import { getColorByIndex } from "../common/color/colors";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import type { HomeAssistant } from "../types";
import { UNAVAILABLE_STATES } from "./entity";
import { isUnavailableState } from "./entity";
export interface Calendar {
entity_id: string;
@ -138,7 +138,7 @@ export const getCalendars = (hass: HomeAssistant): Calendar[] =>
.filter(
(eid) =>
computeDomain(eid) === "calendar" &&
!UNAVAILABLE_STATES.includes(hass.states[eid].state)
!isUnavailableState(hass.states[eid].state)
)
.sort()
.map((eid, idx) => ({

View File

@ -2,6 +2,7 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { TranslationDict } from "../types";
export type HvacMode =
| "off"
@ -14,7 +15,12 @@ export type HvacMode =
export const CLIMATE_PRESET_NONE = "none";
export type HvacAction = "off" | "heating" | "cooling" | "drying" | "idle";
type ClimateAttributes = TranslationDict["state_attributes"]["climate"];
export type HvacAction = keyof ClimateAttributes["hvac_action"];
export type FanMode = keyof ClimateAttributes["fan_mode"];
export type PresetMode =
| keyof ClimateAttributes["preset_mode"]
| typeof CLIMATE_PRESET_NONE;
export type ClimateEntity = HassEntityBase & {
attributes: HassEntityAttributeBase & {
@ -34,10 +40,10 @@ export type ClimateEntity = HassEntityBase & {
target_humidity_high?: number;
min_humidity?: number;
max_humidity?: number;
fan_mode?: string;
fan_modes?: string[];
preset_mode?: string;
preset_modes?: string[];
fan_mode?: FanMode;
fan_modes?: FanMode[];
preset_mode?: PresetMode;
preset_modes?: PresetMode[];
swing_mode?: string;
swing_modes?: string[];
aux_heat?: "on" | "off";

View File

@ -1,7 +1,12 @@
import { arrayLiteralIncludes } from "../common/array/literal-includes";
export const UNAVAILABLE = "unavailable";
export const UNKNOWN = "unknown";
export const ON = "on";
export const OFF = "off";
export const UNAVAILABLE_STATES = [UNAVAILABLE, UNKNOWN];
export const OFF_STATES = [UNAVAILABLE, UNKNOWN, OFF];
export const UNAVAILABLE_STATES = [UNAVAILABLE, UNKNOWN] as const;
export const OFF_STATES = [UNAVAILABLE, UNKNOWN, OFF] as const;
export const isUnavailableState = arrayLiteralIncludes(UNAVAILABLE_STATES);
export const isOffState = arrayLiteralIncludes(OFF_STATES);

View File

@ -2,14 +2,24 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { FIXED_DOMAIN_STATES } from "../common/entity/get_states";
import { TranslationDict } from "../types";
import { UNAVAILABLE_STATES } from "./entity";
type HumidifierState =
| typeof FIXED_DOMAIN_STATES.humidifier[number]
| typeof UNAVAILABLE_STATES[number];
type HumidifierMode =
keyof TranslationDict["state_attributes"]["humidifier"]["mode"];
export type HumidifierEntity = HassEntityBase & {
state: HumidifierState;
attributes: HassEntityAttributeBase & {
humidity?: number;
min_humidity?: number;
max_humidity?: number;
mode?: string;
available_modes?: string[];
mode?: HumidifierMode;
available_modes?: HumidifierMode[];
};
};

View File

@ -35,7 +35,7 @@ import type {
import { supportsFeature } from "../common/entity/supports-feature";
import { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse";
import type { HomeAssistant, TranslationDict } from "../types";
import { UNAVAILABLE_STATES } from "./entity";
import { isUnavailableState } from "./entity";
import { isTTSMediaSource } from "./tts";
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
@ -259,7 +259,7 @@ export const computeMediaControls = (
const state = stateObj.state;
if (UNAVAILABLE_STATES.includes(state)) {
if (isUnavailableState(state)) {
return undefined;
}

View File

@ -4,7 +4,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-relative-time";
import { triggerAutomationActions } from "../../../data/automation";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
@customElement("more-info-automation")
@ -32,7 +32,7 @@ class MoreInfoAutomation extends LitElement {
<div class="actions">
<mwc-button
@click=${this._runActions}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj!.state)}
.disabled=${isUnavailableState(this.stateObj!.state)}
>
${this.hass.localize("ui.card.automation.trigger")}
</mwc-button>

View File

@ -2,7 +2,7 @@ import "@material/mwc-button";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
@customElement("more-info-counter")
@ -16,7 +16,7 @@ class MoreInfoCounter extends LitElement {
return html``;
}
const disabled = UNAVAILABLE_STATES.includes(this.stateObj!.state);
const disabled = isUnavailableState(this.stateObj!.state);
return html`
<div class="actions">

View File

@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-date-input";
import "../../../components/ha-time-input";
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import { isUnavailableState, UNKNOWN } from "../../../data/entity";
import {
setInputDateTimeValue,
stateToIsoDateString,
@ -28,7 +28,7 @@ class MoreInfoInputDatetime extends LitElement {
<ha-date-input
.locale=${this.hass.locale}
.value=${stateToIsoDateString(this.stateObj)}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
.disabled=${isUnavailableState(this.stateObj.state)}
@value-changed=${this._dateChanged}
>
</ha-date-input>
@ -45,7 +45,7 @@ class MoreInfoInputDatetime extends LitElement {
? this.stateObj.state.split(" ")[1]
: this.stateObj.state}
.locale=${this.hass.locale}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
.disabled=${isUnavailableState(this.stateObj.state)}
@value-changed=${this._timeChanged}
@click=${this._stopEventPropagation}
></ha-time-input>

View File

@ -9,7 +9,7 @@ import "../../../components/ha-checkbox";
import "../../../components/ha-circular-progress";
import "../../../components/ha-formfield";
import "../../../components/ha-markdown";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import {
UpdateEntity,
updateIsInstalling,
@ -37,7 +37,7 @@ class MoreInfoUpdate extends LitElement {
if (
!this.hass ||
!this.stateObj ||
UNAVAILABLE_STATES.includes(this.stateObj.state)
isUnavailableState(this.stateObj.state)
) {
return html``;
}

View File

@ -45,7 +45,7 @@ import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
@customElement("ha-scene-dashboard")
class HaSceneDashboard extends LitElement {
@ -116,7 +116,7 @@ class HaSceneDashboard extends LitElement {
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
${last_activated && !UNAVAILABLE_STATES.includes(last_activated)
${last_activated && !isUnavailableState(last_activated)
? dayDifference > 3
? formatShortDateTime(date, this.hass.locale)
: relativeTime(date, this.hass.locale)

View File

@ -42,7 +42,7 @@ import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@ -181,8 +181,7 @@ export class HuiAreaCard
: entities
).some(
(entity) =>
!UNAVAILABLE_STATES.includes(entity.state) &&
!STATES_OFF.includes(entity.state)
!isUnavailableState(entity.state) && !STATES_OFF.includes(entity.state)
);
}

View File

@ -27,7 +27,7 @@ import {
import { iconColorCSS } from "../../../common/style/icon_color_css";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { formatAttributeValue } from "../../../data/entity_attributes";
import { LightEntity } from "../../../data/light";
import { HomeAssistant } from "../../../types";
@ -130,7 +130,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
const domain = computeStateDomain(stateObj);
const showUnit = this._config.attribute
? this._config.attribute in stateObj.attributes
: !UNAVAILABLE_STATES.includes(stateObj.state);
: !isUnavailableState(stateObj.state);
const name = this._config.name || computeStateName(stateObj);

View File

@ -17,7 +17,7 @@ import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../../../components/ha-relative-time";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import {
ActionHandlerEvent,
CallServiceActionConfig,
@ -315,7 +315,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
${computeDomain(entityConf.entity) === "sensor" &&
stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
!UNAVAILABLE_STATES.includes(stateObj.state)
!isUnavailableState(stateObj.state)
? html`
<hui-timestamp-display
.hass=${this.hass}

View File

@ -18,7 +18,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { HumidifierEntity } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
@ -97,7 +97,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
const rtlDirection = computeRTLDirection(this.hass);
const slider = UNAVAILABLE_STATES.includes(stateObj.state)
const slider = isUnavailableState(stateObj.state)
? html` <round-slider disabled="true"></round-slider> `
: html`
<round-slider
@ -115,7 +115,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
const setValues = html`
<svg viewBox="0 0 24 20">
<text x="50%" dx="1" y="73%" text-anchor="middle" id="set-values">
${UNAVAILABLE_STATES.includes(stateObj.state) ||
${isUnavailableState(stateObj.state) ||
setHumidity === undefined ||
setHumidity === null
? ""
@ -132,8 +132,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
<svg viewBox="0 0 40 10" id="humidity">
<text x="50%" y="50%" text-anchor="middle" id="set-mode">
${this.hass!.localize(`state.default.${stateObj.state}`)}
${stateObj.attributes.mode &&
!UNAVAILABLE_STATES.includes(stateObj.state)
${stateObj.attributes.mode && !isUnavailableState(stateObj.state)
? html`
-
${this.hass!.localize(
@ -161,7 +160,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
<div id="slider-center">
<ha-icon-button
class="toggle-button"
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
@click=${this._toggle}
tabindex="0"
>
@ -225,7 +224,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
}
private _getSetHum(stateObj: HassEntity): undefined | number {
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
if (isUnavailableState(stateObj.state)) {
return undefined;
}

View File

@ -18,7 +18,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-state-icon";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
import { LightEntity, lightSupportsBrightness } from "../../../data/light";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
@ -118,7 +118,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
min="1"
max="100"
.value=${brightness}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
@value-changing=${this._dragEvent}
@value-changed=${this._setBrightness}
style=${styleMap({
@ -133,7 +133,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
"state-on": stateObj.state === "on",
"state-unavailable": stateObj.state === UNAVAILABLE,
})}"
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
style=${styleMap({
filter: this._computeBrightness(stateObj),
color: this._computeColor(stateObj),
@ -154,7 +154,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
</div>
<div id="info" .title=${name}>
${UNAVAILABLE_STATES.includes(stateObj.state)
${isUnavailableState(stateObj.state)
? html`
<div>
${computeStateDisplay(

View File

@ -22,7 +22,7 @@ import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-state-icon";
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import {
cleanupMediaTitle,
computeMediaControls,
@ -173,7 +173,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
const isOffState = entityState === "off";
const isUnavailable =
UNAVAILABLE_STATES.includes(entityState) ||
isUnavailableState(entityState) ||
(entityState === "off" && !supportsFeature(stateObj, SUPPORT_TURN_ON));
const hasNoImage = !this._image;
const controls = computeMediaControls(stateObj, false);

View File

@ -20,7 +20,7 @@ import "../../../components/tile/ha-tile-image";
import "../../../components/tile/ha-tile-info";
import { cameraUrlWithWidthHeight } from "../../../data/camera";
import { CoverEntity } from "../../../data/cover";
import { ON, UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState, ON } from "../../../data/entity";
import { FanEntity } from "../../../data/fan";
import { LightEntity } from "../../../data/light";
import { ActionHandlerEvent } from "../../../data/lovelace";
@ -171,7 +171,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
if (
(stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP ||
TIMESTAMP_STATE_DOMAINS.includes(domain)) &&
!UNAVAILABLE_STATES.includes(stateObj.state)
!isUnavailableState(stateObj.state)
) {
return html`
<hui-timestamp-display

View File

@ -1,5 +1,6 @@
import {
mdiClockOutline,
mdiFan,
mdiFire,
mdiPower,
mdiSnowflake,
@ -12,6 +13,7 @@ import { ComputeBadgeFunction } from "./tile-badge";
export const CLIMATE_HVAC_ACTION_ICONS: Record<HvacAction, string> = {
cooling: mdiSnowflake,
drying: mdiWaterPercent,
fan: mdiFan,
heating: mdiFire,
idle: mdiClockOutline,
off: mdiPower,

View File

@ -17,7 +17,7 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/search-input";
import "../../../../components/ha-circular-progress";
import { UNAVAILABLE_STATES } from "../../../../data/entity";
import { isUnavailableState } from "../../../../data/entity";
import type {
LovelaceCardConfig,
LovelaceConfig,
@ -163,12 +163,12 @@ export class HuiCardPicker extends LitElement {
this._usedEntities = [...usedEntities].filter(
(eid) =>
this.hass!.states[eid] &&
!UNAVAILABLE_STATES.includes(this.hass!.states[eid].state)
!isUnavailableState(this.hass!.states[eid].state)
);
this._unusedEntities = [...unusedEntities].filter(
(eid) =>
this.hass!.states[eid] &&
!UNAVAILABLE_STATES.includes(this.hass!.states[eid].state)
!isUnavailableState(this.hass!.states[eid].state)
);
this._loadCards();

View File

@ -1,6 +1,7 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/entity/ha-entity-toggle";
import { HumidifierEntity } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
@ -30,7 +31,7 @@ class HuiHumidifierEntityRow extends LitElement implements LovelaceRow {
return html``;
}
const stateObj = this.hass.states[this._config.entity];
const stateObj = this.hass.states[this._config.entity] as HumidifierEntity;
if (!stateObj) {
return html`

View File

@ -8,7 +8,7 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../components/ha-date-input";
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import { isUnavailableState, UNKNOWN } from "../../../data/entity";
import {
setInputDateTimeValue,
stateToIsoDateString,
@ -67,7 +67,7 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
<ha-date-input
.label=${stateObj.attributes.has_time ? name : undefined}
.locale=${this.hass.locale}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
.value=${stateToIsoDateString(stateObj)}
@value-changed=${this._dateChanged}
>
@ -83,7 +83,7 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
? stateObj.state.split(" ")[1]
: stateObj.state}
.locale=${this.hass.locale}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
@value-changed=${this._timeChanged}
@click=${this._stopEventPropagation}
></ha-time-input>

View File

@ -12,7 +12,7 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-slider";
import "../../../components/ha-textfield";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@ -85,7 +85,7 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
? html`
<div class="flex">
<ha-slider
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
.dir=${computeRTLDirection(this.hass)}
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
@ -109,7 +109,7 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
: html`
<div class="flex state">
<ha-textfield
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
pattern="[0-9]+([\\.][0-9]+)?"
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}

View File

@ -1,6 +1,6 @@
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@ -70,7 +70,7 @@ class HuiInputTextEntityRow extends LitElement implements LovelaceRow {
const newValue = ev.target.value;
// Filter out invalid text states
if (newValue && UNAVAILABLE_STATES.includes(newValue)) {
if (newValue && isUnavailableState(newValue)) {
ev.target.value = stateObj.state;
return;
}

View File

@ -8,7 +8,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
@ -51,7 +51,7 @@ class HuiLockEntityRow extends LitElement implements LovelaceRow {
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
<mwc-button
@click=${this._callService}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
.disabled=${isUnavailableState(stateObj.state)}
class="text-content"
>
${stateObj.state === "locked"

View File

@ -27,7 +27,7 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-icon-button";
import "../../../components/ha-slider";
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../../data/entity";
import {
computeMediaDescription,
ControlButton,
@ -203,7 +203,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
<div class="controls">
${supportsFeature(stateObj, SUPPORT_TURN_ON) &&
entityState === "off" &&
!UNAVAILABLE_STATES.includes(entityState)
!isUnavailableState(entityState)
? html`
<ha-icon-button
.path=${mdiPower}
@ -217,7 +217,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
: ""}
${supportsFeature(stateObj, SUPPORT_TURN_OFF) &&
entityState !== "off" &&
!UNAVAILABLE_STATES.includes(entityState)
!isUnavailableState(entityState)
? html`
<ha-icon-button
.path=${mdiPower}

View File

@ -8,7 +8,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { canRun, ScriptEntity } from "../../../data/script";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@ -65,7 +65,7 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow {
${stateObj.state === "off" || stateObj.attributes.max
? html`<mwc-button
@click=${this._runScript}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state) ||
.disabled=${isUnavailableState(stateObj.state) ||
!canRun(stateObj)}
>
${this._config.action_name ||

View File

@ -8,7 +8,7 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
import { HomeAssistant } from "../../../types";
@ -70,8 +70,7 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
})}
>
${stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
!UNAVAILABLE_STATES.includes(stateObj.state)
SENSOR_DEVICE_CLASS_TIMESTAMP && !isUnavailableState(stateObj.state)
? html`
<hui-timestamp-display
.hass=${this.hass}

View File

@ -1,6 +1,6 @@
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
import { TextEntity, setValue } from "../../../data/text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@ -71,7 +71,7 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
const newValue = ev.target.value;
// Filter out invalid text states
if (newValue && UNAVAILABLE_STATES.includes(newValue)) {
if (newValue && isUnavailableState(newValue)) {
ev.target.value = stateObj.state;
return;
}

View File

@ -2,7 +2,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import "../../../components/entity/ha-entity-toggle";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
@ -44,7 +44,7 @@ class HuiToggleEntityRow extends LitElement implements LovelaceRow {
const showToggle =
stateObj.state === "on" ||
stateObj.state === "off" ||
UNAVAILABLE_STATES.includes(stateObj.state);
isUnavailableState(stateObj.state);
return html`
<hui-generic-entity-row

View File

@ -13,7 +13,7 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa
import { computeStateName } from "../../../common/entity/compute_state_name";
import { formatNumber } from "../../../common/number/format_number";
import "../../../components/entity/state-badge";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { isUnavailableState } from "../../../data/entity";
import { ActionHandlerEvent } from "../../../data/lovelace";
import {
getSecondaryWeatherAttribute,
@ -141,7 +141,7 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
})}
>
<div>
${UNAVAILABLE_STATES.includes(stateObj.state) ||
${isUnavailableState(stateObj.state) ||
stateObj.attributes.temperature === undefined ||
stateObj.attributes.temperature === null
? computeStateDisplay(

View File

@ -7,7 +7,7 @@ import { computeDomain } from "../common/entity/compute_domain";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/entity/state-info";
import { UNAVAILABLE_STATES } from "../data/entity";
import { isUnavailableState } from "../data/entity";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor";
import "../panels/lovelace/components/hui-timestamp-display";
import { haStyle } from "../resources/styles";
@ -42,7 +42,7 @@ export class StateCardDisplay extends LitElement {
${computeDomain(this.stateObj.entity_id) === "sensor" &&
this.stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
!UNAVAILABLE_STATES.includes(this.stateObj.state)
!isUnavailableState(this.stateObj.state)
? html` <hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(this.stateObj.state)}

View File

@ -4,7 +4,7 @@ import { CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/ha-entity-toggle";
import "../components/entity/state-info";
import { UNAVAILABLE_STATES } from "../data/entity";
import { isUnavailableState } from "../data/entity";
import { canRun, ScriptEntity } from "../data/script";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@ -41,7 +41,7 @@ export class StateCardScript extends LitElement {
${stateObj.state === "off" || stateObj.attributes.max
? html`<mwc-button
@click=${this._runScript}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state) ||
.disabled=${isUnavailableState(stateObj.state) ||
!canRun(stateObj)}
>
${this.hass!.localize("ui.card.script.run")}

View File

@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
import { computeStateName } from "../common/entity/compute_state_name";
import { stopPropagation } from "../common/dom/stop_propagation";
import "../components/entity/state-badge";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../data/entity";
import { isUnavailableState, UNAVAILABLE } from "../data/entity";
import { TextEntity, setValue } from "../data/text";
import type { HomeAssistant } from "../types";
@ -37,7 +37,7 @@ class StateCardText extends LitElement {
const value = ev.target.value;
// Filter out invalid text states
if (value && UNAVAILABLE_STATES.includes(value)) {
if (value && isUnavailableState(value)) {
ev.target.value = this.stateObj.state;
return;
}