Compare commits

..

4 Commits

Author SHA1 Message Date
Paul Bottein a3a43d0d1e Show the event type in the logbook for event entities 2026-06-25 15:00:35 +02:00
Paul Bottein 179b4cf77c Show dash for unavailable number entity in slider row (#52866) 2026-06-25 14:17:54 +02:00
Paul Bottein 542f07606a Fix logbook padding and margin (#52864) 2026-06-25 14:17:24 +02:00
Paul Bottein cf2c440e7b Show action labels instead of timestamps in the logbook (#52861) 2026-06-25 14:16:01 +02:00
14 changed files with 129 additions and 52 deletions
-1
View File
@@ -240,7 +240,6 @@ gulp.task("rspack-dev-server-e2e-test-app", () =>
),
contentBase: paths.e2eTestApp_output_root,
port: 8095,
open: false,
})
);
-1
View File
@@ -28,7 +28,6 @@
"test:e2e:show-report": "yarn playwright show-report test/e2e/reports/combined",
"test:e2e:demo": "playwright test --config test/e2e/playwright.demo.config.ts",
"test:e2e:app": "playwright test --config test/e2e/playwright.app.config.ts",
"test:e2e:app:dev": "test/e2e/app/script/develop_app",
"test:e2e:gallery": "playwright test --config test/e2e/playwright.gallery.config.ts"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
+9 -2
View File
@@ -111,7 +111,7 @@ export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set([
]);
/** Domains that use a timestamp for state. */
export const TIMESTAMP_STATE_DOMAINS = new Set([
const TIMESTAMP_STATE_DOMAINS_LIST = [
"ai_task",
"button",
"conversation",
@@ -127,7 +127,14 @@ export const TIMESTAMP_STATE_DOMAINS = new Set([
"tts",
"wake_word",
"datetime",
]);
] as const;
export type TimestampStateDomain =
(typeof TIMESTAMP_STATE_DOMAINS_LIST)[number];
export const TIMESTAMP_STATE_DOMAINS = new Set<string>(
TIMESTAMP_STATE_DOMAINS_LIST
);
/** Temperature units. */
export const UNIT_C = "°C";
+51 -3
View File
@@ -1,5 +1,6 @@
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { DOMAINS_WITH_DYNAMIC_PICTURE } from "../common/const";
import type { TimestampStateDomain } from "../common/const";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import type { LocalizeFunc } from "../common/translations/localize";
@@ -26,6 +27,7 @@ export interface LogbookEntry {
source?: string; // The trigger source (English phrase, parsed for the cause)
domain?: string;
state?: string; // The state of the entity
attributes?: { event_type?: string }; // Selected attributes the backend surfaces
// Context data
context_id?: string;
context_user_id?: string;
@@ -239,17 +241,63 @@ export const parseTriggerSource = (source: string): ParsedTriggerSource => {
return {};
};
// Short label shown instead of the bare timestamp for each timestamp-state
// domain. Typed to TIMESTAMP_STATE_DOMAINS minus datetime (a real value) and
// event (handled separately via its event type), so a new timestamp domain
// won't compile until it gets a label here.
type LogbookActionMessage =
| "pressed"
| "activated"
| "scanned"
| "updated"
| "sent"
| "detected"
| "transcribed"
| "spoke"
| "responded"
| "ran"
| "command_sent";
const STATE_ACTION_MESSAGES: Record<
Exclude<TimestampStateDomain, "datetime" | "event">,
LogbookActionMessage
> = {
button: "pressed",
input_button: "pressed",
scene: "activated",
tag: "scanned",
image: "updated",
notify: "sent",
wake_word: "detected",
stt: "transcribed",
tts: "spoke",
conversation: "responded",
ai_task: "ran",
infrared: "command_sent",
radio_frequency: "command_sent",
};
export const localizeStateMessage = (
hass: HomeAssistant,
state: string,
stateObj: HassEntity,
domain: string
domain: string,
attributes?: LogbookEntry["attributes"]
): string => {
// Events expose a timestamp as their state, which has no meaningful display
// value, so keep a dedicated phrase.
// Events show the triggered event type, falling back to a generic label when
// the type is unknown (the timestamp state is meaningless on its own).
if (domain === "event") {
const eventType = attributes?.event_type;
if (eventType != null) {
return hass.formatEntityAttributeValue(stateObj, "event_type", eventType);
}
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.detected_event_no_type`);
}
const actionKey: LogbookActionMessage | undefined =
STATE_ACTION_MESSAGES[domain as keyof typeof STATE_ACTION_MESSAGES];
if (actionKey) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.${actionKey}`);
}
// Every other domain reuses the backend state translation, so the logbook
// speaks the same vocabulary as the rest of the UI.
return hass.formatEntityState(stateObj, state);
+1 -2
View File
@@ -1044,8 +1044,7 @@ export class MoreInfoDialog extends DirtyStateProviderMixin<
}
ha-more-info-history-and-logbook {
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-6)
var(--ha-space-6);
padding: var(--ha-space-2) 0 var(--ha-space-6) 0;
display: block;
}
@@ -278,6 +278,7 @@ export class MoreInfoHistory extends LitElement {
justify-content: space-between;
align-items: center;
margin-bottom: var(--ha-space-2);
padding-inline: var(--ha-space-6);
}
.header > a,
a:visited {
@@ -290,6 +291,12 @@ export class MoreInfoHistory extends LitElement {
h2 {
margin: 0;
}
ha-alert,
state-history-charts,
statistics-chart {
display: block;
padding-inline: var(--ha-space-6);
}
`,
];
}
@@ -70,6 +70,7 @@ export class MoreInfoLogbook extends LitElement {
css`
ha-logbook {
--logbook-max-height: 250px;
--logbook-horizontal-padding: var(--ha-space-6);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-logbook {
@@ -82,6 +83,7 @@ export class MoreInfoLogbook extends LitElement {
justify-content: space-between;
align-items: center;
margin-bottom: var(--ha-space-2);
padding-inline: var(--ha-space-6);
}
.header > a,
a:visited {
+12 -1
View File
@@ -8,6 +8,7 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { computeTimelineColor } from "../../components/chart/timeline-color";
import { computeDomain } from "../../common/entity/compute_domain";
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
import { useAmPm } from "../../common/datetime/use_am_pm";
import { fireEvent } from "../../common/dom/fire_event";
import { navigate } from "../../common/navigate";
import { computeRTL } from "../../common/util/compute_rtl";
@@ -126,6 +127,7 @@ class HaLogbookEntry extends LitElement {
[`node-${node}`]: true,
"last-of-day": this.lastOfDay,
[`category-${ctx.category}`]: true,
"time-am-pm": useAmPm(this.hass.locale),
})}"
>
${layout === "timeline"
@@ -591,7 +593,7 @@ class HaLogbookEntry extends LitElement {
width: 100%;
box-sizing: border-box;
/* No vertical padding: the rail must reach the row edges to stay continuous between nodes. */
padding: 0 var(--ha-space-4);
padding: 0 var(--logbook-horizontal-padding, var(--ha-space-4));
grid-auto-rows: minmax(60px, auto);
line-height: var(--ha-line-height-normal);
align-items: stretch;
@@ -913,6 +915,7 @@ class HaLogbookEntry extends LitElement {
.time-chip {
flex-shrink: 0;
text-align: end;
line-height: 1;
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
@@ -921,6 +924,14 @@ class HaLogbookEntry extends LitElement {
user-select: none;
}
.time-chip {
min-width: 4.5em;
}
.entry.time-am-pm .time-chip {
min-width: 6em;
}
.time-chip:hover {
opacity: 0.75;
}
+2 -1
View File
@@ -197,7 +197,8 @@ class HaLogbookRenderer extends LitElement {
.date {
margin: var(--ha-space-2) 0 0;
padding: var(--ha-space-2) var(--ha-space-4) 0;
padding: var(--ha-space-2)
var(--logbook-horizontal-padding, var(--ha-space-4)) 0;
font-weight: var(--ha-font-weight-medium);
}
+7 -1
View File
@@ -266,7 +266,13 @@ const computeLogbookValue = (
if (item.entity_id && item.state) {
return {
text: stateObj
? localizeStateMessage(hass, item.state, stateObj, domain!)
? localizeStateMessage(
hass,
item.state,
stateObj,
domain!,
item.attributes
)
: item.state,
type: "state",
};
+22 -28
View File
@@ -1,4 +1,3 @@
import { mdiChevronRight } from "@mdi/js";
import { startOfYesterday } from "date-fns";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
@@ -10,10 +9,10 @@ import { ensureArray } from "../../../common/array/ensure-array";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { getEntityEntryContext } from "../../../common/entity/context/get_entity_context";
import { navigate } from "../../../common/navigate";
import { createSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-tooltip";
import { resolveEntityIDs } from "../../../data/selector";
import type { HomeAssistant } from "../../../types";
import "../../logbook/ha-logbook";
@@ -73,6 +72,8 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
@state() private _stateFilter?: string[];
private _showMoreLinkId = `logbook-${Math.random().toString(36).substring(2, 9)}`;
public getCardSize(): number {
return 9 + (this._config?.title ? 1 : 0);
}
@@ -142,10 +143,6 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
this._stateFilter = ensureArray(config.state_filter);
}
private _showMore() {
navigate(this._showMoreUrl());
}
private _showMoreUrl(): string {
const target = this._targetPickerValue;
const params: Record<string, string> = {
@@ -291,16 +288,21 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
return html`
<ha-card class=${classMap({ "no-header": !this._config!.title })}>
${this._config!.title
? html`<div class="card-header">
<h1 class="name">${this._config!.title}</h1>
<ha-icon-button
.path=${mdiChevronRight}
.label=${this.hass.localize(
? html`<h1 class="card-header">
${this._config!.title}
<a
id=${this._showMoreLinkId}
href=${this._showMoreUrl()}
aria-label=${this.hass.localize(
"ui.dialogs.more_info_control.show_more"
)}
@click=${this._showMore}
></ha-icon-button>
</div>`
>
<ha-icon-next></ha-icon-next>
</a>
<ha-tooltip for=${this._showMoreLinkId} placement="left">
${this.hass.localize("ui.dialogs.more_info_control.show_more")}
</ha-tooltip>
</h1>`
: nothing}
<div class="content">
<ha-logbook
@@ -336,26 +338,18 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 16px 0;
padding-bottom: 0;
}
.card-header .name {
margin: 0;
font-size: var(--ha-card-header-font-size, 1.4rem);
font-weight: var(--ha-card-header-font-weight, 500);
color: var(--ha-card-header-color, var(--primary-text-color));
}
.card-header a {
.card-header ha-icon-next {
--ha-icon-button-size: 24px;
line-height: 24px;
color: var(--primary-text-color);
margin-right: calc(var(--ha-space-2) * -1);
margin-inline-end: calc(var(--ha-space-2) * -1);
margin-inline-start: initial;
}
.content {
height: 100%;
padding: 0 16px 16px;
padding: 0 0 16px;
}
.no-header .content {
@@ -5,7 +5,7 @@ import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-slider";
import "../../../components/input/ha-input";
import type { HaInput } from "../../../components/input/ha-input";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import { setValue } from "../../../data/input_text";
import type { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@@ -91,7 +91,9 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
@change=${this._selectedValueChanged}
></ha-slider>
<span class="state">
${this.hass.formatEntityState(stateObj)}
${stateObj.state === UNAVAILABLE || stateObj.state === UNKNOWN
? "—"
: this.hass.formatEntityState(stateObj)}
</span>
</div>
`
+12 -1
View File
@@ -715,7 +715,18 @@
"retrieval_error": "Could not load activity",
"not_loaded": "[%key:ui::dialogs::helper_settings::platform_not_loaded%]",
"messages": {
"detected_event_no_type": "detected an event"
"detected_event_no_type": "Event detected",
"pressed": "Pressed",
"activated": "Activated",
"scanned": "Scanned",
"updated": "Updated",
"sent": "Sent",
"detected": "Detected",
"transcribed": "Transcribed",
"spoke": "Spoke",
"responded": "Responded",
"ran": "Ran",
"command_sent": "Command sent"
}
},
"entity": {
-9
View File
@@ -1,9 +0,0 @@
#!/bin/sh
# Develop the e2e test app
# Stop on errors
set -e
cd "$(dirname "$0")/../../../.."
./node_modules/.bin/gulp develop-e2e-test-app