Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions src/components/entity/state-badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../common/color/compute-color";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { stateActive } from "../../common/entity/state_active";
import {
stateColorBrightness,
stateColorCss,
Expand All @@ -27,10 +29,15 @@ export class StateBadge extends LitElement {

@property({ attribute: false }) public overrideImage?: string;

// Cannot be a boolean attribute because undefined is treated different than
// false. When it is undefined, state is still colored for light entities.
/**
* Cannot be a boolean attribute because undefined is treated different than
* false. When it is undefined, state is still colored for light entities.
* @deprecated use `color` instead
*/
@property({ attribute: false }) public stateColor?: boolean;

// "state", "none" or a color (theme color name or CSS color). Custom colors
// apply when the entity is active. Takes precedence over stateColor.
@property() public color?: string;

// @todo Consider reworking to eliminate need for attribute since it is manipulated internally
Expand Down Expand Up @@ -67,11 +74,17 @@ export class StateBadge extends LitElement {
}
}

private get _stateColor() {
private get _color(): string | undefined {
if (this.color) {
return this.color;
}
if (this.stateColor !== undefined) {
return this.stateColor ? "state" : "none";
}
const domain = this.stateObj
? computeStateDomain(this.stateObj)
: undefined;
return this.stateColor ?? domain === "light";
return domain === "light" ? "state" : undefined;
}

protected render() {
Expand Down Expand Up @@ -131,6 +144,7 @@ export class StateBadge extends LitElement {

if (stateObj) {
const domain = computeDomain(stateObj.entity_id);
const color = this._color;
if (this.overrideImage === undefined) {
// hide icon if we have entity picture
if (
Expand All @@ -147,13 +161,10 @@ export class StateBadge extends LitElement {
}
backgroundImage = `url(${imageUrl})`;
this.icon = false;
} else if (this.color) {
// Externally provided overriding color wins over state color
iconStyle.color = this.color;
} else if (this._stateColor) {
const color = stateColorCss(stateObj);
if (color) {
iconStyle.color = color;
} else if (color === "state") {
const stateColor = stateColorCss(stateObj);
if (stateColor) {
iconStyle.color = stateColor;
}
if (stateObj.attributes.rgb_color) {
iconStyle.color = `rgb(${stateObj.attributes.rgb_color.join(",")})`;
Expand All @@ -180,6 +191,8 @@ export class StateBadge extends LitElement {
delete iconStyle.color;
}
}
} else if (color && color !== "none" && stateActive(stateObj)) {
iconStyle.color = computeCssColor(color);
}
} else if (this.overrideImage) {
backgroundImage = `url(${this._resolveImageUrl(this.overrideImage)})`;
Expand Down
6 changes: 4 additions & 2 deletions src/components/entity/state-info.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import type { HomeAssistant } from "../../types";
import "../ha-relative-time";
import "../ha-tooltip";
Expand All @@ -23,10 +24,11 @@ class StateInfo extends LitElement {

const name = this.hass.formatEntityName(this.stateObj, { type: "entity" });

// Inline style because the state-badge color API only colors active entities
return html`<state-badge
.stateObj=${this.stateObj}
.stateColor=${true}
.color=${this.color}
.stateColor=${!this.color}
style=${styleMap({ color: this.color })}
></state-badge>
<div class="info">
<div class="name ${this.inDialog ? "in-dialog" : ""}" .title=${name}>
Expand Down
27 changes: 14 additions & 13 deletions src/panels/lovelace/cards/hui-entities-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../../../components/ha-card";
import type { HomeAssistant } from "../../../types";
import { computeCardSize } from "../common/compute-card-size";
import { findEntities } from "../common/find-entities";
import { applyDefaultColor } from "../common/entity-color-config";
import { processConfigEntities } from "../common/process-config-entities";
import "../components/hui-entities-toggle";
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
Expand All @@ -24,7 +25,7 @@ import type {
LovelaceHeaderFooter,
} from "../types";
import { migrateEntitiesCardConfig } from "./migrate-card-config";
import type { EntitiesCardConfig } from "./types";
import type { EntitiesCardConfig, EntitiesCardEntityConfig } from "./types";
import { haStyleScrollbar } from "../../../resources/styles";

export const computeShowHeaderToggle = <
Expand Down Expand Up @@ -159,7 +160,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
const migratedConfig = migrateEntitiesCardConfig(config);
const entities = processConfigEntities(migratedConfig.entities);

this._config = migratedConfig;
this._config = { color: "state", ...migratedConfig };
this._configEntities = entities;
this._showHeaderToggle = computeShowHeaderToggle(migratedConfig, entities);
if (this._config.header) {
Expand Down Expand Up @@ -343,18 +344,18 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
`,
];

private _renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
const element = createRowElement(
(!("type" in entityConf) || entityConf.type === "conditional") &&
"state_color" in this._config!
? ({
state_color: this._config.state_color,
...(entityConf as EntityConfig),
} as EntityConfig)
: entityConf.type === "perform-action"
? { ...entityConf, type: "call-service" }
: entityConf
private _rowConfig(entityConf: LovelaceRowConfig): LovelaceRowConfig {
if (entityConf.type === "perform-action") {
return { ...entityConf, type: "call-service" };
}
return applyDefaultColor(
entityConf as EntitiesCardEntityConfig,
this._config!.color
);
}

private _renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
const element = createRowElement(this._rowConfig(entityConf));
if (this._hass) {
element.hass = this._hass;
}
Expand Down
20 changes: 12 additions & 8 deletions src/panels/lovelace/cards/hui-glance-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { findEntities } from "../common/find-entities";
import { handleAction } from "../common/handle-action";
import { hasAction, hasAnyAction } from "../common/has-action";
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
import { applyDefaultColor } from "../common/entity-color-config";
import { processConfigEntities } from "../common/process-config-entities";
import "../components/hui-timestamp-display";
import { createEntityNotFoundWarning } from "../components/hui-warning";
Expand Down Expand Up @@ -87,14 +88,19 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
show_name: true,
show_state: true,
show_icon: true,
state_color: true,
color: "state",
...migratedConfig,
};
const cardColor = this._config.color;
const entities = processConfigEntities(migratedConfig.entities).map(
(entityConf) => ({
hold_action: { action: "more-info" } as MoreInfoActionConfig,
...entityConf,
})
(entityConf) =>
applyDefaultColor(
{
hold_action: { action: "more-info" } as MoreInfoActionConfig,
...entityConf,
},
cardColor
)
);

for (const entity of entities) {
Expand Down Expand Up @@ -294,9 +300,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
.stateObj=${stateObj}
.overrideIcon=${entityConf.icon}
.overrideImage=${entityConf.image}
.stateColor=${
entityConf.state_color ?? this._config!.state_color
}
.color=${entityConf.color}
></state-badge>
`
: ""
Expand Down
77 changes: 46 additions & 31 deletions src/panels/lovelace/cards/migrate-card-config.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,56 @@
import type { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
import { migrateStateColorConfig } from "../common/entity-color-config";
import { migrateTimeFormatConfig } from "../common/entity-time-format-config";
import type {
ConditionalRowConfig,
LovelaceRowConfig,
} from "../entity-rows/types";
import type {
EntitiesCardConfig,
EntitiesCardEntityConfig,
GlanceCardConfig,
GlanceConfigEntity,
} from "./types";

const migrateEntitiesRowConfig = (
rowConf: LovelaceRowConfig | string
): LovelaceRowConfig | string => {
if (typeof rowConf !== "object") {
return rowConf;
}
let newConf: LovelaceRowConfig = rowConf;
newConf = migrateTimeFormatConfig(newConf as EntitiesCardEntityConfig);
newConf = migrateStateColorConfig(newConf as EntitiesCardEntityConfig);
if (newConf.type === "conditional") {
const row = (newConf as ConditionalRowConfig).row;
if (row && typeof row === "object") {
let newRow = migrateTimeFormatConfig(row as EntitiesCardEntityConfig);
newRow = migrateStateColorConfig(newRow);
if (newRow !== row) {
newConf = { ...newConf, row: newRow } as ConditionalRowConfig;
}
}
}
return newConf;
};

export const migrateEntitiesCardConfig = (
config: EntitiesCardConfig
): EntitiesCardConfig => {
let changed = false;
const newEntities = config.entities?.map((e) => {
if (typeof e !== "object") {
return e;
}
// Custom rows own their config schema and may use `format` with a
// different meaning (e.g. custom:multiple-entity-row), so leave it
// untouched.
if (e.type?.startsWith("custom:")) {
return e;
const newConf = migrateEntitiesRowConfig(e);
if (newConf !== e) {
changed = true;
}
if (!("format" in e)) {
return e;
}
changed = true;
const { format, ...rest } = e;
return {
...rest,
time_format: (rest as EntityConfig).time_format ?? format,
};
return newConf;
});
const newConfig = migrateStateColorConfig(config);
if (!changed) {
return config;
return newConfig;
}
return {
...config,
entities: newEntities as (LovelaceRowConfig | string)[],
...newConfig,
entities: newEntities,
};
};

Expand All @@ -46,21 +62,20 @@ export const migrateGlanceCardConfig = (
if (typeof e !== "object") {
return e;
}
if (!("format" in e)) {
return e;
let newConf = e;
newConf = migrateTimeFormatConfig(newConf);
newConf = migrateStateColorConfig(newConf);
if (newConf !== e) {
changed = true;
}
changed = true;
const { format, ...rest } = e;
return {
...rest,
time_format: rest.time_format ?? format,
};
return newConf;
});
const newConfig = migrateStateColorConfig(config);
if (!changed) {
return config;
return newConfig;
}
return {
...config,
...newConfig,
entities: newEntities as (GlanceConfigEntity | string)[],
};
};
8 changes: 8 additions & 0 deletions src/panels/lovelace/cards/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
/** @deprecated use `color` instead */
state_color?: boolean;
color?: string;
show_name?: boolean;
show_icon?: boolean;
}
Expand All @@ -126,7 +128,9 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
icon?: string;
header?: LovelaceHeaderFooterConfig;
footer?: LovelaceHeaderFooterConfig;
/** @deprecated use `color` instead */
state_color?: boolean;
color?: string;
}

export type AreaCardDisplayType = "compact" | "icon" | "picture" | "camera";
Expand Down Expand Up @@ -330,7 +334,9 @@ export interface GlanceConfigEntity extends ConfigEntity {
show_last_changed?: boolean;
image?: string;
show_state?: boolean;
/** @deprecated use `color` instead */
state_color?: boolean;
color?: string;
time_format?: TimestampRenderingFormat;
}

Expand All @@ -342,7 +348,9 @@ export interface GlanceCardConfig extends LovelaceCardConfig {
theme?: string;
entities: (string | GlanceConfigEntity)[];
columns?: number;
/** @deprecated use `color` instead */
state_color?: boolean;
color?: string;
}

export interface HumidifierCardConfig extends LovelaceCardConfig {
Expand Down
34 changes: 34 additions & 0 deletions src/panels/lovelace/common/entity-color-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { isCustomType } from "../../../data/lovelace_custom_cards";

interface LegacyStateColorConfig {
type?: string;
color?: string;
state_color?: boolean;
}

export const migrateStateColorConfig = <T extends LegacyStateColorConfig>(
config: T
): T => {
// Custom elements own their config schema, leave them untouched
if (config.type !== undefined && isCustomType(config.type)) {
return config;
}
if (config.state_color === undefined) {
return config;
}
const { state_color, ...rest } = config;
return {
color: state_color ? "state" : "none",
...rest,
} as T;
};

export const applyDefaultColor = <T extends { type?: string; color?: string }>(
config: T,
color: string | undefined
): T =>
color === undefined ||
config.color !== undefined ||
(config.type !== undefined && isCustomType(config.type))
? config
: { ...config, color };
Loading