mirror of
https://github.com/home-assistant/frontend.git
synced 2026-02-04 01:10:33 -06:00
Add button to heading card (#28991)
* Add heading badge button * Fix look and feel * Improve editor * Prettier
This commit is contained in:
parent
e060c179f6
commit
1f04379974
@ -153,7 +153,7 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
.content:hover ha-icon-next {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import "../heading-badges/hui-button-heading-badge";
|
||||
import "../heading-badges/hui-entity-heading-badge";
|
||||
|
||||
import {
|
||||
@ -6,7 +7,7 @@ import {
|
||||
} from "./create-element-base";
|
||||
import type { LovelaceHeadingBadgeConfig } from "../heading-badges/types";
|
||||
|
||||
const ALWAYS_LOADED_TYPES = new Set(["error", "entity"]);
|
||||
const ALWAYS_LOADED_TYPES = new Set(["error", "entity", "button"]);
|
||||
|
||||
export const createHeadingBadgeElement = (config: LovelaceHeadingBadgeConfig) =>
|
||||
createLovelaceElement(
|
||||
|
||||
@ -1,21 +1,33 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiDelete, mdiDragHorizontalVariant, mdiPencil } from "@mdi/js";
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { preventDefault } from "../../../../common/dom/prevent_default";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../../components/entity/ha-entity-picker";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { computeEntityNameList } from "../../../../common/entity/compute_entity_name_display";
|
||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getHeadingBadgeElementClass } from "../../create-element/create-heading-badge-element";
|
||||
import type {
|
||||
ButtonHeadingBadgeConfig,
|
||||
EntityHeadingBadgeConfig,
|
||||
LovelaceHeadingBadgeConfig,
|
||||
} from "../../heading-badges/types";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
|
||||
const UI_BADGE_TYPES = ["entity", "button"] as const;
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -41,8 +53,12 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
return this._badgesKeys.get(badge)!;
|
||||
}
|
||||
|
||||
private _createValueChangedHandler(index: number) {
|
||||
return (ev: CustomEvent) => this._valueChanged(ev, index);
|
||||
private _getBadgeTypeLabel(type: string): string {
|
||||
return (
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.heading-badges.types.${type}.label`
|
||||
) || type
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@ -51,120 +67,186 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.badges
|
||||
${this.badges?.length
|
||||
? html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
@item-moved=${this._badgeMoved}
|
||||
>
|
||||
<div class="entities">
|
||||
<div class="badges">
|
||||
${repeat(
|
||||
this.badges,
|
||||
this.badges.filter(Boolean),
|
||||
(badge) => this._getKey(badge),
|
||||
(badge, index) => {
|
||||
const type = badge.type ?? "entity";
|
||||
const isEntityBadge =
|
||||
type === "entity" && "entity" in badge;
|
||||
const entityBadge = isEntityBadge
|
||||
? (badge as EntityHeadingBadgeConfig)
|
||||
: undefined;
|
||||
return html`
|
||||
<div class="badge">
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
</div>
|
||||
${isEntityBadge && entityBadge
|
||||
? html`
|
||||
<ha-entity-picker
|
||||
hide-clear-icon
|
||||
.hass=${this.hass}
|
||||
.value=${entityBadge.entity ?? ""}
|
||||
@value-changed=${this._createValueChangedHandler(
|
||||
index
|
||||
)}
|
||||
></ha-entity-picker>
|
||||
`
|
||||
: html`
|
||||
<div class="badge-content">
|
||||
<span>${type}</span>
|
||||
</div>
|
||||
`}
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.entities.edit`
|
||||
)}
|
||||
.path=${mdiPencil}
|
||||
class="edit-icon"
|
||||
.index=${index}
|
||||
@click=${this._editBadge}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.entities.remove`
|
||||
)}
|
||||
.path=${mdiDelete}
|
||||
class="remove-icon"
|
||||
.index=${index}
|
||||
@click=${this._removeEntity}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
(badge, index) => this._renderBadgeItem(badge, index)
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
<div class="add-container">
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.choose_entity"
|
||||
)}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.choose_entity"
|
||||
)}
|
||||
@value-changed=${this._entityPicked}
|
||||
.value=${undefined}
|
||||
@click=${preventDefault}
|
||||
add-button
|
||||
></ha-entity-picker>
|
||||
<ha-button-menu
|
||||
fixed
|
||||
@action=${this._addBadge}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-button slot="trigger" appearance="filled" size="small">
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(`ui.panel.lovelace.editor.heading-badges.add`)}
|
||||
</ha-button>
|
||||
${UI_BADGE_TYPES.map(
|
||||
(type) => html`
|
||||
<ha-list-item .value=${type}>
|
||||
${this._getBadgeTypeLabel(type)}
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderBadgeItem(badge: LovelaceHeadingBadgeConfig, index: number) {
|
||||
const type = badge.type ?? "entity";
|
||||
const entityBadge = badge as EntityHeadingBadgeConfig;
|
||||
const isWarning =
|
||||
type === "entity" &&
|
||||
(!entityBadge.entity || !this.hass.states[entityBadge.entity]);
|
||||
|
||||
return html`
|
||||
<div class=${classMap({ badge: true, warning: isWarning })}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon .path=${mdiDragHorizontalVariant}></ha-svg-icon>
|
||||
</div>
|
||||
${type === "entity"
|
||||
? this._renderEntityBadge(entityBadge)
|
||||
: type === "button"
|
||||
? this._renderButtonBadge(badge as ButtonHeadingBadgeConfig)
|
||||
: this._renderUnknownBadge(type)}
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize(`ui.panel.lovelace.editor.badges.edit`)}
|
||||
.path=${mdiPencil}
|
||||
class="edit-icon"
|
||||
.index=${index}
|
||||
@click=${this._editBadge}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize(`ui.panel.lovelace.editor.badges.remove`)}
|
||||
.path=${mdiDelete}
|
||||
class="remove-icon"
|
||||
.index=${index}
|
||||
@click=${this._removeBadge}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _entityPicked(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
private _renderEntityBadge(badge: EntityHeadingBadgeConfig) {
|
||||
const entityId = badge.entity;
|
||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||
|
||||
if (!entityId) {
|
||||
return html`
|
||||
<div class="badge-content">
|
||||
<div>
|
||||
<span>${this._getBadgeTypeLabel("entity")}</span>
|
||||
<span class="secondary"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.heading-badges.no_entity"
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const newEntity: LovelaceHeadingBadgeConfig = {
|
||||
type: "entity",
|
||||
entity: ev.detail.value,
|
||||
};
|
||||
const newBadges = [...(this.badges || []), newEntity];
|
||||
(ev.target as HaEntityPicker).value = undefined;
|
||||
fireEvent(this, "heading-badges-changed", { badges: newBadges });
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<div class="badge-content">
|
||||
<div>
|
||||
<span>${entityId}</span>
|
||||
<span class="secondary"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.heading-badges.entity_not_found"
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const [entityName, deviceName, areaName] = computeEntityNameList(
|
||||
stateObj,
|
||||
[{ type: "entity" }, { type: "device" }, { type: "area" }],
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
);
|
||||
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [entityName ? deviceName : undefined, areaName]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
return html`
|
||||
<div class="badge-content">
|
||||
<div>
|
||||
<span>${primary}</span>
|
||||
${secondary
|
||||
? html`<span class="secondary">${secondary}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent, index: number): void {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
const newBadges = [...(this.badges || [])];
|
||||
private _renderButtonBadge(badge: ButtonHeadingBadgeConfig) {
|
||||
return html`
|
||||
<div class="badge-content">
|
||||
<div>
|
||||
<span>${this._getBadgeTypeLabel("button")}</span>
|
||||
${badge.text
|
||||
? html`<span class="secondary">${badge.text}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
newBadges.splice(index, 1);
|
||||
private _renderUnknownBadge(type: string) {
|
||||
return html`
|
||||
<div class="badge-content">
|
||||
<div>
|
||||
<span>${type}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _addBadge(ev: CustomEvent): Promise<void> {
|
||||
const index = ev.detail.index as number;
|
||||
|
||||
if (index == null) return;
|
||||
|
||||
const type = UI_BADGE_TYPES[index];
|
||||
if (!type) return;
|
||||
|
||||
const elClass = await getHeadingBadgeElementClass(type);
|
||||
|
||||
let newBadge: LovelaceHeadingBadgeConfig;
|
||||
if (elClass && elClass.getStubConfig) {
|
||||
newBadge = elClass.getStubConfig(this.hass);
|
||||
} else {
|
||||
newBadges[index] = {
|
||||
...newBadges[index],
|
||||
entity: value,
|
||||
};
|
||||
newBadge = { type } as LovelaceHeadingBadgeConfig;
|
||||
}
|
||||
|
||||
const newBadges = [...(this.badges || []), newBadge];
|
||||
|
||||
fireEvent(this, "heading-badges-changed", { badges: newBadges });
|
||||
|
||||
await nextRender();
|
||||
// Open the editor for the new badge
|
||||
fireEvent(this, "edit-heading-badge", { index: newBadges.length - 1 });
|
||||
}
|
||||
|
||||
private _badgeMoved(ev: CustomEvent): void {
|
||||
@ -177,7 +259,7 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
fireEvent(this, "heading-badges-changed", { badges: newBadges });
|
||||
}
|
||||
|
||||
private _removeEntity(ev: CustomEvent): void {
|
||||
private _removeBadge(ev: CustomEvent): void {
|
||||
const index = (ev.currentTarget as any).index;
|
||||
const newBadges = [...(this.badges || [])];
|
||||
|
||||
@ -198,11 +280,12 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-button {
|
||||
|
||||
ha-button-menu {
|
||||
margin-top: var(--ha-space-2);
|
||||
}
|
||||
|
||||
.entities {
|
||||
.badges {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-2);
|
||||
@ -212,6 +295,7 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.badge .handle {
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
@ -220,13 +304,14 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
padding-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.badge .handle > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.badge-content {
|
||||
height: 60px;
|
||||
font-size: var(--ha-font-size-l);
|
||||
height: var(--ha-space-12);
|
||||
font-size: var(--ha-font-size-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@ -238,15 +323,9 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.badge ha-entity-picker {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.remove-icon,
|
||||
.edit-icon {
|
||||
--mdc-icon-button-size: 36px;
|
||||
--mdc-icon-button-size: var(--ha-space-9);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
@ -255,24 +334,19 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.badge.warning {
|
||||
background-color: var(--ha-color-fill-warning-quiet-resting);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.badge.warning .secondary {
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
|
||||
li[divider] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
|
||||
.add-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-top: var(--ha-space-2);
|
||||
}
|
||||
|
||||
mwc-menu-surface {
|
||||
--mdc-menu-min-width: 100%;
|
||||
}
|
||||
|
||||
ha-entity-picker {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -139,9 +139,7 @@ export class HuiHeadingCardEditor
|
||||
<ha-expansion-panel outlined>
|
||||
<ha-svg-icon slot="leading-icon" .path=${mdiListBox}></ha-svg-icon>
|
||||
<h3 slot="header">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.heading.entities"
|
||||
)}
|
||||
${this.hass!.localize("ui.panel.lovelace.editor.card.heading.badges")}
|
||||
</h3>
|
||||
<div class="content">
|
||||
<hui-heading-badges-editor
|
||||
|
||||
@ -0,0 +1,218 @@
|
||||
import { mdiEye, mdiGestureTap } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { any, array, assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import type { ButtonHeadingBadgeConfig } from "../../heading-badges/types";
|
||||
import type { LovelaceGenericElementEditor } from "../../types";
|
||||
import "../conditions/ha-card-conditions-editor";
|
||||
import { configElementStyle } from "../config-elements/config-elements-style";
|
||||
import { actionConfigStruct } from "../structs/action-struct";
|
||||
|
||||
const buttonConfigStruct = object({
|
||||
type: optional(string()),
|
||||
text: optional(string()),
|
||||
icon: optional(string()),
|
||||
color: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
visibility: optional(array(any())),
|
||||
});
|
||||
|
||||
@customElement("hui-button-heading-badge-editor")
|
||||
export class HuiButtonHeadingBadgeEditor
|
||||
extends LitElement
|
||||
implements LovelaceGenericElementEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public preview = false;
|
||||
|
||||
@state() private _config?: ButtonHeadingBadgeConfig;
|
||||
|
||||
public setConfig(config: ButtonHeadingBadgeConfig): void {
|
||||
assert(config, buttonConfigStruct);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
name: "text",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "icon",
|
||||
selector: { icon: {} },
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
selector: {
|
||||
ui_color: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema();
|
||||
|
||||
const conditions = this._config.visibility ?? [];
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
<ha-expansion-panel outlined>
|
||||
<ha-svg-icon slot="leading-icon" .path=${mdiEye}></ha-svg-icon>
|
||||
<h3 slot="header">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.heading.button_config.visibility"
|
||||
)}
|
||||
</h3>
|
||||
<div class="content">
|
||||
<p class="intro">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.heading.button_config.visibility_explanation"
|
||||
)}
|
||||
</p>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
@value-changed=${this._conditionChanged}
|
||||
>
|
||||
</ha-card-conditions-editor>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = ev.detail.value as ButtonHeadingBadgeConfig;
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _conditionChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conditions = ev.detail.value as Condition[];
|
||||
|
||||
const newConfig: ButtonHeadingBadgeConfig = {
|
||||
...this._config,
|
||||
visibility: conditions,
|
||||
};
|
||||
if (newConfig.visibility?.length === 0) {
|
||||
delete newConfig.visibility;
|
||||
}
|
||||
|
||||
fireEvent(this, "config-changed", { config: newConfig });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "text":
|
||||
case "color":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.heading.button_config.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
configElementStyle,
|
||||
css`
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-form {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.intro {
|
||||
margin: 0;
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-button-heading-badge-editor": HuiButtonHeadingBadgeEditor;
|
||||
}
|
||||
}
|
||||
143
src/panels/lovelace/heading-badges/hui-button-heading-badge.ts
Normal file
143
src/panels/lovelace/heading-badges/hui-button-heading-badge.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-icon";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import type {
|
||||
LovelaceHeadingBadge,
|
||||
LovelaceHeadingBadgeEditor,
|
||||
} from "../types";
|
||||
import type { ButtonHeadingBadgeConfig } from "./types";
|
||||
|
||||
const DEFAULT_ACTIONS: Pick<
|
||||
ButtonHeadingBadgeConfig,
|
||||
"tap_action" | "hold_action" | "double_tap_action"
|
||||
> = {
|
||||
tap_action: { action: "none" },
|
||||
hold_action: { action: "none" },
|
||||
double_tap_action: { action: "none" },
|
||||
};
|
||||
|
||||
@customElement("hui-button-heading-badge")
|
||||
export class HuiButtonHeadingBadge
|
||||
extends LitElement
|
||||
implements LovelaceHeadingBadge
|
||||
{
|
||||
public static async getConfigElement(): Promise<LovelaceHeadingBadgeEditor> {
|
||||
await import("../editor/heading-badge-editor/hui-button-heading-badge-editor");
|
||||
return document.createElement("hui-button-heading-badge-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): ButtonHeadingBadgeConfig {
|
||||
return {
|
||||
type: "button",
|
||||
icon: "mdi:gesture-tap-button",
|
||||
};
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: ButtonHeadingBadgeConfig;
|
||||
|
||||
@property({ type: Boolean }) public preview = false;
|
||||
|
||||
public setConfig(config: ButtonHeadingBadgeConfig): void {
|
||||
this._config = {
|
||||
...DEFAULT_ACTIONS,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
get hasAction() {
|
||||
return (
|
||||
hasAction(this._config?.tap_action) ||
|
||||
hasAction(this._config?.hold_action) ||
|
||||
hasAction(this._config?.double_tap_action)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const config = this._config;
|
||||
|
||||
const color = config.color ? computeCssColor(config.color) : undefined;
|
||||
|
||||
const style = { "--color": color };
|
||||
|
||||
return html`
|
||||
<ha-control-button
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
style=${styleMap(style)}
|
||||
.label=${config.text}
|
||||
class=${classMap({ colored: !!color, "with-text": !!config.text })}
|
||||
>
|
||||
<span class="content">
|
||||
${config.icon
|
||||
? html`<ha-icon .icon=${config.icon}></ha-icon>`
|
||||
: nothing}
|
||||
${config.text
|
||||
? html`<span class="text">${config.text}</span>`
|
||||
: nothing}
|
||||
</span>
|
||||
</ha-control-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-control-button {
|
||||
--control-button-border-radius: var(
|
||||
--ha-heading-badge-border-radius,
|
||||
var(--ha-border-radius-pill)
|
||||
);
|
||||
--control-button-padding: 0;
|
||||
--mdc-icon-size: var(--ha-heading-badge-icon-size, 14px);
|
||||
width: auto;
|
||||
height: var(--ha-heading-badge-size, 26px);
|
||||
min-width: var(--ha-heading-badge-size, 26px);
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
ha-control-button.with-text {
|
||||
--control-button-padding: 0 var(--ha-space-2);
|
||||
}
|
||||
ha-control-button.colored {
|
||||
--control-button-icon-color: var(--color);
|
||||
--control-button-background-color: var(--color);
|
||||
--control-button-focus-color: var(--color);
|
||||
--ha-ripple-color: var(--color);
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.text {
|
||||
padding: 0 var(--ha-space-1);
|
||||
line-height: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-button-heading-badge": HuiButtonHeadingBadge;
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ import "../../../state-display/state-display";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { DEFAULT_CONFIG } from "../editor/heading-badge-editor/hui-entity-heading-badge-editor";
|
||||
@ -43,6 +44,24 @@ export class HuiEntityHeadingBadge
|
||||
return document.createElement("hui-heading-entity-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(hass: HomeAssistant): EntityHeadingBadgeConfig {
|
||||
const includeDomains = ["sensor", "light", "switch"];
|
||||
const maxEntities = 1;
|
||||
const entities = Object.keys(hass.states);
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
[],
|
||||
includeDomains
|
||||
);
|
||||
|
||||
return {
|
||||
type: "entity",
|
||||
entity: foundEntities[0] || "",
|
||||
};
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EntityHeadingBadgeConfig;
|
||||
|
||||
@ -26,3 +26,13 @@ export interface EntityHeadingBadgeConfig extends LovelaceHeadingBadgeConfig {
|
||||
hold_action?: ActionConfig;
|
||||
double_tap_action?: ActionConfig;
|
||||
}
|
||||
|
||||
export interface ButtonHeadingBadgeConfig extends LovelaceHeadingBadgeConfig {
|
||||
type: "button";
|
||||
text?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
double_tap_action?: ActionConfig;
|
||||
}
|
||||
|
||||
@ -8480,7 +8480,7 @@
|
||||
"title": "Title",
|
||||
"subtitle": "Subtitle"
|
||||
},
|
||||
"entities": "Entities",
|
||||
"badges": "Badges",
|
||||
"entity_config": {
|
||||
"color": "[%key:ui::panel::lovelace::editor::card::tile::color%]",
|
||||
"color_helper": "[%key:ui::panel::lovelace::editor::card::tile::color_helper%]",
|
||||
@ -8495,6 +8495,12 @@
|
||||
"state": "[%key:ui::panel::lovelace::editor::badge::entity::displayed_elements_options::state%]"
|
||||
}
|
||||
},
|
||||
"button_config": {
|
||||
"text": "Text",
|
||||
"color": "Color",
|
||||
"visibility": "Visibility",
|
||||
"visibility_explanation": "The button will be shown when ALL conditions below are fulfilled. If no conditions are set, the button will always be shown."
|
||||
},
|
||||
"default_heading": "Kitchen"
|
||||
},
|
||||
"map": {
|
||||
@ -8772,6 +8778,11 @@
|
||||
"remove": "Remove entity",
|
||||
"form-label": "Edit entity"
|
||||
},
|
||||
"badges": {
|
||||
"name": "Badges",
|
||||
"edit": "Edit badge",
|
||||
"remove": "Remove badge"
|
||||
},
|
||||
"features": {
|
||||
"name": "Features",
|
||||
"not_compatible": "Not compatible",
|
||||
@ -9026,6 +9037,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"heading-badges": {
|
||||
"add": "Add badge",
|
||||
"no_entity": "No entity selected",
|
||||
"entity_not_found": "Entity not found",
|
||||
"types": {
|
||||
"entity": {
|
||||
"label": "Entity"
|
||||
},
|
||||
"button": {
|
||||
"label": "Button"
|
||||
}
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
"original-states": {
|
||||
"areas": "Areas to display",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user