mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Add tile card feature for counter actions (#24340)
* Add tile card feature for counter actions * Format * Change icon * Disable buttons when hit limit * Change increment/decrement icons
This commit is contained in:
parent
0bd7d27c57
commit
a7a4194e09
@ -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<LovelaceCardFeatureEditor> {
|
||||||
|
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`
|
||||||
|
<ha-control-button-group>
|
||||||
|
${this._config?.actions
|
||||||
|
?.filter((action) => COUNTER_ACTIONS.includes(action))
|
||||||
|
.map((action) => {
|
||||||
|
const button = COUNTER_ACTIONS_BUTTON[action](this.stateObj!);
|
||||||
|
return html`
|
||||||
|
<ha-control-button
|
||||||
|
.entry=${button}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
// @ts-ignore
|
||||||
|
`ui.card.counter.actions.${button.translationKey}`
|
||||||
|
)}
|
||||||
|
@click=${this._onActionTap}
|
||||||
|
.disabled=${button.disabled ||
|
||||||
|
this.stateObj?.state === UNAVAILABLE}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
|
||||||
|
</ha-control-button>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</ha-control-button-group>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -83,6 +83,15 @@ export interface ClimatePresetModesCardFeatureConfig {
|
|||||||
preset_modes?: string[];
|
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 {
|
export interface SelectOptionsCardFeatureConfig {
|
||||||
type: "select-options";
|
type: "select-options";
|
||||||
options?: string[];
|
options?: string[];
|
||||||
@ -156,6 +165,7 @@ export type LovelaceCardFeatureConfig =
|
|||||||
| ClimateSwingHorizontalModesCardFeatureConfig
|
| ClimateSwingHorizontalModesCardFeatureConfig
|
||||||
| ClimateHvacModesCardFeatureConfig
|
| ClimateHvacModesCardFeatureConfig
|
||||||
| ClimatePresetModesCardFeatureConfig
|
| ClimatePresetModesCardFeatureConfig
|
||||||
|
| CounterActionsCardFeatureConfig
|
||||||
| CoverOpenCloseCardFeatureConfig
|
| CoverOpenCloseCardFeatureConfig
|
||||||
| CoverPositionCardFeatureConfig
|
| CoverPositionCardFeatureConfig
|
||||||
| CoverTiltPositionCardFeatureConfig
|
| CoverTiltPositionCardFeatureConfig
|
||||||
|
@ -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-swing-horizontal-modes-card-feature";
|
||||||
import "../card-features/hui-climate-hvac-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-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-open-close-card-feature";
|
||||||
import "../card-features/hui-cover-position-card-feature";
|
import "../card-features/hui-cover-position-card-feature";
|
||||||
import "../card-features/hui-cover-tilt-card-feature";
|
import "../card-features/hui-cover-tilt-card-feature";
|
||||||
@ -40,6 +41,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
|
|||||||
"climate-swing-horizontal-modes",
|
"climate-swing-horizontal-modes",
|
||||||
"climate-hvac-modes",
|
"climate-hvac-modes",
|
||||||
"climate-preset-modes",
|
"climate-preset-modes",
|
||||||
|
"counter-actions",
|
||||||
"cover-open-close",
|
"cover-open-close",
|
||||||
"cover-position",
|
"cover-position",
|
||||||
"cover-tilt-position",
|
"cover-tilt-position",
|
||||||
|
@ -24,6 +24,7 @@ import { supportsClimateHvacModesCardFeature } from "../../card-features/hui-cli
|
|||||||
import { supportsClimatePresetModesCardFeature } from "../../card-features/hui-climate-preset-modes-card-feature";
|
import { supportsClimatePresetModesCardFeature } from "../../card-features/hui-climate-preset-modes-card-feature";
|
||||||
import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-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 { 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 { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover-open-close-card-feature";
|
||||||
import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature";
|
import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature";
|
||||||
import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature";
|
import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature";
|
||||||
@ -59,6 +60,7 @@ const UI_FEATURE_TYPES = [
|
|||||||
"climate-preset-modes",
|
"climate-preset-modes",
|
||||||
"climate-swing-modes",
|
"climate-swing-modes",
|
||||||
"climate-swing-horizontal-modes",
|
"climate-swing-horizontal-modes",
|
||||||
|
"counter-actions",
|
||||||
"cover-open-close",
|
"cover-open-close",
|
||||||
"cover-position",
|
"cover-position",
|
||||||
"cover-tilt-position",
|
"cover-tilt-position",
|
||||||
@ -92,6 +94,7 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
|
|||||||
"climate-preset-modes",
|
"climate-preset-modes",
|
||||||
"climate-swing-modes",
|
"climate-swing-modes",
|
||||||
"climate-swing-horizontal-modes",
|
"climate-swing-horizontal-modes",
|
||||||
|
"counter-actions",
|
||||||
"fan-preset-modes",
|
"fan-preset-modes",
|
||||||
"humidifier-modes",
|
"humidifier-modes",
|
||||||
"lawn-mower-commands",
|
"lawn-mower-commands",
|
||||||
@ -113,6 +116,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
|||||||
supportsClimateSwingHorizontalModesCardFeature,
|
supportsClimateSwingHorizontalModesCardFeature,
|
||||||
"climate-hvac-modes": supportsClimateHvacModesCardFeature,
|
"climate-hvac-modes": supportsClimateHvacModesCardFeature,
|
||||||
"climate-preset-modes": supportsClimatePresetModesCardFeature,
|
"climate-preset-modes": supportsClimatePresetModesCardFeature,
|
||||||
|
"counter-actions": supportsCounterActionsCardFeature,
|
||||||
"cover-open-close": supportsCoverOpenCloseCardFeature,
|
"cover-open-close": supportsCoverOpenCloseCardFeature,
|
||||||
"cover-position": supportsCoverPositionCardFeature,
|
"cover-position": supportsCoverPositionCardFeature,
|
||||||
"cover-tilt-position": supportsCoverTiltPositionCardFeature,
|
"cover-tilt-position": supportsCoverTiltPositionCardFeature,
|
||||||
|
@ -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`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this._config}
|
||||||
|
.schema=${schema}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -7001,7 +7001,8 @@
|
|||||||
"suggested_cards": "Suggested cards",
|
"suggested_cards": "Suggested cards",
|
||||||
"other_cards": "Other cards",
|
"other_cards": "Other cards",
|
||||||
"custom_cards": "Custom cards",
|
"custom_cards": "Custom cards",
|
||||||
"features": "Features"
|
"features": "Features",
|
||||||
|
"actions": "Actions"
|
||||||
},
|
},
|
||||||
"heading": {
|
"heading": {
|
||||||
"name": "Heading",
|
"name": "Heading",
|
||||||
@ -7320,6 +7321,14 @@
|
|||||||
"customize_modes": "Customize preset modes",
|
"customize_modes": "Customize preset modes",
|
||||||
"preset_modes": "Preset modes"
|
"preset_modes": "Preset modes"
|
||||||
},
|
},
|
||||||
|
"counter-actions": {
|
||||||
|
"label": "Counter actions",
|
||||||
|
"actions": {
|
||||||
|
"increment": "Increment",
|
||||||
|
"decrement": "Decrement",
|
||||||
|
"reset": "Reset"
|
||||||
|
}
|
||||||
|
},
|
||||||
"fan-preset-modes": {
|
"fan-preset-modes": {
|
||||||
"label": "Fan preset modes",
|
"label": "Fan preset modes",
|
||||||
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",
|
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user