mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Use entity based color for state history (#14330)
This commit is contained in:
parent
238e844068
commit
1c03dc9b77
@ -5,14 +5,14 @@ import { computeDomain } from "./compute_domain";
|
|||||||
const NORMAL_UNKNOWN_DOMAIN = ["button", "input_button", "scene"];
|
const NORMAL_UNKNOWN_DOMAIN = ["button", "input_button", "scene"];
|
||||||
const NORMAL_OFF_DOMAIN = ["script"];
|
const NORMAL_OFF_DOMAIN = ["script"];
|
||||||
|
|
||||||
export function stateActive(stateObj: HassEntity): boolean {
|
export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||||
const domain = computeDomain(stateObj.entity_id);
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
const state = stateObj.state;
|
const compareState = state !== undefined ? state : stateObj?.state;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
OFF_STATES.includes(state) &&
|
OFF_STATES.includes(compareState) &&
|
||||||
!(NORMAL_UNKNOWN_DOMAIN.includes(domain) && state === "unknown") &&
|
!(NORMAL_UNKNOWN_DOMAIN.includes(domain) && compareState === "unknown") &&
|
||||||
!(NORMAL_OFF_DOMAIN.includes(domain) && state === "script")
|
!(NORMAL_OFF_DOMAIN.includes(domain) && compareState === "script")
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -20,16 +20,16 @@ export function stateActive(stateObj: HassEntity): boolean {
|
|||||||
// Custom cases
|
// Custom cases
|
||||||
switch (domain) {
|
switch (domain) {
|
||||||
case "cover":
|
case "cover":
|
||||||
return state === "open" || state === "opening";
|
return compareState === "open" || compareState === "opening";
|
||||||
case "device_tracker":
|
case "device_tracker":
|
||||||
case "person":
|
case "person":
|
||||||
return state !== "not_home";
|
return compareState !== "not_home";
|
||||||
case "media-player":
|
case "media-player":
|
||||||
return state !== "idle" && state !== "standby";
|
return compareState !== "idle" && compareState !== "standby";
|
||||||
case "vacuum":
|
case "vacuum":
|
||||||
return state === "on" || state === "cleaning";
|
return compareState === "on" || compareState === "cleaning";
|
||||||
case "plant":
|
case "plant":
|
||||||
return state === "problem";
|
return compareState === "problem";
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,12 @@ import { sensorColor } from "./color/sensor_color";
|
|||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
import { stateActive } from "./state_active";
|
import { stateActive } from "./state_active";
|
||||||
|
|
||||||
export const stateColorCss = (stateObj?: HassEntity) => {
|
export const stateColorCss = (stateObj?: HassEntity, state?: string) => {
|
||||||
if (!stateObj || !stateActive(stateObj)) {
|
if (!stateObj || !stateActive(stateObj, state)) {
|
||||||
return `var(--rgb-disabled-color)`;
|
return `var(--rgb-disabled-color)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = stateColor(stateObj);
|
const color = stateColor(stateObj, state);
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
return `var(--rgb-state-${color}-color)`;
|
return `var(--rgb-state-${color}-color)`;
|
||||||
@ -24,13 +24,13 @@ export const stateColorCss = (stateObj?: HassEntity) => {
|
|||||||
return `var(--rgb-primary-color)`;
|
return `var(--rgb-primary-color)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stateColor = (stateObj: HassEntity) => {
|
export const stateColor = (stateObj: HassEntity, state?: string) => {
|
||||||
const state = stateObj.state;
|
const compareState = state !== undefined ? state : stateObj?.state;
|
||||||
const domain = computeDomain(stateObj.entity_id);
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
|
|
||||||
switch (domain) {
|
switch (domain) {
|
||||||
case "alarm_control_panel":
|
case "alarm_control_panel":
|
||||||
return alarmControlPanelColor(state);
|
return alarmControlPanelColor(compareState);
|
||||||
|
|
||||||
case "binary_sensor":
|
case "binary_sensor":
|
||||||
return binarySensorColor(stateObj);
|
return binarySensorColor(stateObj);
|
||||||
@ -39,10 +39,10 @@ export const stateColor = (stateObj: HassEntity) => {
|
|||||||
return coverColor(stateObj);
|
return coverColor(stateObj);
|
||||||
|
|
||||||
case "climate":
|
case "climate":
|
||||||
return climateColor(state);
|
return climateColor(compareState);
|
||||||
|
|
||||||
case "lock":
|
case "lock":
|
||||||
return lockColor(state);
|
return lockColor(compareState);
|
||||||
|
|
||||||
case "light":
|
case "light":
|
||||||
return "light";
|
return "light";
|
||||||
@ -64,7 +64,7 @@ export const stateColor = (stateObj: HassEntity) => {
|
|||||||
return "vacuum";
|
return "vacuum";
|
||||||
|
|
||||||
case "sun":
|
case "sun":
|
||||||
return state === "above_horizon" ? "sun-day" : "sun-night";
|
return compareState === "above_horizon" ? "sun-day" : "sun-night";
|
||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
return updateIsInstalling(stateObj as UpdateEntity)
|
return updateIsInstalling(stateObj as UpdateEntity)
|
||||||
|
@ -3,8 +3,10 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { getGraphColorByIndex } from "../../common/color/colors";
|
import { getGraphColorByIndex } from "../../common/color/colors";
|
||||||
|
import { rgb2hex } from "../../common/color/convert-color";
|
||||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { stateActive } from "../../common/entity/state_active";
|
||||||
|
import { stateColor } from "../../common/entity/state_color";
|
||||||
import { numberFormatToLocale } from "../../common/number/format_number";
|
import { numberFormatToLocale } from "../../common/number/format_number";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import { TimelineEntity } from "../../data/history";
|
import { TimelineEntity } from "../../data/history";
|
||||||
@ -12,65 +14,55 @@ import { HomeAssistant } from "../../types";
|
|||||||
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
||||||
import type { TimeLineData } from "./timeline-chart/const";
|
import type { TimeLineData } from "./timeline-chart/const";
|
||||||
|
|
||||||
/** Binary sensor device classes for which the static colors for on/off are NOT inverted.
|
const stateColorTokenMap: Map<string, string> = new Map();
|
||||||
* List the ones were "on" = good or normal state => should be rendered "green".
|
|
||||||
* Note: It is now a "not inverted" list (compared to the past) since we now have more inverted ones.
|
|
||||||
*/
|
|
||||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED = new Set([
|
|
||||||
"battery_charging",
|
|
||||||
"connectivity",
|
|
||||||
"light",
|
|
||||||
"moving",
|
|
||||||
"plug",
|
|
||||||
"power",
|
|
||||||
"presence",
|
|
||||||
"running",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const STATIC_STATE_COLORS = new Set([
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"home",
|
|
||||||
"not_home",
|
|
||||||
"unavailable",
|
|
||||||
"unknown",
|
|
||||||
"idle",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const stateColorMap: Map<string, string> = new Map();
|
const stateColorMap: Map<string, string> = new Map();
|
||||||
|
|
||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
|
|
||||||
const invertOnOff = (entityState?: HassEntity) =>
|
export const getStateColorToken = (
|
||||||
entityState &&
|
stateString: string,
|
||||||
computeDomain(entityState.entity_id) === "binary_sensor" &&
|
entityState: HassEntity
|
||||||
"device_class" in entityState.attributes &&
|
) => {
|
||||||
!BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED.has(
|
if (!stateActive(entityState, stateString)) {
|
||||||
entityState.attributes.device_class!
|
return `disabled`;
|
||||||
);
|
}
|
||||||
|
const color = stateColor(entityState, stateString);
|
||||||
|
if (color) {
|
||||||
|
return `state-${color}`;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const getColor = (
|
const getColor = (
|
||||||
stateString: string,
|
stateString: string,
|
||||||
entityState: HassEntity,
|
entityState: HassEntity,
|
||||||
computedStyles: CSSStyleDeclaration
|
computedStyles: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
// Inversion is only valid for "on" or "off" state
|
const stateColorToken = getStateColorToken(stateString, entityState);
|
||||||
if (
|
|
||||||
(stateString === "on" || stateString === "off") &&
|
if (stateColorToken) {
|
||||||
invertOnOff(entityState)
|
if (stateColorTokenMap.has(stateColorToken)) {
|
||||||
) {
|
return stateColorTokenMap.get(stateColorToken);
|
||||||
stateString = stateString === "on" ? "off" : "on";
|
}
|
||||||
|
const value = computedStyles.getPropertyValue(
|
||||||
|
`--rgb-${stateColorToken}-color`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const parsedValue = value.split(",").map((v) => Number(v)) as [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number
|
||||||
|
];
|
||||||
|
const hexValue = rgb2hex(parsedValue);
|
||||||
|
stateColorTokenMap.set(stateColorToken, hexValue);
|
||||||
|
return hexValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateColorMap.has(stateString)) {
|
if (stateColorMap.has(stateString)) {
|
||||||
return stateColorMap.get(stateString);
|
return stateColorMap.get(stateString);
|
||||||
}
|
}
|
||||||
if (STATIC_STATE_COLORS.has(stateString)) {
|
|
||||||
const color = computedStyles.getPropertyValue(
|
|
||||||
`--state-${stateString}-color`
|
|
||||||
);
|
|
||||||
stateColorMap.set(stateString, color);
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
const color = getGraphColorByIndex(colorIndex, computedStyles);
|
const color = getGraphColorByIndex(colorIndex, computedStyles);
|
||||||
colorIndex++;
|
colorIndex++;
|
||||||
stateColorMap.set(stateString, color);
|
stateColorMap.set(stateString, color);
|
||||||
|
@ -45,6 +45,7 @@ export class MoreInfoLogbook extends LitElement {
|
|||||||
narrow
|
narrow
|
||||||
no-icon
|
no-icon
|
||||||
no-name
|
no-name
|
||||||
|
show-indicator
|
||||||
relative-time
|
relative-time
|
||||||
></ha-logbook>
|
></ha-logbook>
|
||||||
`;
|
`;
|
||||||
|
@ -11,12 +11,15 @@ import {
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { customElement, eventOptions, property } from "lit/decorators";
|
import { customElement, eventOptions, property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { formatDate } from "../../common/datetime/format_date";
|
import { formatDate } from "../../common/datetime/format_date";
|
||||||
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
|
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
|
||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
|
import { stateActive } from "../../common/entity/state_active";
|
||||||
|
import { stateColorCss } from "../../common/entity/state_color";
|
||||||
import "../../components/entity/state-badge";
|
import "../../components/entity/state-badge";
|
||||||
import "../../components/ha-circular-progress";
|
import "../../components/ha-circular-progress";
|
||||||
import "../../components/ha-relative-time";
|
import "../../components/ha-relative-time";
|
||||||
@ -67,6 +70,9 @@ class HaLogbookRenderer extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "virtualize", reflect: true })
|
@property({ type: Boolean, attribute: "virtualize", reflect: true })
|
||||||
public virtualize = false;
|
public virtualize = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "show-indicator" })
|
||||||
|
public showIndicator = false;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-icon" })
|
@property({ type: Boolean, attribute: "no-icon" })
|
||||||
public noIcon = false;
|
public noIcon = false;
|
||||||
|
|
||||||
@ -132,7 +138,7 @@ class HaLogbookRenderer extends LitElement {
|
|||||||
if (!item || index === undefined) {
|
if (!item || index === undefined) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const previous = this.entries[index - 1];
|
const previous = this.entries[index - 1] as LogbookEntry | undefined;
|
||||||
const seenEntityIds: string[] = [];
|
const seenEntityIds: string[] = [];
|
||||||
const currentStateObj = item.entity_id
|
const currentStateObj = item.entity_id
|
||||||
? this.hass.states[item.entity_id]
|
? this.hass.states[item.entity_id]
|
||||||
@ -199,6 +205,7 @@ class HaLogbookRenderer extends LitElement {
|
|||||||
></state-badge>
|
></state-badge>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this.showIndicator ? this._renderIndicator(item) : ""}
|
||||||
<div class="message-relative_time">
|
<div class="message-relative_time">
|
||||||
<div class="message">
|
<div class="message">
|
||||||
${!this.noName // Used for more-info panel (single entity case)
|
${!this.noName // Used for more-info panel (single entity case)
|
||||||
@ -253,6 +260,23 @@ class HaLogbookRenderer extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderIndicator(item: LogbookEntry) {
|
||||||
|
const stateObj = this.hass.states[item.entity_id!] as
|
||||||
|
| HassEntity
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const color =
|
||||||
|
stateObj && stateActive(stateObj, item.state)
|
||||||
|
? stateColorCss(stateObj, item.state)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
"--indicator-color": color,
|
||||||
|
};
|
||||||
|
|
||||||
|
return html` <div class="indicator" style=${styleMap(style)}></div> `;
|
||||||
|
}
|
||||||
|
|
||||||
private _renderMessage(
|
private _renderMessage(
|
||||||
item: LogbookEntry,
|
item: LogbookEntry,
|
||||||
seenEntityIds: string[],
|
seenEntityIds: string[],
|
||||||
@ -541,6 +565,7 @@ class HaLogbookRenderer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
@ -551,6 +576,20 @@ class HaLogbookRenderer extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
background-color: rgb(
|
||||||
|
var(--indicator-color, var(--rgb-disabled-color))
|
||||||
|
);
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
margin-inline-end: 12px;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
|
||||||
ha-icon-next {
|
ha-icon-next {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,9 @@ export class HaLogbook extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "no-name" })
|
@property({ type: Boolean, attribute: "no-name" })
|
||||||
public noName = false;
|
public noName = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "show-indicator" })
|
||||||
|
public showIndicator = false;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "relative-time" })
|
@property({ type: Boolean, attribute: "relative-time" })
|
||||||
public relativeTime = false;
|
public relativeTime = false;
|
||||||
|
|
||||||
@ -126,6 +129,7 @@ export class HaLogbook extends LitElement {
|
|||||||
.virtualize=${this.virtualize}
|
.virtualize=${this.virtualize}
|
||||||
.noIcon=${this.noIcon}
|
.noIcon=${this.noIcon}
|
||||||
.noName=${this.noName}
|
.noName=${this.noName}
|
||||||
|
.showIndicator=${this.showIndicator}
|
||||||
.relativeTime=${this.relativeTime}
|
.relativeTime=${this.relativeTime}
|
||||||
.entries=${this._logbookEntries}
|
.entries=${this._logbookEntries}
|
||||||
.traceContexts=${this._traceContexts}
|
.traceContexts=${this._traceContexts}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user