mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Add range support to icon translations (#25541)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
2d549ba22f
commit
9b7db191a6
@ -2,7 +2,6 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
import { updateIcon } from "./update_icon";
|
import { updateIcon } from "./update_icon";
|
||||||
import { deviceTrackerIcon } from "./device_tracker_icon";
|
import { deviceTrackerIcon } from "./device_tracker_icon";
|
||||||
import { batteryIcon } from "./battery_icon";
|
|
||||||
|
|
||||||
export const stateIcon = (
|
export const stateIcon = (
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
@ -10,17 +9,10 @@ export const stateIcon = (
|
|||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
const compareState = state ?? stateObj.state;
|
const compareState = state ?? stateObj.state;
|
||||||
const dc = stateObj.attributes.device_class;
|
|
||||||
switch (domain) {
|
switch (domain) {
|
||||||
case "update":
|
case "update":
|
||||||
return updateIcon(stateObj, compareState);
|
return updateIcon(stateObj, compareState);
|
||||||
|
|
||||||
case "sensor":
|
|
||||||
if (dc === "battery") {
|
|
||||||
return batteryIcon(stateObj, compareState);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "device_tracker":
|
case "device_tracker":
|
||||||
return deviceTrackerIcon(stateObj, compareState);
|
return deviceTrackerIcon(stateObj, compareState);
|
||||||
|
|
||||||
|
@ -145,10 +145,12 @@ type PlatformIcons = Record<
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
state: Record<string, string>;
|
state: Record<string, string>;
|
||||||
|
range?: Record<string, string>;
|
||||||
state_attributes: Record<
|
state_attributes: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
state: Record<string, string>;
|
state: Record<string, string>;
|
||||||
|
range?: Record<string, string>;
|
||||||
default: string;
|
default: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
@ -160,10 +162,12 @@ export type ComponentIcons = Record<
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
state?: Record<string, string>;
|
state?: Record<string, string>;
|
||||||
|
range?: Record<string, string>;
|
||||||
state_attributes?: Record<
|
state_attributes?: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
state: Record<string, string>;
|
state: Record<string, string>;
|
||||||
|
range?: Record<string, string>;
|
||||||
default: string;
|
default: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
@ -286,6 +290,74 @@ export const getServiceIcons = async (
|
|||||||
return resources.services.domains[domain];
|
return resources.services.domains[domain];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cache for sorted range keys
|
||||||
|
const sortedRangeCache = new WeakMap<Record<string, string>, number[]>();
|
||||||
|
|
||||||
|
// Helper function to get an icon from a range of values
|
||||||
|
const getIconFromRange = (
|
||||||
|
value: number,
|
||||||
|
range: Record<string, string>
|
||||||
|
): string | undefined => {
|
||||||
|
// Get cached range values or compute and cache them
|
||||||
|
let rangeValues = sortedRangeCache.get(range);
|
||||||
|
if (!rangeValues) {
|
||||||
|
rangeValues = Object.keys(range)
|
||||||
|
.map(Number)
|
||||||
|
.filter((k) => !isNaN(k))
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
sortedRangeCache.set(range, rangeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeValues.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value is below the first threshold, return undefined
|
||||||
|
// (we'll fall back to the default icon)
|
||||||
|
if (value < rangeValues[0]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the highest threshold that's less than or equal to the value
|
||||||
|
let selectedThreshold = rangeValues[0];
|
||||||
|
for (const threshold of rangeValues) {
|
||||||
|
if (value >= threshold) {
|
||||||
|
selectedThreshold = threshold;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return range[selectedThreshold.toString()];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get an icon based on state and translations
|
||||||
|
const getIconFromTranslations = (
|
||||||
|
state: string | number | undefined,
|
||||||
|
translations:
|
||||||
|
| {
|
||||||
|
default?: string;
|
||||||
|
state?: Record<string, string>;
|
||||||
|
range?: Record<string, string>;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
): string | undefined => {
|
||||||
|
if (!translations) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check for exact state match
|
||||||
|
if (state && translations.state?.[state]) {
|
||||||
|
return translations.state[state];
|
||||||
|
}
|
||||||
|
// Then check for range-based icons if we have a numeric state
|
||||||
|
if (state !== undefined && translations.range && !isNaN(Number(state))) {
|
||||||
|
return getIconFromRange(Number(state), translations.range);
|
||||||
|
}
|
||||||
|
// Fallback to default icon
|
||||||
|
return translations.default;
|
||||||
|
};
|
||||||
|
|
||||||
export const entityIcon = async (
|
export const entityIcon = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
@ -331,7 +403,8 @@ const getEntityIcon = async (
|
|||||||
const platformIcons = await getPlatformIcons(hass, platform);
|
const platformIcons = await getPlatformIcons(hass, platform);
|
||||||
if (platformIcons) {
|
if (platformIcons) {
|
||||||
const translations = platformIcons[domain]?.[translation_key];
|
const translations = platformIcons[domain]?.[translation_key];
|
||||||
icon = (state && translations?.state?.[state]) || translations?.default;
|
|
||||||
|
icon = getIconFromTranslations(state, translations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +418,8 @@ const getEntityIcon = async (
|
|||||||
const translations =
|
const translations =
|
||||||
(device_class && entityComponentIcons[device_class]) ||
|
(device_class && entityComponentIcons[device_class]) ||
|
||||||
entityComponentIcons._;
|
entityComponentIcons._;
|
||||||
icon = (state && translations?.state?.[state]) || translations?.default;
|
|
||||||
|
icon = getIconFromTranslations(state, translations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return icon;
|
return icon;
|
||||||
@ -372,9 +446,10 @@ export const attributeIcon = async (
|
|||||||
if (translation_key && platform) {
|
if (translation_key && platform) {
|
||||||
const platformIcons = await getPlatformIcons(hass, platform);
|
const platformIcons = await getPlatformIcons(hass, platform);
|
||||||
if (platformIcons) {
|
if (platformIcons) {
|
||||||
const translations =
|
icon = getIconFromTranslations(
|
||||||
platformIcons[domain]?.[translation_key]?.state_attributes?.[attribute];
|
value,
|
||||||
icon = (value && translations?.state?.[value]) || translations?.default;
|
platformIcons[domain]?.[translation_key]?.state_attributes?.[attribute]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
@ -384,7 +459,8 @@ export const attributeIcon = async (
|
|||||||
(deviceClass &&
|
(deviceClass &&
|
||||||
entityComponentIcons[deviceClass]?.state_attributes?.[attribute]) ||
|
entityComponentIcons[deviceClass]?.state_attributes?.[attribute]) ||
|
||||||
entityComponentIcons._?.state_attributes?.[attribute];
|
entityComponentIcons._?.state_attributes?.[attribute];
|
||||||
icon = (value && translations?.state?.[value]) || translations?.default;
|
|
||||||
|
icon = getIconFromTranslations(value, translations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return icon;
|
return icon;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user