This commit is contained in:
karwosts 2024-04-27 22:14:11 +02:00 committed by GitHub
commit 142bb07162
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 190 additions and 65 deletions

View File

@ -368,6 +368,7 @@ export class DemoEntityState extends LitElement {
hass.localize,
entry.stateObj,
hass.locale,
[], // numericDeviceClasses
hass.config,
hass.entities
)}`,

View File

@ -19,28 +19,11 @@ import { blankBeforeUnit } from "../translations/blank_before_unit";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
export const computeStateDisplaySingleEntity = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
config: HassConfig,
entity: EntityRegistryDisplayEntry | undefined,
state?: string
): string =>
computeStateDisplayFromEntityAttributes(
localize,
locale,
config,
entity,
stateObj.entity_id,
stateObj.attributes,
state !== undefined ? state : stateObj.state
);
export const computeStateDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
sensorNumericDeviceClasses: string[],
config: HassConfig,
entities: HomeAssistant["entities"],
state?: string
@ -52,6 +35,7 @@ export const computeStateDisplay = (
return computeStateDisplayFromEntityAttributes(
localize,
locale,
sensorNumericDeviceClasses,
config,
entity,
stateObj.entity_id,
@ -63,6 +47,7 @@ export const computeStateDisplay = (
export const computeStateDisplayFromEntityAttributes = (
localize: LocalizeFunc,
locale: FrontendLocaleData,
sensorNumericDeviceClasses: string[],
config: HassConfig,
entity: EntityRegistryDisplayEntry | undefined,
entityId: string,
@ -73,8 +58,15 @@ export const computeStateDisplayFromEntityAttributes = (
return localize(`state.default.${state}`);
}
const domain = computeDomain(entityId);
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
if (isNumericFromAttributes(attributes)) {
if (
isNumericFromAttributes(
attributes,
domain === "sensor" ? sensorNumericDeviceClasses : []
)
) {
// state is duration
if (
attributes.device_class === "duration" &&
@ -120,8 +112,6 @@ export const computeStateDisplayFromEntityAttributes = (
return value;
}
const domain = computeDomain(entityId);
if (domain === "datetime") {
const time = new Date(state);
return formatDateTime(time, locale, config);

View File

@ -14,8 +14,12 @@ export const isNumericState = (stateObj: HassEntity): boolean =>
isNumericFromAttributes(stateObj.attributes);
export const isNumericFromAttributes = (
attributes: HassEntityAttributeBase
): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
attributes: HassEntityAttributeBase,
numericDeviceClasses?: string[]
): boolean =>
!!attributes.unit_of_measurement ||
!!attributes.state_class ||
(numericDeviceClasses || []).includes(attributes.device_class || "");
export const numberFormatToLocale = (
localeOptions: FrontendLocaleData

View File

@ -21,7 +21,8 @@ export const computeFormatFunctions = async (
localize: LocalizeFunc,
locale: FrontendLocaleData,
config: HassConfig,
entities: HomeAssistant["entities"]
entities: HomeAssistant["entities"],
sensorNumericDeviceClasses: string[]
): Promise<{
formatEntityState: FormatEntityStateFunc;
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
@ -35,7 +36,15 @@ export const computeFormatFunctions = async (
return {
formatEntityState: (stateObj, state) =>
computeStateDisplay(localize, stateObj, locale, config, entities, state),
computeStateDisplay(
localize,
stateObj,
locale,
sensorNumericDeviceClasses,
config,
entities,
state
),
formatEntityAttributeValue: (stateObj, attribute, value) =>
computeAttributeValueDisplay(
localize,

View File

@ -297,6 +297,7 @@ const processTimelineEntity = (
state_localize: computeStateDisplayFromEntityAttributes(
localize,
locale,
[], // numeric device classes not used for Timeline
config,
entities[entityId],
entityId,

View File

@ -18,7 +18,9 @@ export type SensorNumericDeviceClasses = {
numeric_device_classes: string[];
};
let sensorNumericDeviceClassesCache: SensorNumericDeviceClasses | undefined;
let sensorNumericDeviceClassesCache:
| Promise<SensorNumericDeviceClasses>
| undefined;
export const getSensorNumericDeviceClasses = async (
hass: HomeAssistant
@ -26,7 +28,7 @@ export const getSensorNumericDeviceClasses = async (
if (sensorNumericDeviceClassesCache) {
return sensorNumericDeviceClassesCache;
}
sensorNumericDeviceClassesCache = await hass.callWS({
sensorNumericDeviceClassesCache = hass.callWS({
type: "sensor/numeric_device_classes",
});
return sensorNumericDeviceClassesCache!;

View File

@ -119,7 +119,8 @@ export const provideHass = (
hass().localize,
hass().locale,
hass().config,
hass().entities
hass().entities,
[] // numericDeviceClasses
);
hass().updateHass({
formatEntityState,

View File

@ -23,7 +23,6 @@ import { transform } from "../../../common/decorators/transform";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDisplaySingleEntity } from "../../../common/entity/compute_state_display";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import {
@ -243,13 +242,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
: ""}
${this._config.show_state && stateObj
? html`<span class="state">
${computeStateDisplaySingleEntity(
this._localize,
stateObj,
this._locale,
this._hassConfig,
this._entity
)}
${this.hass.formatEntityState(stateObj)}
</span>`
: ""}
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}

View File

@ -1,7 +1,6 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import "../components/entity/state-info";
import HassMediaPlayerEntity from "../util/hass-media-player-model";
import { HomeAssistant } from "../types";
@ -26,7 +25,7 @@ class StateCardMediaPlayer extends LitElement {
></state-info>
<div class="state">
<div class="main-text" take-height=${!playerObj.secondaryTitle}>
${this._computePrimaryText(this.hass.localize, playerObj)}
${this._computePrimaryText(playerObj)}
</div>
<div class="secondary-text">${playerObj.secondaryTitle}</div>
</div>
@ -34,16 +33,9 @@ class StateCardMediaPlayer extends LitElement {
`;
}
private _computePrimaryText(localize, playerObj) {
private _computePrimaryText(playerObj) {
return (
playerObj.primaryTitle ||
computeStateDisplay(
localize,
playerObj.stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities
)
playerObj.primaryTitle || this.hass.formatEntityState(playerObj.stateObj)
);
}

View File

@ -1,4 +1,5 @@
import { computeFormatFunctions } from "../common/translations/entity-state";
import { getSensorNumericDeviceClasses } from "../data/sensor";
import { Constructor, HomeAssistant } from "../types";
import { HassBaseEl } from "./hass-base-mixin";
@ -31,6 +32,10 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) => {
private _updateStateDisplay = async () => {
if (!this.hass) return;
const { numeric_device_classes: sensorNumericDeviceClasses } =
await getSensorNumericDeviceClasses(this.hass);
const {
formatEntityState,
formatEntityAttributeName,
@ -39,7 +44,8 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) => {
this.hass.localize,
this.hass.locale,
this.hass.config,
this.hass.entities
this.hass.entities,
sensorNumericDeviceClasses
);
this._updateHass({
formatEntityState,

View File

@ -18,6 +18,8 @@ describe("computeStateDisplay", () => {
const localize = (message, ...args) =>
message + (args.length ? ": " + args.join(",") : "");
const numericDeviceClasses = [];
beforeEach(() => {
localeData = {
language: "en",
@ -36,7 +38,14 @@ describe("computeStateDisplay", () => {
attributes: {},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"component.binary_sensor.entity_component._.state.off"
);
});
@ -50,7 +59,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"component.binary_sensor.state.moisture.off"
);
});
@ -70,7 +86,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
altLocalize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"component.binary_sensor.state.invalid_device_class.off"
);
});
@ -84,7 +107,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"123 m"
);
});
@ -98,7 +128,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"1,234.5 m"
);
});
@ -112,7 +149,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"1,234.5"
);
});
@ -132,7 +176,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
altLocalize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"state.default.unknown"
);
});
@ -152,7 +203,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
altLocalize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"state.default.unavailable"
);
});
@ -172,7 +230,14 @@ describe("computeStateDisplay", () => {
attributes: {},
};
assert.strictEqual(
computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
altLocalize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"component.sensor.entity_component._.state.custom_state"
);
});
@ -194,14 +259,28 @@ describe("computeStateDisplay", () => {
};
it("Uses am/pm time format", () => {
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"November 18, 2017 at 11:12 PM"
);
});
it("Uses 24h time format", () => {
localeData.time_format = TimeFormat.twenty_four;
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"November 18, 2017 at 23:12"
);
});
@ -223,7 +302,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"November 18, 2017"
);
});
@ -246,14 +332,28 @@ describe("computeStateDisplay", () => {
it("Uses am/pm time format", () => {
localeData.time_format = TimeFormat.am_pm;
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"11:12 PM"
);
});
it("Uses 24h time format", () => {
localeData.time_format = TimeFormat.twenty_four;
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"23:12"
);
});
@ -280,6 +380,7 @@ describe("computeStateDisplay", () => {
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{},
"2021-07-04 15:40:03"
@ -294,6 +395,7 @@ describe("computeStateDisplay", () => {
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{},
"2021-07-04 15:40:03"
@ -323,6 +425,7 @@ describe("computeStateDisplay", () => {
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{},
"2021-07-04"
@ -353,6 +456,7 @@ describe("computeStateDisplay", () => {
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{},
"17:05:07"
@ -367,6 +471,7 @@ describe("computeStateDisplay", () => {
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{},
"17:05:07"
@ -389,7 +494,14 @@ describe("computeStateDisplay", () => {
attributes: {},
};
assert.strictEqual(
computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
altLocalize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"state.default.unavailable"
);
});
@ -404,7 +516,14 @@ describe("computeStateDisplay", () => {
attributes: {},
};
assert.strictEqual(
computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}),
computeStateDisplay(
altLocalize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
{}
),
"My Custom State"
);
});
@ -422,7 +541,14 @@ describe("computeStateDisplay", () => {
},
};
assert.strictEqual(
computeStateDisplay(localize, stateObj, localeData, demoConfig, entities),
computeStateDisplay(
localize,
stateObj,
localeData,
numericDeviceClasses,
demoConfig,
entities
),
"component.custom_integration.entity.sensor.custom_translation.state.custom_state"
);
});