mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 23:36:36 +00:00
Add event entity (#17332)
This commit is contained in:
parent
3189ef0701
commit
0eebc9095c
@ -135,6 +135,14 @@ const ENTITIES = [
|
||||
getEntity("text", "unavailable", "unavailable", {
|
||||
friendly_name: "Message",
|
||||
}),
|
||||
getEntity("event", "unavailable", "unavailable", {
|
||||
friendly_name: "Empty remote",
|
||||
}),
|
||||
getEntity("event", "doorbell", "2023-07-17T21:26:11.615+00:00", {
|
||||
friendly_name: "Doorbell",
|
||||
device_class: "doorbell",
|
||||
event_type: "Ding-Dong",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@ -154,6 +162,7 @@ const CONFIGS = [
|
||||
- input_number.number
|
||||
- sensor.humidity
|
||||
- text.message
|
||||
- event.doorbell
|
||||
`,
|
||||
},
|
||||
{
|
||||
@ -246,6 +255,7 @@ const CONFIGS = [
|
||||
- input_number.unavailable
|
||||
- input_select.unavailable
|
||||
- text.unavailable
|
||||
- event.unavailable
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
mdiBookmark,
|
||||
mdiBrightness5,
|
||||
mdiBullhorn,
|
||||
mdiButtonPointer,
|
||||
mdiCalendar,
|
||||
mdiCalendarClock,
|
||||
mdiCarCoolantLevel,
|
||||
@ -28,7 +29,6 @@ import {
|
||||
mdiFormatListBulleted,
|
||||
mdiFormTextbox,
|
||||
mdiGauge,
|
||||
mdiGestureTapButton,
|
||||
mdiGoogleAssistant,
|
||||
mdiGoogleCirclesCommunities,
|
||||
mdiHomeAssistant,
|
||||
@ -93,7 +93,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
homekit: mdiHomeAutomation,
|
||||
image: mdiImage,
|
||||
image_processing: mdiImageFilterFrames,
|
||||
input_button: mdiGestureTapButton,
|
||||
input_button: mdiButtonPointer,
|
||||
input_datetime: mdiCalendarClock,
|
||||
input_number: mdiRayVertex,
|
||||
input_select: mdiFormatListBulleted,
|
||||
@ -178,6 +178,7 @@ export const DOMAINS_WITH_CARD = [
|
||||
"climate",
|
||||
"cover",
|
||||
"configurator",
|
||||
"event",
|
||||
"input_button",
|
||||
"input_select",
|
||||
"input_number",
|
||||
|
@ -185,9 +185,15 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
|
||||
// state is a timestamp
|
||||
if (
|
||||
["button", "image", "input_button", "scene", "stt", "tts"].includes(
|
||||
domain
|
||||
) ||
|
||||
[
|
||||
"button",
|
||||
"event",
|
||||
"image",
|
||||
"input_button",
|
||||
"scene",
|
||||
"stt",
|
||||
"tts",
|
||||
].includes(domain) ||
|
||||
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||
) {
|
||||
try {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
mdiAudioVideoOff,
|
||||
mdiBluetooth,
|
||||
mdiBluetoothConnect,
|
||||
mdiButtonPointer,
|
||||
mdiCalendar,
|
||||
mdiCast,
|
||||
mdiCastConnected,
|
||||
@ -16,8 +17,10 @@ import {
|
||||
mdiClock,
|
||||
mdiCloseCircleOutline,
|
||||
mdiCrosshairsQuestion,
|
||||
mdiDoorbell,
|
||||
mdiFan,
|
||||
mdiFanOff,
|
||||
mdiGestureTap,
|
||||
mdiGestureTapButton,
|
||||
mdiLanConnect,
|
||||
mdiLanDisconnect,
|
||||
@ -25,6 +28,7 @@ import {
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
mdiLockOpen,
|
||||
mdiMotionSensor,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
mdiPackageUp,
|
||||
@ -111,7 +115,7 @@ export const domainIconWithoutDefault = (
|
||||
case "update":
|
||||
return mdiPackageUp;
|
||||
default:
|
||||
return mdiGestureTapButton;
|
||||
return mdiButtonPointer;
|
||||
}
|
||||
|
||||
case "camera":
|
||||
@ -131,6 +135,18 @@ export const domainIconWithoutDefault = (
|
||||
}
|
||||
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
|
||||
|
||||
case "event":
|
||||
switch (stateObj?.attributes.device_class) {
|
||||
case "doorbell":
|
||||
return mdiDoorbell;
|
||||
case "button":
|
||||
return mdiGestureTapButton;
|
||||
case "motion":
|
||||
return mdiMotionSensor;
|
||||
default:
|
||||
return mdiGestureTap;
|
||||
}
|
||||
|
||||
case "fan":
|
||||
return compareState === "off" ? mdiFanOff : mdiFan;
|
||||
|
||||
|
@ -250,6 +250,11 @@ export const getStates = (
|
||||
result.push("home", "not_home");
|
||||
}
|
||||
break;
|
||||
case "event":
|
||||
if (attribute === "event_type") {
|
||||
result.push(...state.attributes.event_types);
|
||||
}
|
||||
break;
|
||||
case "fan":
|
||||
if (attribute === "preset_mode") {
|
||||
result.push(...state.attributes.preset_modes);
|
||||
|
@ -6,7 +6,7 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
|
||||
if (["button", "input_button", "scene"].includes(domain)) {
|
||||
if (["button", "event", "input_button", "scene"].includes(domain)) {
|
||||
return compareState !== UNAVAILABLE;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ export const STATE_ATTRIBUTES = [
|
||||
"emulated_hue_name",
|
||||
"emulated_hue",
|
||||
"entity_picture",
|
||||
"event_types",
|
||||
"friendly_name",
|
||||
"haaska_hidden",
|
||||
"haaska_name",
|
||||
|
@ -12,6 +12,7 @@ import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { HaEntityPickerEntityFilterFunc } from "../components/entity/ha-entity-picker";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE, UNKNOWN } from "./entity";
|
||||
import { computeAttributeValueDisplay } from "../common/entity/compute_attribute_display";
|
||||
|
||||
const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
|
||||
export const CONTINUOUS_DOMAINS = ["counter", "proximity", "sensor", "zone"];
|
||||
@ -156,6 +157,7 @@ export const createHistoricState = (
|
||||
attributes: {
|
||||
// Rebuild the historical state by copying static attributes only
|
||||
device_class: currentStateObj?.attributes.device_class,
|
||||
event_type: currentStateObj?.attributes.event_type,
|
||||
source_type: currentStateObj?.attributes.source_type,
|
||||
has_date: currentStateObj?.attributes.has_date,
|
||||
has_time: currentStateObj?.attributes.has_time,
|
||||
@ -343,6 +345,23 @@ export const localizeStateMessage = (
|
||||
}
|
||||
break;
|
||||
|
||||
case "event": {
|
||||
const event_type =
|
||||
computeAttributeValueDisplay(
|
||||
hass!.localize,
|
||||
stateObj,
|
||||
hass.locale,
|
||||
hass.config,
|
||||
hass.entities,
|
||||
"event_type"
|
||||
)?.toString() ||
|
||||
localize(`${LOGBOOK_LOCALIZE_PATH}.detected_unknown_event`);
|
||||
|
||||
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_event`, {
|
||||
event_type: autoCaseNoun(event_type, hass.language),
|
||||
});
|
||||
}
|
||||
|
||||
case "lock":
|
||||
switch (state) {
|
||||
case "unlocked":
|
||||
|
@ -10,6 +10,7 @@ export const SCENE_IGNORED_DOMAINS = [
|
||||
"button",
|
||||
"configuration",
|
||||
"device_tracker",
|
||||
"event",
|
||||
"image_processing",
|
||||
"input_button",
|
||||
"persistent_notification",
|
||||
|
@ -72,6 +72,7 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
|
||||
"select",
|
||||
"text",
|
||||
"update",
|
||||
"event",
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
|
@ -52,6 +52,8 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
"effect_list",
|
||||
"effect",
|
||||
"entity_picture",
|
||||
"event_type",
|
||||
"event_types",
|
||||
"fan_mode",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
|
@ -42,6 +42,7 @@ const SCHEMA = [
|
||||
"editable",
|
||||
"effect_list",
|
||||
"entity_picture",
|
||||
"event_types",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
"forecast",
|
||||
|
@ -51,6 +51,8 @@ export class HaNumericStateTrigger extends LitElement {
|
||||
"effect",
|
||||
"entity_id",
|
||||
"entity_picture",
|
||||
"event_type",
|
||||
"event_types",
|
||||
"fan_mode",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
|
@ -76,6 +76,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
"effect_list",
|
||||
"entity_id",
|
||||
"entity_picture",
|
||||
"event_types",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
"friendly_name",
|
||||
|
@ -207,16 +207,25 @@ export class HaConfigDevicePage extends LitElement {
|
||||
const result = groupBy(entities, (entry) =>
|
||||
entry.entity_category
|
||||
? entry.entity_category
|
||||
: computeDomain(entry.entity_id) === "event"
|
||||
? "event"
|
||||
: SENSOR_ENTITIES.includes(computeDomain(entry.entity_id))
|
||||
? "sensor"
|
||||
: "control"
|
||||
) as Record<
|
||||
| "control"
|
||||
| "event"
|
||||
| "sensor"
|
||||
| NonNullable<EntityRegistryEntry["entity_category"]>,
|
||||
EntityRegistryStateEntry[]
|
||||
>;
|
||||
for (const key of ["control", "sensor", "diagnostic", "config"]) {
|
||||
for (const key of [
|
||||
"config",
|
||||
"control",
|
||||
"diagnostic",
|
||||
"event",
|
||||
"sensor",
|
||||
]) {
|
||||
if (!(key in result)) {
|
||||
result[key] = [];
|
||||
}
|
||||
@ -877,8 +886,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
${!this.narrow ? [automationCard, sceneCard, scriptCard] : ""}
|
||||
</div>
|
||||
<div class="column">
|
||||
${(["control", "sensor", "config", "diagnostic"] as const).map(
|
||||
(category) =>
|
||||
${(
|
||||
["control", "sensor", "event", "config", "diagnostic"] as const
|
||||
).map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "../entity-rows/hui-event-entity-row";
|
||||
import "../entity-rows/hui-media-player-entity-row";
|
||||
import "../entity-rows/hui-scene-entity-row";
|
||||
import "../entity-rows/hui-script-entity-row";
|
||||
@ -29,6 +30,7 @@ const LAZY_LOAD_TYPES = {
|
||||
"cover-entity": () => import("../entity-rows/hui-cover-entity-row"),
|
||||
"date-entity": () => import("../entity-rows/hui-date-entity-row"),
|
||||
"datetime-entity": () => import("../entity-rows/hui-datetime-entity-row"),
|
||||
"event-entity": () => import("../entity-rows/hui-event-entity-row"),
|
||||
"group-entity": () => import("../entity-rows/hui-group-entity-row"),
|
||||
"input-button-entity": () =>
|
||||
import("../entity-rows/hui-input-button-entity-row"),
|
||||
@ -65,6 +67,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
cover: "cover",
|
||||
date: "date",
|
||||
datetime: "datetime",
|
||||
event: "event",
|
||||
fan: "toggle",
|
||||
group: "group",
|
||||
humidifier: "humidifier",
|
||||
|
129
src/panels/lovelace/entity-rows/hui-event-entity-row.ts
Normal file
129
src/panels/lovelace/entity-rows/hui-event-entity-row.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EntitiesCardEntityConfig } from "../cards/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
import "../components/hui-timestamp-display";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { TimestampRenderingFormat } from "../components/types";
|
||||
import { LovelaceRow } from "./types";
|
||||
|
||||
interface EventEntityConfig extends EntitiesCardEntityConfig {
|
||||
format?: TimestampRenderingFormat;
|
||||
}
|
||||
|
||||
@customElement("hui-event-entity-row")
|
||||
class HuiEventEntityRow extends LitElement implements LovelaceRow {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EventEntityConfig;
|
||||
|
||||
public setConfig(config: EventEntityConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning>
|
||||
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
||||
</hui-warning>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
<div
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config.hold_action),
|
||||
hasDoubleClick: hasAction(this._config.double_tap_action),
|
||||
})}
|
||||
>
|
||||
<div class="what">
|
||||
${isUnavailableState(stateObj.state)
|
||||
? computeStateDisplay(
|
||||
this.hass!.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities
|
||||
)
|
||||
: computeAttributeValueDisplay(
|
||||
this.hass!.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities,
|
||||
"event_type"
|
||||
)}
|
||||
</div>
|
||||
<div class="when">
|
||||
${isUnavailableState(stateObj.state)
|
||||
? ``
|
||||
: html`
|
||||
<hui-timestamp-display
|
||||
.hass=${this.hass}
|
||||
.ts=${new Date(stateObj.state)}
|
||||
.format=${this._config.format}
|
||||
capitalize
|
||||
></hui-timestamp-display>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
handleAction(this, this.hass!, this._config!, ev.detail.action);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
div {
|
||||
text-align: right;
|
||||
}
|
||||
.when {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.what {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-event-entity-row": HuiEventEntityRow;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import "./state-card-humidifier";
|
||||
import "./state-card-configurator";
|
||||
import "./state-card-cover";
|
||||
import "./state-card-display";
|
||||
import "./state-card-event";
|
||||
import "./state-card-input_button";
|
||||
import "./state-card-input_number";
|
||||
import "./state-card-input_select";
|
||||
|
59
src/state-summary/state-card-event.ts
Normal file
59
src/state-summary/state-card-event.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/entity/ha-entity-toggle";
|
||||
import "../components/entity/state-info";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { isUnavailableState } from "../data/entity";
|
||||
import { computeAttributeValueDisplay } from "../common/entity/compute_attribute_display";
|
||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||
import { haStyle } from "../resources/styles";
|
||||
|
||||
@customElement("state-card-event")
|
||||
export class StateCardEvent extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj!: HassEntity;
|
||||
|
||||
@property({ type: Boolean }) public inDialog = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="horizontal justified layout">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
.inDialog=${this.inDialog}
|
||||
></state-info>
|
||||
<div class="event_type">
|
||||
${isUnavailableState(this.stateObj.state)
|
||||
? computeStateDisplay(
|
||||
this.hass!.localize,
|
||||
this.stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities
|
||||
)
|
||||
: computeAttributeValueDisplay(
|
||||
this.hass!.localize,
|
||||
this.stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities,
|
||||
"event_type"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return haStyle;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"state-card-event": StateCardEvent;
|
||||
}
|
||||
}
|
@ -357,7 +357,9 @@
|
||||
"became_unavailable": "became unavailable",
|
||||
"became_unknown": "became unknown",
|
||||
"detected_tampering": "detected tampering",
|
||||
"cleared_tampering": "cleared tampering"
|
||||
"cleared_tampering": "cleared tampering",
|
||||
"detected_event": "{event_type} event detected",
|
||||
"detected_unknown_event": "detected an unknown event"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
@ -2322,7 +2324,7 @@
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
"label": "Event",
|
||||
"label": "Manual event",
|
||||
"event_type": "Event type",
|
||||
"event_data": "Event data",
|
||||
"context_users": "Limit to events triggered by",
|
||||
@ -2640,11 +2642,11 @@
|
||||
"label": "Condition"
|
||||
},
|
||||
"event": {
|
||||
"label": "Event",
|
||||
"event": "[%key:ui::panel::config::automation::editor::triggers::type::event::label%]",
|
||||
"label": "Manual event",
|
||||
"event": "[%key:ui::panel::config::automation::editor::triggers::type::event::event_type%]",
|
||||
"event_data": "[%key:ui::panel::config::automation::editor::triggers::type::event::event_data%]",
|
||||
"description": {
|
||||
"full": "Fire event {name}",
|
||||
"full": "Manually fire event {name}",
|
||||
"template": "based on a template"
|
||||
}
|
||||
},
|
||||
@ -3212,6 +3214,7 @@
|
||||
"entities": {
|
||||
"entities": "Entities",
|
||||
"control": "Controls",
|
||||
"event": "Events",
|
||||
"sensor": "Sensors",
|
||||
"diagnostic": "Diagnostic",
|
||||
"config": "Configuration",
|
||||
|
Loading…
x
Reference in New Issue
Block a user