mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Custom device_classes for alert icons in Area Card (#19131)
* Custom device_classes for alerts in Area Card * small refactor * drop more-info feature * Update src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * localization and css updates --------- Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
2053cf23c2
commit
9f26bedf51
@ -26,6 +26,7 @@ import memoizeOne from "memoize-one";
|
||||
import { STATES_OFF } from "../../../common/const";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { binarySensorIcon } from "../../../common/entity/binary_sensor_icon";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
@ -67,7 +68,7 @@ const TOGGLE_DOMAINS = ["light", "switch", "fan"];
|
||||
|
||||
const OTHER_DOMAINS = ["camera"];
|
||||
|
||||
const DEVICE_CLASSES = {
|
||||
export const DEVICE_CLASSES = {
|
||||
sensor: ["temperature", "humidity"],
|
||||
binary_sensor: ["motion", "moisture"],
|
||||
};
|
||||
@ -113,6 +114,8 @@ export class HuiAreaCard
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
private _deviceClasses: { [key: string]: string[] } = DEVICE_CLASSES;
|
||||
|
||||
private _ratio: {
|
||||
w: number;
|
||||
h: number;
|
||||
@ -123,6 +126,7 @@ export class HuiAreaCard
|
||||
areaId: string,
|
||||
devicesInArea: Set<string>,
|
||||
registryEntities: EntityRegistryEntry[],
|
||||
deviceClasses: { [key: string]: string[] },
|
||||
states: HomeAssistant["states"]
|
||||
) => {
|
||||
const entitiesInArea = registryEntities
|
||||
@ -156,7 +160,7 @@ export class HuiAreaCard
|
||||
|
||||
if (
|
||||
(SENSOR_DOMAINS.includes(domain) || ALERT_DOMAINS.includes(domain)) &&
|
||||
!DEVICE_CLASSES[domain].includes(
|
||||
!deviceClasses[domain].includes(
|
||||
stateObj.attributes.device_class || ""
|
||||
)
|
||||
) {
|
||||
@ -173,11 +177,12 @@ export class HuiAreaCard
|
||||
}
|
||||
);
|
||||
|
||||
private _isOn(domain: string, deviceClass?: string): boolean | undefined {
|
||||
private _isOn(domain: string, deviceClass?: string): HassEntity | undefined {
|
||||
const entities = this._entitiesByDomain(
|
||||
this._config!.area,
|
||||
this._devicesInArea(this._config!.area, this._devices!),
|
||||
this._entities!,
|
||||
this._deviceClasses,
|
||||
this.hass.states
|
||||
)[domain];
|
||||
if (!entities) {
|
||||
@ -189,7 +194,7 @@ export class HuiAreaCard
|
||||
(entity) => entity.attributes.device_class === deviceClass
|
||||
)
|
||||
: entities
|
||||
).some(
|
||||
).find(
|
||||
(entity) =>
|
||||
!isUnavailableState(entity.state) && !STATES_OFF.includes(entity.state)
|
||||
);
|
||||
@ -200,6 +205,7 @@ export class HuiAreaCard
|
||||
this._config!.area,
|
||||
this._devicesInArea(this._config!.area, this._devices!),
|
||||
this._entities!,
|
||||
this._deviceClasses,
|
||||
this.hass.states
|
||||
)[domain].filter((entity) =>
|
||||
deviceClass ? entity.attributes.device_class === deviceClass : true
|
||||
@ -273,6 +279,11 @@ export class HuiAreaCard
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
|
||||
this._deviceClasses = { ...DEVICE_CLASSES };
|
||||
if (config.alert_classes) {
|
||||
this._deviceClasses.binary_sensor = config.alert_classes;
|
||||
}
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
@ -314,6 +325,7 @@ export class HuiAreaCard
|
||||
this._config.area,
|
||||
this._devicesInArea(this._config.area, this._devices),
|
||||
this._entities,
|
||||
this._deviceClasses,
|
||||
this.hass.states
|
||||
);
|
||||
|
||||
@ -355,6 +367,7 @@ export class HuiAreaCard
|
||||
this._config.area,
|
||||
this._devicesInArea(this._config.area, this._devices),
|
||||
this._entities,
|
||||
this._deviceClasses,
|
||||
this.hass.states
|
||||
);
|
||||
const area = this._area(this._config.area, this._areas);
|
||||
@ -427,17 +440,16 @@ export class HuiAreaCard
|
||||
if (!(domain in entitiesByDomain)) {
|
||||
return "";
|
||||
}
|
||||
return DEVICE_CLASSES[domain].map((deviceClass) =>
|
||||
this._isOn(domain, deviceClass)
|
||||
? html`
|
||||
${DOMAIN_ICONS[domain][deviceClass]
|
||||
? html`<ha-svg-icon
|
||||
.path=${DOMAIN_ICONS[domain][deviceClass]}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
`
|
||||
: ""
|
||||
);
|
||||
return this._deviceClasses[domain].map((deviceClass) => {
|
||||
const entity = this._isOn(domain, deviceClass);
|
||||
return entity
|
||||
? html`<ha-svg-icon
|
||||
class="alert"
|
||||
.path=${DOMAIN_ICONS[domain][deviceClass] ||
|
||||
binarySensorIcon(entity.state, entity)}
|
||||
></ha-svg-icon>`
|
||||
: nothing;
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
<div class="bottom">
|
||||
@ -562,6 +574,7 @@ export class HuiAreaCard
|
||||
background: var(--accent-color);
|
||||
color: var(--text-accent-color, var(--text-primary-color));
|
||||
padding: 8px;
|
||||
margin-right: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,29 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import {
|
||||
assert,
|
||||
array,
|
||||
assign,
|
||||
boolean,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { DEFAULT_ASPECT_RATIO } from "../../cards/hui-area-card";
|
||||
import {
|
||||
DEFAULT_ASPECT_RATIO,
|
||||
DEVICE_CLASSES,
|
||||
} from "../../cards/hui-area-card";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { AreaCardConfig } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
|
||||
import { SelectOption } from "../../../../data/selector";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@ -20,8 +34,10 @@ const cardConfigStruct = assign(
|
||||
show_camera: optional(boolean()),
|
||||
camera_view: optional(string()),
|
||||
aspect_ratio: optional(string()),
|
||||
alert_classes: optional(array(string())),
|
||||
})
|
||||
);
|
||||
|
||||
@customElement("hui-area-card-editor")
|
||||
export class HuiAreaCardEditor
|
||||
extends LitElement
|
||||
@ -32,7 +48,7 @@ export class HuiAreaCardEditor
|
||||
@state() private _config?: AreaCardConfig;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(showCamera: boolean) =>
|
||||
(showCamera: boolean, binaryClasses: SelectOption[]) =>
|
||||
[
|
||||
{ name: "area", selector: { area: {} } },
|
||||
{ name: "show_camera", required: false, selector: { boolean: {} } },
|
||||
@ -61,9 +77,60 @@ export class HuiAreaCardEditor
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "alert_classes",
|
||||
selector: {
|
||||
select: {
|
||||
reorder: true,
|
||||
multiple: true,
|
||||
custom_value: true,
|
||||
options: binaryClasses,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
private _binaryClassesForArea = memoizeOne((area: string): string[] => {
|
||||
const entities = Object.values(this.hass!.entities).filter(
|
||||
(e) =>
|
||||
computeDomain(e.entity_id) === "binary_sensor" &&
|
||||
!e.entity_category &&
|
||||
!e.hidden &&
|
||||
(e.area_id === area ||
|
||||
(e.device_id && this.hass!.devices[e.device_id].area_id === area))
|
||||
);
|
||||
|
||||
const classes = entities
|
||||
.map((e) => this.hass!.states[e.entity_id]?.attributes.device_class || "")
|
||||
.filter((c) => c);
|
||||
|
||||
return [...new Set(classes)];
|
||||
});
|
||||
|
||||
private _buildOptions = memoizeOne(
|
||||
(possibleClasses: string[], currentClasses: string[]): SelectOption[] => {
|
||||
const options = [...new Set([...possibleClasses, ...currentClasses])].map(
|
||||
(deviceClass) => ({
|
||||
value: deviceClass,
|
||||
label:
|
||||
this.hass!.localize(
|
||||
`component.binary_sensor.entity_component.${deviceClass}.name`
|
||||
) || deviceClass,
|
||||
})
|
||||
);
|
||||
options.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.label,
|
||||
b.label,
|
||||
this.hass!.locale.language
|
||||
)
|
||||
);
|
||||
|
||||
return options;
|
||||
}
|
||||
);
|
||||
|
||||
public setConfig(config: AreaCardConfig): void {
|
||||
assert(config, cardConfigStruct);
|
||||
this._config = config;
|
||||
@ -74,10 +141,20 @@ export class HuiAreaCardEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema(this._config.show_camera || false);
|
||||
const possibleClasses = this._binaryClassesForArea(this._config.area || "");
|
||||
const selectOptions = this._buildOptions(
|
||||
possibleClasses,
|
||||
this._config.alert_classes || DEVICE_CLASSES.binary_sensor
|
||||
);
|
||||
|
||||
const schema = this._schema(
|
||||
this._config.show_camera || false,
|
||||
selectOptions
|
||||
);
|
||||
|
||||
const data = {
|
||||
camera_view: "auto",
|
||||
alert_classes: DEVICE_CLASSES.binary_sensor,
|
||||
...this._config,
|
||||
};
|
||||
|
||||
@ -124,6 +201,10 @@ export class HuiAreaCardEditor
|
||||
return this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.camera_view"
|
||||
);
|
||||
case "alert_classes":
|
||||
return this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.area.alert_classes"
|
||||
);
|
||||
}
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.area.${schema.name}`
|
||||
|
@ -5132,6 +5132,7 @@
|
||||
},
|
||||
"area": {
|
||||
"name": "Area",
|
||||
"alert_classes": "Alert Classes",
|
||||
"description": "The Area card automatically displays entities of a specific area.",
|
||||
"show_camera": "Show camera feed instead of area picture"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user