mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-25 17:51:39 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3a43d0d1e | |||
| 179b4cf77c | |||
| 542f07606a | |||
| cf2c440e7b |
+9
-2
@@ -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
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user