Use entity based color for state history (#14330)

This commit is contained in:
Paul Bottein 2022-11-10 09:51:13 +01:00 committed by GitHub
parent 238e844068
commit 1c03dc9b77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 102 additions and 66 deletions

View File

@ -5,14 +5,14 @@ import { computeDomain } from "./compute_domain";
const NORMAL_UNKNOWN_DOMAIN = ["button", "input_button", "scene"];
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 state = stateObj.state;
const compareState = state !== undefined ? state : stateObj?.state;
if (
OFF_STATES.includes(state) &&
!(NORMAL_UNKNOWN_DOMAIN.includes(domain) && state === "unknown") &&
!(NORMAL_OFF_DOMAIN.includes(domain) && state === "script")
OFF_STATES.includes(compareState) &&
!(NORMAL_UNKNOWN_DOMAIN.includes(domain) && compareState === "unknown") &&
!(NORMAL_OFF_DOMAIN.includes(domain) && compareState === "script")
) {
return false;
}
@ -20,16 +20,16 @@ export function stateActive(stateObj: HassEntity): boolean {
// Custom cases
switch (domain) {
case "cover":
return state === "open" || state === "opening";
return compareState === "open" || compareState === "opening";
case "device_tracker":
case "person":
return state !== "not_home";
return compareState !== "not_home";
case "media-player":
return state !== "idle" && state !== "standby";
return compareState !== "idle" && compareState !== "standby";
case "vacuum":
return state === "on" || state === "cleaning";
return compareState === "on" || compareState === "cleaning";
case "plant":
return state === "problem";
return compareState === "problem";
default:
return true;
}

View File

@ -10,12 +10,12 @@ import { sensorColor } from "./color/sensor_color";
import { computeDomain } from "./compute_domain";
import { stateActive } from "./state_active";
export const stateColorCss = (stateObj?: HassEntity) => {
if (!stateObj || !stateActive(stateObj)) {
export const stateColorCss = (stateObj?: HassEntity, state?: string) => {
if (!stateObj || !stateActive(stateObj, state)) {
return `var(--rgb-disabled-color)`;
}
const color = stateColor(stateObj);
const color = stateColor(stateObj, state);
if (color) {
return `var(--rgb-state-${color}-color)`;
@ -24,13 +24,13 @@ export const stateColorCss = (stateObj?: HassEntity) => {
return `var(--rgb-primary-color)`;
};
export const stateColor = (stateObj: HassEntity) => {
const state = stateObj.state;
export const stateColor = (stateObj: HassEntity, state?: string) => {
const compareState = state !== undefined ? state : stateObj?.state;
const domain = computeDomain(stateObj.entity_id);
switch (domain) {
case "alarm_control_panel":
return alarmControlPanelColor(state);
return alarmControlPanelColor(compareState);
case "binary_sensor":
return binarySensorColor(stateObj);
@ -39,10 +39,10 @@ export const stateColor = (stateObj: HassEntity) => {
return coverColor(stateObj);
case "climate":
return climateColor(state);
return climateColor(compareState);
case "lock":
return lockColor(state);
return lockColor(compareState);
case "light":
return "light";
@ -64,7 +64,7 @@ export const stateColor = (stateObj: HassEntity) => {
return "vacuum";
case "sun":
return state === "above_horizon" ? "sun-day" : "sun-night";
return compareState === "above_horizon" ? "sun-day" : "sun-night";
case "update":
return updateIsInstalling(stateObj as UpdateEntity)

View File

@ -3,8 +3,10 @@ import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { getGraphColorByIndex } from "../../common/color/colors";
import { rgb2hex } from "../../common/color/convert-color";
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 { computeRTL } from "../../common/util/compute_rtl";
import { TimelineEntity } from "../../data/history";
@ -12,65 +14,55 @@ import { HomeAssistant } from "../../types";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
import type { TimeLineData } from "./timeline-chart/const";
/** Binary sensor device classes for which the static colors for on/off are NOT inverted.
* 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 stateColorTokenMap: Map<string, string> = new Map();
const stateColorMap: Map<string, string> = new Map();
let colorIndex = 0;
const invertOnOff = (entityState?: HassEntity) =>
entityState &&
computeDomain(entityState.entity_id) === "binary_sensor" &&
"device_class" in entityState.attributes &&
!BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED.has(
entityState.attributes.device_class!
);
export const getStateColorToken = (
stateString: string,
entityState: HassEntity
) => {
if (!stateActive(entityState, stateString)) {
return `disabled`;
}
const color = stateColor(entityState, stateString);
if (color) {
return `state-${color}`;
}
return undefined;
};
const getColor = (
stateString: string,
entityState: HassEntity,
computedStyles: CSSStyleDeclaration
) => {
// Inversion is only valid for "on" or "off" state
if (
(stateString === "on" || stateString === "off") &&
invertOnOff(entityState)
) {
stateString = stateString === "on" ? "off" : "on";
const stateColorToken = getStateColorToken(stateString, entityState);
if (stateColorToken) {
if (stateColorTokenMap.has(stateColorToken)) {
return stateColorTokenMap.get(stateColorToken);
}
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)) {
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);
colorIndex++;
stateColorMap.set(stateString, color);

View File

@ -45,6 +45,7 @@ export class MoreInfoLogbook extends LitElement {
narrow
no-icon
no-name
show-indicator
relative-time
></ha-logbook>
`;

View File

@ -11,12 +11,15 @@ import {
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement, eventOptions, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
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 { 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/ha-circular-progress";
import "../../components/ha-relative-time";
@ -67,6 +70,9 @@ class HaLogbookRenderer extends LitElement {
@property({ type: Boolean, attribute: "virtualize", reflect: true })
public virtualize = false;
@property({ type: Boolean, attribute: "show-indicator" })
public showIndicator = false;
@property({ type: Boolean, attribute: "no-icon" })
public noIcon = false;
@ -132,7 +138,7 @@ class HaLogbookRenderer extends LitElement {
if (!item || index === undefined) {
return html``;
}
const previous = this.entries[index - 1];
const previous = this.entries[index - 1] as LogbookEntry | undefined;
const seenEntityIds: string[] = [];
const currentStateObj = item.entity_id
? this.hass.states[item.entity_id]
@ -199,6 +205,7 @@ class HaLogbookRenderer extends LitElement {
></state-badge>
`
: ""}
${this.showIndicator ? this._renderIndicator(item) : ""}
<div class="message-relative_time">
<div class="message">
${!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(
item: LogbookEntry,
seenEntityIds: string[],
@ -541,6 +565,7 @@ class HaLogbookRenderer extends LitElement {
}
.entry {
position: relative;
display: flex;
width: 100%;
line-height: 2em;
@ -551,6 +576,20 @@ class HaLogbookRenderer extends LitElement {
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 {
color: var(--secondary-text-color);
}

View File

@ -65,6 +65,9 @@ export class HaLogbook extends LitElement {
@property({ type: Boolean, attribute: "no-name" })
public noName = false;
@property({ type: Boolean, attribute: "show-indicator" })
public showIndicator = false;
@property({ type: Boolean, attribute: "relative-time" })
public relativeTime = false;
@ -126,6 +129,7 @@ export class HaLogbook extends LitElement {
.virtualize=${this.virtualize}
.noIcon=${this.noIcon}
.noName=${this.noName}
.showIndicator=${this.showIndicator}
.relativeTime=${this.relativeTime}
.entries=${this._logbookEntries}
.traceContexts=${this._traceContexts}