diff --git a/src/panels/lovelace/card-features/hui-counter-actions-card-feature.ts b/src/panels/lovelace/card-features/hui-counter-actions-card-feature.ts new file mode 100644 index 0000000000..d4373a6827 --- /dev/null +++ b/src/panels/lovelace/card-features/hui-counter-actions-card-feature.ts @@ -0,0 +1,134 @@ +import { mdiRestore, mdiPlus, mdiMinus } from "@mdi/js"; +import type { HassEntity } from "home-assistant-js-websocket"; +import type { TemplateResult } from "lit"; +import { LitElement, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import "../../../components/ha-control-select"; +import { UNAVAILABLE } from "../../../data/entity"; +import type { HomeAssistant } from "../../../types"; +import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; +import { COUNTER_ACTIONS, type CounterActionsCardFeatureConfig } from "./types"; +import "../../../components/ha-control-button-group"; +import "../../../components/ha-control-button"; + +export const supportsCounterActionsCardFeature = (stateObj: HassEntity) => { + const domain = computeDomain(stateObj.entity_id); + return domain === "counter"; +}; + +interface CounterButton { + translationKey: string; + icon: string; + serviceName: string; + disabled: boolean; +} + +export const COUNTER_ACTIONS_BUTTON: Record< + string, + (stateObj: HassEntity) => CounterButton +> = { + increment: (stateObj) => ({ + translationKey: "increment", + icon: mdiPlus, + serviceName: "increment", + disabled: parseInt(stateObj.state) === stateObj.attributes.maximum, + }), + reset: () => ({ + translationKey: "reset", + icon: mdiRestore, + serviceName: "reset", + disabled: false, + }), + decrement: (stateObj) => ({ + translationKey: "decrement", + icon: mdiMinus, + serviceName: "decrement", + disabled: parseInt(stateObj.state) === stateObj.attributes.minimum, + }), +}; + +@customElement("hui-counter-actions-card-feature") +class HuiCounterActionsCardFeature + extends LitElement + implements LovelaceCardFeature +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @state() private _config?: CounterActionsCardFeatureConfig; + + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/hui-counter-actions-card-feature-editor" + ); + return document.createElement("hui-counter-actions-card-feature-editor"); + } + + static getStubConfig(): CounterActionsCardFeatureConfig { + return { + type: "counter-actions", + actions: COUNTER_ACTIONS.map((action) => action), + }; + } + + public setConfig(config: CounterActionsCardFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected render(): TemplateResult | null { + if ( + !this._config || + !this.hass || + !this.stateObj || + !supportsCounterActionsCardFeature(this.stateObj) + ) { + return null; + } + + return html` + + ${this._config?.actions + ?.filter((action) => COUNTER_ACTIONS.includes(action)) + .map((action) => { + const button = COUNTER_ACTIONS_BUTTON[action](this.stateObj!); + return html` + + + + `; + })} + + `; + } + + private _onActionTap(ev): void { + ev.stopPropagation(); + const entry = (ev.target! as any).entry as CounterButton; + this.hass!.callService("counter", entry.serviceName, { + entity_id: this.stateObj!.entity_id, + }); + } + + static styles = cardFeatureStyles; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-counter-actions-card-feature": HuiCounterActionsCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 682b30e5ab..3b30ac46d8 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -83,6 +83,15 @@ export interface ClimatePresetModesCardFeatureConfig { preset_modes?: string[]; } +export const COUNTER_ACTIONS = ["increment", "reset", "decrement"] as const; + +export type CounterActions = (typeof COUNTER_ACTIONS)[number]; + +export interface CounterActionsCardFeatureConfig { + type: "counter-actions"; + actions?: CounterActions[]; +} + export interface SelectOptionsCardFeatureConfig { type: "select-options"; options?: string[]; @@ -156,6 +165,7 @@ export type LovelaceCardFeatureConfig = | ClimateSwingHorizontalModesCardFeatureConfig | ClimateHvacModesCardFeatureConfig | ClimatePresetModesCardFeatureConfig + | CounterActionsCardFeatureConfig | CoverOpenCloseCardFeatureConfig | CoverPositionCardFeatureConfig | CoverTiltPositionCardFeatureConfig diff --git a/src/panels/lovelace/create-element/create-card-feature-element.ts b/src/panels/lovelace/create-element/create-card-feature-element.ts index 40ab6f3259..31ce2eccd5 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -4,6 +4,7 @@ import "../card-features/hui-climate-swing-modes-card-feature"; import "../card-features/hui-climate-swing-horizontal-modes-card-feature"; import "../card-features/hui-climate-hvac-modes-card-feature"; import "../card-features/hui-climate-preset-modes-card-feature"; +import "../card-features/hui-counter-actions-card-feature"; import "../card-features/hui-cover-open-close-card-feature"; import "../card-features/hui-cover-position-card-feature"; import "../card-features/hui-cover-tilt-card-feature"; @@ -40,6 +41,7 @@ const TYPES = new Set([ "climate-swing-horizontal-modes", "climate-hvac-modes", "climate-preset-modes", + "counter-actions", "cover-open-close", "cover-position", "cover-tilt-position", diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index 54fb1cc980..cb779eb5d2 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -24,6 +24,7 @@ import { supportsClimateHvacModesCardFeature } from "../../card-features/hui-cli import { supportsClimatePresetModesCardFeature } from "../../card-features/hui-climate-preset-modes-card-feature"; import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature"; import { supportsClimateSwingHorizontalModesCardFeature } from "../../card-features/hui-climate-swing-horizontal-modes-card-feature"; +import { supportsCounterActionsCardFeature } from "../../card-features/hui-counter-actions-card-feature"; import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover-open-close-card-feature"; import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature"; import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature"; @@ -59,6 +60,7 @@ const UI_FEATURE_TYPES = [ "climate-preset-modes", "climate-swing-modes", "climate-swing-horizontal-modes", + "counter-actions", "cover-open-close", "cover-position", "cover-tilt-position", @@ -92,6 +94,7 @@ const EDITABLES_FEATURE_TYPES = new Set([ "climate-preset-modes", "climate-swing-modes", "climate-swing-horizontal-modes", + "counter-actions", "fan-preset-modes", "humidifier-modes", "lawn-mower-commands", @@ -113,6 +116,7 @@ const SUPPORTS_FEATURE_TYPES: Record< supportsClimateSwingHorizontalModesCardFeature, "climate-hvac-modes": supportsClimateHvacModesCardFeature, "climate-preset-modes": supportsClimatePresetModesCardFeature, + "counter-actions": supportsCounterActionsCardFeature, "cover-open-close": supportsCoverOpenCloseCardFeature, "cover-position": supportsCoverPositionCardFeature, "cover-tilt-position": supportsCoverTiltPositionCardFeature, diff --git a/src/panels/lovelace/editor/config-elements/hui-counter-actions-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-counter-actions-card-feature-editor.ts new file mode 100644 index 0000000000..97e17f7c93 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-counter-actions-card-feature-editor.ts @@ -0,0 +1,91 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; +import "../../../../components/ha-form/ha-form"; +import type { HomeAssistant } from "../../../../types"; +import { + COUNTER_ACTIONS, + type LovelaceCardFeatureContext, + type CounterActionsCardFeatureConfig, +} from "../../card-features/types"; +import type { LovelaceCardFeatureEditor } from "../../types"; + +@customElement("hui-counter-actions-card-feature-editor") +export class HuiCounterActionsCardFeatureEditor + extends LitElement + implements LovelaceCardFeatureEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @state() private _config?: CounterActionsCardFeatureConfig; + + public setConfig(config: CounterActionsCardFeatureConfig): void { + this._config = config; + } + + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { + name: "actions", + selector: { + select: { + multiple: true, + mode: "list", + reorder: true, + options: COUNTER_ACTIONS.map((action) => ({ + value: action, + label: `${localize( + `ui.panel.lovelace.editor.features.types.counter-actions.actions.${action}` + )}`, + })), + }, + }, + }, + ] as const + ); + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + const schema = this._schema(this.hass.localize); + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-counter-actions-card-feature-editor": HuiCounterActionsCardFeatureEditor; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 7d9b86c609..d039401df7 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7001,7 +7001,8 @@ "suggested_cards": "Suggested cards", "other_cards": "Other cards", "custom_cards": "Custom cards", - "features": "Features" + "features": "Features", + "actions": "Actions" }, "heading": { "name": "Heading", @@ -7320,6 +7321,14 @@ "customize_modes": "Customize preset modes", "preset_modes": "Preset modes" }, + "counter-actions": { + "label": "Counter actions", + "actions": { + "increment": "Increment", + "decrement": "Decrement", + "reset": "Reset" + } + }, "fan-preset-modes": { "label": "Fan preset modes", "style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",