Logbook: Localize Message & Style updates (#6978)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Zack Barett 2020-09-30 03:24:04 -05:00 committed by GitHub
parent 26162815c8
commit e2427c8dce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 567 additions and 290 deletions

View File

@ -7,6 +7,66 @@
/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
/** Icons for each domain */
export const FIXED_DOMAIN_ICONS = {
alert: "hass:alert",
alexa: "hass:amazon-alexa",
air_quality: "hass:air-filter",
automation: "hass:robot",
calendar: "hass:calendar",
camera: "hass:video",
climate: "hass:thermostat",
configurator: "hass:cog",
conversation: "hass:text-to-speech",
counter: "hass:counter",
device_tracker: "hass:account",
fan: "hass:fan",
google_assistant: "hass:google-assistant",
group: "hass:google-circles-communities",
homeassistant: "hass:home-assistant",
homekit: "hass:home-automation",
image_processing: "hass:image-filter-frames",
input_boolean: "hass:toggle-switch-outline",
input_datetime: "hass:calendar-clock",
input_number: "hass:ray-vertex",
input_select: "hass:format-list-bulleted",
input_text: "hass:form-textbox",
light: "hass:lightbulb",
mailbox: "hass:mailbox",
notify: "hass:comment-alert",
persistent_notification: "hass:bell",
person: "hass:account",
plant: "hass:flower",
proximity: "hass:apple-safari",
remote: "hass:remote",
scene: "hass:palette",
script: "hass:script-text",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
switch: "hass:flash",
timer: "hass:timer-outline",
updater: "hass:cloud-upload",
vacuum: "hass:robot-vacuum",
water_heater: "hass:thermometer",
weather: "hass:weather-cloudy",
zone: "hass:map-marker-radius",
};
export const FIXED_DEVICE_CLASS_ICONS = {
current: "hass:current-ac",
energy: "hass:flash",
humidity: "hass:water-percent",
illuminance: "hass:brightness-5",
temperature: "hass:thermometer",
pressure: "hass:gauge",
power: "hass:flash",
power_factor: "hass:angle-acute",
signal_strength: "hass:wifi",
timestamp: "hass:clock",
voltage: "hass:sine-wave",
};
/** Domains that have a state card. */
export const DOMAINS_WITH_CARD = [
"climate",
@ -63,6 +123,10 @@ export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"];
/** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"];
/** Binary States */
export const BINARY_STATE_ON = "on";
export const BINARY_STATE_OFF = "off";
/** Domains where we allow toggle in Lovelace. */
export const DOMAINS_TOGGLE = new Set([
"fan",

View File

@ -2,9 +2,9 @@ import { HassEntity } from "home-assistant-js-websocket";
/** Return an icon representing a binary sensor state. */
export const binarySensorIcon = (state: HassEntity) => {
const is_off = state.state && state.state === "off";
switch (state.attributes.device_class) {
export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
const is_off = state === "off";
switch (stateObj?.attributes.device_class) {
case "battery":
return is_off ? "hass:battery" : "hass:battery-outline";
case "battery_charging":

View File

@ -9,14 +9,17 @@ import { computeStateDomain } from "./compute_state_domain";
export const computeStateDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
language: string
language: string,
state?: string
): string => {
if (stateObj.state === UNKNOWN || stateObj.state === UNAVAILABLE) {
return localize(`state.default.${stateObj.state}`);
const compareState = state !== undefined ? state : stateObj.state;
if (compareState === UNKNOWN || compareState === UNAVAILABLE) {
return localize(`state.default.${compareState}`);
}
if (stateObj.attributes.unit_of_measurement) {
return `${stateObj.state} ${stateObj.attributes.unit_of_measurement}`;
return `${compareState} ${stateObj.attributes.unit_of_measurement}`;
}
const domain = computeStateDomain(stateObj);
@ -56,7 +59,7 @@ export const computeStateDisplay = (
}
if (domain === "humidifier") {
if (stateObj.state === "on" && stateObj.attributes.humidity) {
if (compareState === "on" && stateObj.attributes.humidity) {
return `${stateObj.attributes.humidity}%`;
}
}
@ -65,11 +68,11 @@ export const computeStateDisplay = (
// Return device class translation
(stateObj.attributes.device_class &&
localize(
`component.${domain}.state.${stateObj.attributes.device_class}.${stateObj.state}`
`component.${domain}.state.${stateObj.attributes.device_class}.${compareState}`
)) ||
// Return default translation
localize(`component.${domain}.state._.${stateObj.state}`) ||
localize(`component.${domain}.state._.${compareState}`) ||
// We don't know! Return the raw state.
stateObj.state
compareState
);
};

View File

@ -1,13 +1,12 @@
/** Return an icon representing a cover state. */
import { HassEntity } from "home-assistant-js-websocket";
import { domainIcon } from "./domain_icon";
export const coverIcon = (state: HassEntity): string => {
const open = state.state !== "closed";
export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
const open = state !== "closed";
switch (state.attributes.device_class) {
switch (stateObj?.attributes.device_class) {
case "garage":
switch (state.state) {
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
@ -18,7 +17,7 @@ export const coverIcon = (state: HassEntity): string => {
return "hass:garage-open";
}
case "gate":
switch (state.state) {
switch (state) {
case "opening":
case "closing":
return "hass:gate-arrow-right";
@ -32,7 +31,7 @@ export const coverIcon = (state: HassEntity): string => {
case "damper":
return open ? "hass:circle" : "hass:circle-slice-8";
case "shutter":
switch (state.state) {
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
@ -44,7 +43,7 @@ export const coverIcon = (state: HassEntity): string => {
}
case "blind":
case "curtain":
switch (state.state) {
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
@ -55,7 +54,7 @@ export const coverIcon = (state: HassEntity): string => {
return "hass:blinds-open";
}
case "window":
switch (state.state) {
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
@ -65,7 +64,16 @@ export const coverIcon = (state: HassEntity): string => {
default:
return "hass:window-open";
}
}
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
return "hass:arrow-down-box";
case "closed":
return "hass:window-closed";
default:
return domainIcon("cover", state.state);
return "hass:window-open";
}
};

View File

@ -1,63 +1,24 @@
import { HassEntity } from "home-assistant-js-websocket";
/**
* Return the icon to be used for a domain.
*
* Optionally pass in a state to influence the domain icon.
*/
import { DEFAULT_DOMAIN_ICON } from "../const";
import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
import { binarySensorIcon } from "./binary_sensor_icon";
import { coverIcon } from "./cover_icon";
import { sensorIcon } from "./sensor_icon";
const fixedIcons = {
alert: "hass:alert",
alexa: "hass:amazon-alexa",
air_quality: "hass:air-filter",
automation: "hass:robot",
calendar: "hass:calendar",
camera: "hass:video",
climate: "hass:thermostat",
configurator: "hass:cog",
conversation: "hass:text-to-speech",
counter: "hass:counter",
device_tracker: "hass:account",
fan: "hass:fan",
google_assistant: "hass:google-assistant",
group: "hass:google-circles-communities",
homeassistant: "hass:home-assistant",
homekit: "hass:home-automation",
image_processing: "hass:image-filter-frames",
input_boolean: "hass:toggle-switch-outline",
input_datetime: "hass:calendar-clock",
input_number: "hass:ray-vertex",
input_select: "hass:format-list-bulleted",
input_text: "hass:form-textbox",
light: "hass:lightbulb",
mailbox: "hass:mailbox",
notify: "hass:comment-alert",
persistent_notification: "hass:bell",
person: "hass:account",
plant: "hass:flower",
proximity: "hass:apple-safari",
remote: "hass:remote",
scene: "hass:palette",
script: "hass:script-text",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
switch: "hass:flash",
timer: "hass:timer-outline",
updater: "hass:cloud-upload",
vacuum: "hass:robot-vacuum",
water_heater: "hass:thermometer",
weather: "hass:weather-cloudy",
zone: "hass:map-marker-radius",
};
export const domainIcon = (domain: string, state?: string): string => {
if (domain in fixedIcons) {
return fixedIcons[domain];
}
export const domainIcon = (
domain: string,
stateObj?: HassEntity,
state?: string
): string => {
const compareState = state !== undefined ? state : stateObj?.state;
switch (domain) {
case "alarm_control_panel":
switch (state) {
switch (compareState) {
case "armed_home":
return "hass:bell-plus";
case "armed_night":
@ -71,21 +32,10 @@ export const domainIcon = (domain: string, state?: string): string => {
}
case "binary_sensor":
return state && state === "off"
? "hass:radiobox-blank"
: "hass:checkbox-marked-circle";
return binarySensorIcon(compareState, stateObj);
case "cover":
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
return "hass:arrow-down-box";
case "closed":
return "hass:window-closed";
default:
return "hass:window-open";
}
return coverIcon(compareState, stateObj);
case "humidifier":
return state && state === "off"
@ -93,13 +43,13 @@ export const domainIcon = (domain: string, state?: string): string => {
: "hass:air-humidifier";
case "lock":
return state && state === "unlocked" ? "hass:lock-open" : "hass:lock";
return compareState === "unlocked" ? "hass:lock-open" : "hass:lock";
case "media_player":
return state && state === "playing" ? "hass:cast-connected" : "hass:cast";
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";
case "zwave":
switch (state) {
switch (compareState) {
case "dead":
return "hass:emoticon-dead";
case "sleeping":
@ -110,11 +60,32 @@ export const domainIcon = (domain: string, state?: string): string => {
return "hass:z-wave";
}
default:
case "sensor": {
const icon = sensorIcon(stateObj);
if (icon) {
return icon;
}
break;
}
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return "hass:clock";
}
if (!stateObj.attributes.has_time) {
return "hass:calendar";
}
break;
}
if (domain in FIXED_DOMAIN_ICONS) {
return FIXED_DOMAIN_ICONS[domain];
}
// eslint-disable-next-line
console.warn(
"Unable to find icon for domain " + domain + " (" + state + ")"
"Unable to find icon for domain " + domain + " (" + stateObj + ")"
);
return DEFAULT_DOMAIN_ICON;
}
};

View File

@ -1,13 +0,0 @@
/** Return an icon representing an input datetime state. */
import { HassEntity } from "home-assistant-js-websocket";
import { domainIcon } from "./domain_icon";
export const inputDateTimeIcon = (state: HassEntity): string => {
if (!state.attributes.has_date) {
return "hass:clock";
}
if (!state.attributes.has_time) {
return "hass:calendar";
}
return domainIcon("input_datetime");
};

View File

@ -1,36 +1,23 @@
/** Return an icon representing a sensor state. */
import { HassEntity } from "home-assistant-js-websocket";
import { UNIT_C, UNIT_F } from "../const";
import { domainIcon } from "./domain_icon";
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
import { batteryIcon } from "./battery_icon";
const fixedDeviceClassIcons = {
current: "hass:current-ac",
energy: "hass:flash",
humidity: "hass:water-percent",
illuminance: "hass:brightness-5",
temperature: "hass:thermometer",
pressure: "hass:gauge",
power: "hass:flash",
power_factor: "hass:angle-acute",
signal_strength: "hass:wifi",
timestamp: "hass:clock",
voltage: "hass:sine-wave",
};
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
export const sensorIcon = (state: HassEntity) => {
const dclass = state.attributes.device_class;
if (dclass && dclass in fixedDeviceClassIcons) {
return fixedDeviceClassIcons[dclass];
if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
if (dclass === "battery") {
return batteryIcon(state);
return stateObj ? batteryIcon(stateObj) : "hass:battery";
}
const unit = state.attributes.unit_of_measurement;
const unit = stateObj?.attributes.unit_of_measurement;
if (unit === UNIT_C || unit === UNIT_F) {
return "hass:thermometer";
}
return domainIcon("sensor");
return undefined;
};

View File

@ -1,19 +1,8 @@
/** Return an icon representing a state. */
import { HassEntity } from "home-assistant-js-websocket";
import { DEFAULT_DOMAIN_ICON } from "../const";
import { binarySensorIcon } from "./binary_sensor_icon";
import { computeDomain } from "./compute_domain";
import { coverIcon } from "./cover_icon";
import { domainIcon } from "./domain_icon";
import { inputDateTimeIcon } from "./input_dateteime_icon";
import { sensorIcon } from "./sensor_icon";
const domainIcons = {
binary_sensor: binarySensorIcon,
cover: coverIcon,
sensor: sensorIcon,
input_datetime: inputDateTimeIcon,
};
export const stateIcon = (state: HassEntity) => {
if (!state) {
@ -23,10 +12,5 @@ export const stateIcon = (state: HassEntity) => {
return state.attributes.icon;
}
const domain = computeDomain(state.entity_id);
if (domain in domainIcons) {
return domainIcons[domain](state);
}
return domainIcon(domain, state.state);
return domainIcon(computeDomain(state.entity_id), state);
};

View File

@ -147,7 +147,7 @@ export class HaStateLabelBadge extends LitElement {
return "hass:alert-circle";
}
// state == 'disarmed'
return domainIcon(domain, state.state);
return domainIcon(domain, state);
case "binary_sensor":
case "device_tracker":
case "updater":

View File

@ -1,9 +1,16 @@
import { HassEntity } from "home-assistant-js-websocket";
import { BINARY_STATE_OFF, BINARY_STATE_ON } from "../common/const";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import { HomeAssistant } from "../types";
import { UNAVAILABLE_STATES } from "./entity";
const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
export interface LogbookEntry {
when: string;
name: string;
message: string;
message?: string;
entity_id?: string;
domain: string;
context_user_id?: string;
@ -13,13 +20,43 @@ export interface LogbookEntry {
context_entity_id?: string;
context_entity_id_name?: string;
context_name?: string;
state?: string;
}
const DATA_CACHE: {
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
} = {};
export const getLogbookData = (
export const getLogbookData = async (
hass: HomeAssistant,
startDate: string,
endDate: string,
entityId?: string,
entity_matches_only?: boolean
) => {
const logbookData = await getLogbookDataCache(
hass,
startDate,
endDate,
entityId,
entity_matches_only
);
for (const entry of logbookData) {
if (entry.state) {
entry.message = getLogbookMessage(
hass,
entry.state,
hass!.states[entry.entity_id!],
computeDomain(entry.entity_id!)
);
}
}
return logbookData;
};
export const getLogbookDataCache = async (
hass: HomeAssistant,
startDate: string,
endDate: string,
@ -43,9 +80,8 @@ export const getLogbookData = (
}
if (entityId !== ALL_ENTITIES && DATA_CACHE[cacheKey][ALL_ENTITIES]) {
return DATA_CACHE[cacheKey][ALL_ENTITIES].then((entities) =>
entities.filter((entity) => entity.entity_id === entityId)
);
const entities = await DATA_CACHE[cacheKey][ALL_ENTITIES];
return entities.filter((entity) => entity.entity_id === entityId);
}
DATA_CACHE[cacheKey][entityId] = getLogbookDataFromServer(
@ -72,6 +108,168 @@ const getLogbookDataFromServer = async (
return hass.callApi<LogbookEntry[]>("GET", url);
};
export const clearLogbookCache = (startDate, endDate) => {
export const clearLogbookCache = (startDate: string, endDate: string) => {
DATA_CACHE[`${startDate}${endDate}`] = {};
};
export const getLogbookMessage = (
hass: HomeAssistant,
state: string,
stateObj: HassEntity,
domain: string
): string => {
switch (domain) {
case "device_tracker":
case "person":
return state === "not_home"
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`)
: hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.was_at_state`,
"state",
state
);
case "sun":
return state === "above_horizon"
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.rose`)
: hass.localize(`${LOGBOOK_LOCALIZE_PATH}.set`);
case "binary_sensor": {
const isOn = state === BINARY_STATE_ON;
const isOff = state === BINARY_STATE_OFF;
const device_class = stateObj.attributes.device_class;
switch (device_class) {
case "battery":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_low`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_normal`);
}
break;
case "connectivity":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_connected`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_disconnected`);
}
break;
case "door":
case "garage_door":
case "opening":
case "window":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
}
break;
case "lock":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
}
break;
case "plug":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_plugged_in`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unplugged`);
}
break;
case "presence":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
}
break;
case "safety":
if (isOn) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unsafe`);
}
if (isOff) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_safe`);
}
break;
case "cold":
case "gas":
case "heat":
case "colightld":
case "moisture":
case "motion":
case "occupancy":
case "power":
case "problem":
case "smoke":
case "sound":
case "vibration":
if (isOn) {
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`,
"device_class",
device_class
);
}
if (isOff) {
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`,
"device_class",
device_class
);
}
break;
}
break;
}
case "cover":
return state === "open"
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`)
: hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
case "lock":
if (state === "unlocked") {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
}
if (state === "locked") {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
}
break;
}
if (state === BINARY_STATE_ON) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.turned_on`);
}
if (state === BINARY_STATE_OFF) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.turned_off`);
}
if (UNAVAILABLE_STATES.includes(state)) {
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.became_unavailable`);
}
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.changed_to_state`,
"state",
stateObj
? computeStateDisplay(hass.localize, stateObj, hass.language, state)
: state
);
};

View File

@ -60,6 +60,7 @@ export class MoreInfoLogbook extends LitElement {
narrow
no-icon
no-name
relative-time
.hass=${this.hass}
.entries=${this._logbookEntries}
.userIdToName=${this._persons}

View File

@ -1,6 +1,6 @@
import {
css,
CSSResult,
CSSResultArray,
customElement,
eventOptions,
html,
@ -15,12 +15,14 @@ import { formatDate } from "../../common/datetime/format_date";
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
import { restoreScroll } from "../../common/decorators/restore-scroll";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { domainIcon } from "../../common/entity/domain_icon";
import { stateIcon } from "../../common/entity/state_icon";
import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
import "../../components/ha-circular-progress";
import "../../components/ha-icon";
import "../../components/ha-relative-time";
import { LogbookEntry } from "../../data/logbook";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@customElement("ha-logbook")
@ -46,6 +48,9 @@ class HaLogbook extends LitElement {
@property({ type: Boolean, attribute: "no-name" })
public noName = false;
@property({ type: Boolean, attribute: "relative-time" })
public relativeTime = false;
// @ts-ignore
@restoreScroll(".container") private _savedScrollPos?: number;
@ -109,6 +114,7 @@ class HaLogbook extends LitElement {
const state = item.entity_id ? this.hass.states[item.entity_id] : undefined;
const item_username =
item.context_user_id && this.userIdToName[item.context_user_id];
return html`
<div class="entry-container">
${index === 0 ||
@ -123,51 +129,52 @@ class HaLogbook extends LitElement {
`
: html``}
<div class="entry">
<div class="time">
${formatTimeWithSeconds(new Date(item.when), this.hass.language)}
</div>
<div class="entry ${classMap({ "no-entity": !item.entity_id })}">
<div class="icon-message">
${!this.noIcon
? html`
<ha-icon
.icon=${state ? stateIcon(state) : domainIcon(item.domain)}
.icon=${domainIcon(
item.entity_id
? computeDomain(item.entity_id)
: item.domain,
state,
item.state
)}
></ha-icon>
`
: ""}
<div class="message-relative_time">
<div class="message">
${!this.noName
? !item.entity_id
? html`<span class="name">${item.name}</span>`
: html`
<a
? html`<a
href="#"
@click=${this._entityClicked}
.entityId=${item.entity_id}
class="name"
>${item.name}</a
>
`
><span class="name">${item.name}</span></a
>`
: ""}
${item.message}
${item_username
? ` by ${item_username}`
? ` ${this.hass.localize(
"ui.components.logbook.by"
)} ${item_username}`
: !item.context_event_type
? ""
: item.context_event_type === "call_service"
? // Service Call
` by service
` ${this.hass.localize("ui.components.logbook.by_service")}
${item.context_domain}.${item.context_service}`
: item.context_entity_id === item.entity_id
? // HomeKit or something that self references
` by
` ${this.hass.localize("ui.components.logbook.by")}
${
item.context_name
? item.context_name
: item.context_event_type
}`
: // Another entity such as an automation or script
html` by
html` ${this.hass.localize("ui.components.logbook.by")}
<a
href="#"
@click=${this._entityClicked}
@ -176,6 +183,20 @@ class HaLogbook extends LitElement {
>${item.context_entity_id_name}</a
>`}
</div>
<div class="secondary">
<span
>${formatTimeWithSeconds(
new Date(item.when),
this.hass.language
)}</span
>
-
<ha-relative-time
.hass=${this.hass}
.datetime=${item.when}
></ha-relative-time>
</div>
</div>
</div>
</div>
</div>
@ -188,14 +209,22 @@ class HaLogbook extends LitElement {
}
private _entityClicked(ev: Event) {
const entityId = (ev.currentTarget as any).entityId;
if (!entityId) {
return;
}
ev.preventDefault();
ev.stopPropagation();
fireEvent(this, "hass-more-info", {
entityId: (ev.target as any).entityId,
entityId: entityId,
});
}
static get styles(): CSSResult {
return css`
static get styles(): CSSResultArray {
return [
haStyle,
css`
:host {
display: block;
height: 100%;
@ -219,14 +248,27 @@ class HaLogbook extends LitElement {
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
.time {
.entry.no-entity,
.no-name .entry {
cursor: default;
}
.entry:hover {
background-color: rgba(var(--rgb-primary-text-color), 0.04);
}
.narrow:not(.no-icon) .time {
margin-left: 32px;
}
.message-relative_time {
display: flex;
justify-content: center;
flex-direction: column;
width: 75px;
flex-shrink: 0;
}
.secondary {
font-size: 12px;
color: var(--secondary-text-color);
line-height: 1.7;
}
.date {
@ -253,9 +295,9 @@ class HaLogbook extends LitElement {
}
ha-icon {
margin: 0 8px 0 16px;
margin-right: 16px;
flex-shrink: 0;
color: var(--primary-text-color);
color: var(--state-icon-color);
}
.message {
@ -283,15 +325,15 @@ class HaLogbook extends LitElement {
}
.narrow .entry {
flex-direction: column;
line-height: 1.5;
padding: 8px 0;
padding: 8px;
}
.narrow .icon-message ha-icon {
margin-left: 0;
}
`;
`,
];
}
}

View File

@ -115,18 +115,22 @@ export class HaPanelLogbook extends LitElement {
</div>
${this._isLoading
? html`<div class="progress-wrapper">
? html`
<div class="progress-wrapper">
<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>
</div>`
: html`<ha-logbook
</div>
`
: html`
<ha-logbook
.hass=${this.hass}
.entries=${this._entries}
.userIdToName=${this._userIdToName}
virtualize
></ha-logbook>`}
></ha-logbook>
`}
</ha-app-layout>
`;
}
@ -268,6 +272,7 @@ export class HaPanelLogbook extends LitElement {
),
this._fetchUserDone,
]);
// Fixed in TS 3.9 but upgrade out of scope for this PR.
// @ts-ignore
this._entries = entries;

View File

@ -285,7 +285,34 @@
},
"components": {
"logbook": {
"entries_not_found": "No logbook entries found."
"entries_not_found": "No logbook entries found.",
"by": "by",
"by_service": "by service",
"messages": {
"was_away": "was away",
"was_at_state": "was at {state}",
"rose": "rose",
"set": "set",
"was_low": "was low",
"was_normal": "was normal",
"was_connected": "was connected",
"was_disconnected": "was disconnected",
"was_opened": "was opened",
"was_closed": "was closed",
"was_unlocked": "was unlocked",
"was_locked": "was locked",
"was_plugged_in": "was plugged in",
"was_unplugged": "was unplugged",
"was_at_home": "was at home",
"was_unsafe": "was unsafe",
"was_safe": "was safe",
"detected_device_class": "detected {device_class}",
"cleared_device_class": "cleared (no {device_class} detected)",
"turned_off": "turned off",
"turned_on": "turned on",
"changed_to_state": "changed to {state}",
"became_unavailable": "became unavailable"
}
},
"entity": {
"entity-picker": {