Change entity badge display type to 3 booleans : name, state and icon (#21798)

* Change display type to 3 boolean : name, state and icon for entity badge

* Fix image url

* Fix not found entity

* Update state-label badge migration
This commit is contained in:
Paul Bottein 2024-08-28 09:53:07 +02:00 committed by GitHub
parent e9cbd54979
commit 7c5f947865
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 164 additions and 84 deletions

View File

@ -13,7 +13,7 @@ export const ensureBadgeConfig = (
return { return {
type: "entity", type: "entity",
entity: config, entity: config,
display_type: "complete", show_name: true,
}; };
} }
if ("type" in config && config.type) { if ("type" in config && config.type) {

View File

@ -1,3 +1,4 @@
import { mdiAlertCircle } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
@ -5,15 +6,16 @@ import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { mdiAlertCircle } from "@mdi/js";
import { computeCssColor } from "../../../common/color/compute-color"; import { computeCssColor } from "../../../common/color/compute-color";
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color"; import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { stateActive } from "../../../common/entity/state_active"; import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-ripple"; import "../../../components/ha-ripple";
import "../../../components/ha-state-icon"; import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { cameraUrlWithWidthHeight } from "../../../data/camera";
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
@ -22,15 +24,38 @@ import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action"; import { hasAction } from "../common/has-action";
import { LovelaceBadge, LovelaceBadgeEditor } from "../types"; import { LovelaceBadge, LovelaceBadgeEditor } from "../types";
import { EntityBadgeConfig } from "./types"; import { EntityBadgeConfig } from "./types";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { cameraUrlWithWidthHeight } from "../../../data/camera";
export const DISPLAY_TYPES = ["minimal", "standard", "complete"] as const; export const DISPLAY_TYPES = ["minimal", "standard", "complete"] as const;
export type DisplayType = (typeof DISPLAY_TYPES)[number]; export type DisplayType = (typeof DISPLAY_TYPES)[number];
export const DEFAULT_DISPLAY_TYPE: DisplayType = "standard"; export const DEFAULT_DISPLAY_TYPE: DisplayType = "standard";
export const DEFAULT_CONFIG: EntityBadgeConfig = {
type: "entity",
show_name: false,
show_state: true,
show_icon: true,
};
export const migrateLegacyEntityBadgeConfig = (
config: EntityBadgeConfig
): EntityBadgeConfig => {
const newConfig = { ...config };
if (config.display_type) {
if (config.show_name === undefined) {
if (config.display_type === "complete") {
newConfig.show_name = true;
}
}
if (config.show_state === undefined) {
if (config.display_type === "minimal") {
newConfig.show_state = false;
}
}
delete newConfig.display_type;
}
return newConfig;
};
@customElement("hui-entity-badge") @customElement("hui-entity-badge")
export class HuiEntityBadge extends LitElement implements LovelaceBadge { export class HuiEntityBadge extends LitElement implements LovelaceBadge {
public static async getConfigElement(): Promise<LovelaceBadgeEditor> { public static async getConfigElement(): Promise<LovelaceBadgeEditor> {
@ -64,7 +89,10 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
@state() protected _config?: EntityBadgeConfig; @state() protected _config?: EntityBadgeConfig;
public setConfig(config: EntityBadgeConfig): void { public setConfig(config: EntityBadgeConfig): void {
this._config = config; this._config = {
...DEFAULT_CONFIG,
...migrateLegacyEntityBadgeConfig(config),
};
} }
get hasAction() { get hasAction() {
@ -134,9 +162,9 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
return html` return html`
<div class="badge error"> <div class="badge error">
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon> <ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
<span class="content"> <span class="info">
<span class="name">${entityId}</span> <span class="label">${entityId}</span>
<span class="state"> <span class="content">
${this.hass.localize("ui.badge.entity.not_found")} ${this.hass.localize("ui.badge.entity.not_found")}
</span> </span>
</span> </span>
@ -163,18 +191,25 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
const name = this._config.name || stateObj.attributes.friendly_name; const name = this._config.name || stateObj.attributes.friendly_name;
const displayType = this._config.display_type || DEFAULT_DISPLAY_TYPE; const showState = this._config.show_state;
const showName = this._config.show_name;
const showIcon = this._config.show_icon;
const showEntityPicture = this._config.show_entity_picture;
const imageUrl = this._config.show_entity_picture const imageUrl = showEntityPicture
? this._getImageUrl(stateObj) ? this._getImageUrl(stateObj)
: undefined; : undefined;
const label = showState && showName ? name : undefined;
const content = showState ? stateDisplay : showName ? name : undefined;
return html` return html`
<div <div
style=${styleMap(style)} style=${styleMap(style)}
class="badge ${classMap({ class="badge ${classMap({
active, active,
[displayType]: true, "no-info": !showState && !showName,
"no-icon": !showIcon,
})}" })}"
@action=${this._handleAction} @action=${this._handleAction}
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
@ -185,22 +220,22 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
tabindex=${ifDefined(this.hasAction ? "0" : undefined)} tabindex=${ifDefined(this.hasAction ? "0" : undefined)}
> >
<ha-ripple .disabled=${!this.hasAction}></ha-ripple> <ha-ripple .disabled=${!this.hasAction}></ha-ripple>
${imageUrl ${showIcon
? html`<img src=${imageUrl} aria-hidden />` ? imageUrl
: html` ? html`<img src=${imageUrl} aria-hidden />`
<ha-state-icon : html`
.hass=${this.hass} <ha-state-icon
.stateObj=${stateObj} .hass=${this.hass}
.icon=${this._config.icon} .stateObj=${stateObj}
></ha-state-icon> .icon=${this._config.icon}
`} ></ha-state-icon>
${displayType !== "minimal" `
: nothing}
${content
? html` ? html`
<span class="content"> <span class="info">
${displayType === "complete" ${label ? html`<span class="label">${name}</span>` : nothing}
? html`<span class="name">${name}</span>` <span class="content">${content}</span>
: nothing}
<span class="state">${stateDisplay}</span>
</span> </span>
` `
: nothing} : nothing}
@ -277,7 +312,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
.badge.active { .badge.active {
--badge-color: var(--primary-color); --badge-color: var(--primary-color);
} }
.content { .info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@ -285,7 +320,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
padding-inline-end: 4px; padding-inline-end: 4px;
padding-inline-start: initial; padding-inline-start: initial;
} }
.name { .label {
font-size: 10px; font-size: 10px;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
@ -293,7 +328,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
letter-spacing: 0.1px; letter-spacing: 0.1px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.state { .content {
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
@ -313,14 +348,20 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
object-fit: cover; object-fit: cover;
overflow: hidden; overflow: hidden;
} }
.badge.minimal { .badge.no-info {
padding: 0; padding: 0;
} }
.badge:not(.minimal) img { .badge:not(.no-icon) img {
margin-left: -6px; margin-left: -6px;
margin-inline-start: -6px; margin-inline-start: -6px;
margin-inline-end: initial; margin-inline-end: initial;
} }
.badge.no-icon .info {
padding-right: 4px;
padding-left: 4px;
padding-inline-end: 4px;
padding-inline-start: 4px;
}
`; `;
} }
} }

View File

@ -16,10 +16,10 @@ export class HuiStateLabelBadge extends HuiEntityBadge {
const entityBadgeConfig: EntityBadgeConfig = { const entityBadgeConfig: EntityBadgeConfig = {
type: "entity", type: "entity",
entity: config.entity, entity: config.entity,
display_type: config.show_name === false ? "standard" : "complete", show_name: config.show_name ?? true,
}; };
this._config = entityBadgeConfig; super.setConfig(entityBadgeConfig);
} }
} }

View File

@ -3,6 +3,7 @@ import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
import type { LegacyStateFilter } from "../common/evaluate-filter"; import type { LegacyStateFilter } from "../common/evaluate-filter";
import type { Condition } from "../common/validate-condition"; import type { Condition } from "../common/validate-condition";
import type { EntityFilterEntityConfig } from "../entity-rows/types"; import type { EntityFilterEntityConfig } from "../entity-rows/types";
import type { DisplayType } from "./hui-entity-badge";
export interface EntityFilterBadgeConfig extends LovelaceBadgeConfig { export interface EntityFilterBadgeConfig extends LovelaceBadgeConfig {
type: "entity-filter"; type: "entity-filter";
@ -33,10 +34,16 @@ export interface EntityBadgeConfig extends LovelaceBadgeConfig {
name?: string; name?: string;
icon?: string; icon?: string;
color?: string; color?: string;
show_name?: boolean;
show_state?: boolean;
show_icon?: boolean;
show_entity_picture?: boolean; show_entity_picture?: boolean;
display_type?: "minimal" | "standard" | "complete";
state_content?: string | string[]; state_content?: string | string[];
tap_action?: ActionConfig; tap_action?: ActionConfig;
hold_action?: ActionConfig; hold_action?: ActionConfig;
double_tap_action?: ActionConfig; double_tap_action?: ActionConfig;
/**
* @deprecated use `show_state`, `show_name`, `icon_type`
*/
display_type?: DisplayType;
} }

View File

@ -22,8 +22,9 @@ import type {
} from "../../../../components/ha-form/types"; } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { import {
DEFAULT_DISPLAY_TYPE, DEFAULT_CONFIG,
DISPLAY_TYPES, DISPLAY_TYPES,
migrateLegacyEntityBadgeConfig,
} from "../../badges/hui-entity-badge"; } from "../../badges/hui-entity-badge";
import { EntityBadgeConfig } from "../../badges/types"; import { EntityBadgeConfig } from "../../badges/types";
import type { LovelaceBadgeEditor } from "../../types"; import type { LovelaceBadgeEditor } from "../../types";
@ -42,10 +43,12 @@ const badgeConfigStruct = assign(
icon: optional(string()), icon: optional(string()),
state_content: optional(union([string(), array(string())])), state_content: optional(union([string(), array(string())])),
color: optional(string()), color: optional(string()),
show_name: optional(boolean()),
show_state: optional(boolean()),
show_icon: optional(boolean()),
show_entity_picture: optional(boolean()), show_entity_picture: optional(boolean()),
tap_action: optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
show_name: optional(boolean()), image: optional(string()), // For old badge config support
image: optional(string()),
}) })
); );
@ -60,7 +63,10 @@ export class HuiEntityBadgeEditor
public setConfig(config: EntityBadgeConfig): void { public setConfig(config: EntityBadgeConfig): void {
assert(config, badgeConfigStruct); assert(config, badgeConfigStruct);
this._config = config; this._config = {
...DEFAULT_CONFIG,
...migrateLegacyEntityBadgeConfig(config),
};
} }
private _schema = memoizeOne( private _schema = memoizeOne(
@ -73,20 +79,6 @@ export class HuiEntityBadgeEditor
iconPath: mdiPalette, iconPath: mdiPalette,
title: localize(`ui.panel.lovelace.editor.badge.entity.appearance`), title: localize(`ui.panel.lovelace.editor.badge.entity.appearance`),
schema: [ schema: [
{
name: "display_type",
selector: {
select: {
mode: "dropdown",
options: DISPLAY_TYPES.map((type) => ({
value: type,
label: localize(
`ui.panel.lovelace.editor.badge.entity.display_type_options.${type}`
),
})),
},
},
},
{ {
name: "", name: "",
type: "grid", type: "grid",
@ -97,6 +89,12 @@ export class HuiEntityBadgeEditor
text: {}, text: {},
}, },
}, },
{
name: "color",
selector: {
ui_color: { default_color: true },
},
},
{ {
name: "icon", name: "icon",
selector: { selector: {
@ -104,12 +102,6 @@ export class HuiEntityBadgeEditor
}, },
context: { icon_entity: "entity" }, context: { icon_entity: "entity" },
}, },
{
name: "color",
selector: {
ui_color: { default_color: true },
},
},
{ {
name: "show_entity_picture", name: "show_entity_picture",
selector: { selector: {
@ -118,7 +110,35 @@ export class HuiEntityBadgeEditor
}, },
], ],
}, },
{
name: "displayed_elements",
selector: {
select: {
mode: "list",
multiple: true,
options: [
{
value: "name",
label: localize(
`ui.panel.lovelace.editor.badge.entity.displayed_elements_options.name`
),
},
{
value: "state",
label: localize(
`ui.panel.lovelace.editor.badge.entity.displayed_elements_options.state`
),
},
{
value: "icon",
label: localize(
`ui.panel.lovelace.editor.badge.entity.displayed_elements_options.icon`
),
},
],
},
},
},
{ {
name: "state_content", name: "state_content",
selector: { selector: {
@ -151,6 +171,20 @@ export class HuiEntityBadgeEditor
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
_displayedElements = memoizeOne((config: EntityBadgeConfig) => {
const elements: string[] = [];
if (config.show_name) {
elements.push("name");
}
if (config.show_state) {
elements.push("state");
}
if (config.show_icon) {
elements.push("icon");
}
return elements;
});
protected render() { protected render() {
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return nothing; return nothing;
@ -158,11 +192,10 @@ export class HuiEntityBadgeEditor
const schema = this._schema(this.hass!.localize); const schema = this._schema(this.hass!.localize);
const data = { ...this._config }; const data = {
...this._config,
if (!data.display_type) { displayed_elements: this._displayedElements(this._config),
data.display_type = DEFAULT_DISPLAY_TYPE; };
}
return html` return html`
<ha-form <ha-form
@ -181,18 +214,17 @@ export class HuiEntityBadgeEditor
return; return;
} }
const newConfig = ev.detail.value as EntityBadgeConfig; const config = { ...ev.detail.value } as EntityBadgeConfig;
const config: EntityBadgeConfig = {
...newConfig,
};
if (!config.state_content) { if (!config.state_content) {
delete config.state_content; delete config.state_content;
} }
if (config.display_type === "standard") { if (config.displayed_elements) {
delete config.display_type; config.show_name = config.displayed_elements.includes("name");
config.show_state = config.displayed_elements.includes("state");
config.show_icon = config.displayed_elements.includes("icon");
delete config.displayed_elements;
} }
fireEvent(this, "config-changed", { config }); fireEvent(this, "config-changed", { config });
@ -204,8 +236,8 @@ export class HuiEntityBadgeEditor
switch (schema.name) { switch (schema.name) {
case "color": case "color":
case "state_content": case "state_content":
case "display_type":
case "show_entity_picture": case "show_entity_picture":
case "displayed_elements":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.badge.entity.${schema.name}` `ui.panel.lovelace.editor.badge.entity.${schema.name}`
); );

View File

@ -30,11 +30,11 @@ export class HuiStateLabelBadgeEditor extends HuiEntityBadgeEditor {
const entityBadgeConfig: EntityBadgeConfig = { const entityBadgeConfig: EntityBadgeConfig = {
type: "entity", type: "entity",
entity: config.entity, entity: config.entity,
display_type: config.show_name === false ? "standard" : "complete", show_name: config.show_name ?? true,
}; };
// @ts-ignore // @ts-ignore
this._config = entityBadgeConfig; super.setConfig(entityBadgeConfig);
} }
} }

View File

@ -5959,9 +5959,9 @@
"paste": "Paste from clipboard", "paste": "Paste from clipboard",
"paste_description": "Paste a {type} card from the clipboard", "paste_description": "Paste a {type} card from the clipboard",
"refresh_interval": "Refresh interval", "refresh_interval": "Refresh interval",
"show_icon": "Show icon?", "show_icon": "Show icon",
"show_name": "Show name?", "show_name": "Show name",
"show_state": "Show state?", "show_state": "Show state",
"tap_action": "Tap behavior", "tap_action": "Tap behavior",
"title": "Title", "title": "Title",
"theme": "Theme", "theme": "Theme",
@ -6101,11 +6101,11 @@
"appearance": "Appearance", "appearance": "Appearance",
"show_entity_picture": "Show entity picture", "show_entity_picture": "Show entity picture",
"state_content": "State content", "state_content": "State content",
"display_type": "Display type", "displayed_elements": "Displayed elements",
"display_type_options": { "displayed_elements_options": {
"minimal": "Minimal (icon only)", "icon": "Icon",
"standard": "Standard (icon and state)", "name": "Name",
"complete": "Complete (icon, name and state)" "state": "State"
} }
}, },
"generic": { "generic": {