Updates to alarm panel card configuration (#17598)

* Updates to alarm panel card configuration

* changes from feedback
This commit is contained in:
karwosts 2023-08-30 02:42:51 -07:00 committed by GitHub
parent ae9fcebfd5
commit 034ce56da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 27 deletions

View File

@ -9,6 +9,7 @@ import {
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { HassEntity } from "home-assistant-js-websocket";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon"; import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
@ -20,16 +21,48 @@ import type { HaTextField } from "../../../components/ha-textfield";
import { import {
callAlarmAction, callAlarmAction,
FORMAT_NUMBER, FORMAT_NUMBER,
ALARM_MODES,
AlarmMode,
} from "../../../data/alarm_control_panel"; } from "../../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities"; import { findEntities } from "../common/find-entities";
import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createEntityNotFoundWarning } from "../components/hui-warning";
import type { LovelaceCard } from "../types"; import type { LovelaceCard } from "../types";
import { AlarmPanelCardConfig } from "./types"; import { AlarmPanelCardConfig, AlarmPanelCardConfigState } from "./types";
import { supportsFeature } from "../../../common/entity/supports-feature";
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"]; const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
export const DEFAULT_STATES = [
"arm_home",
"arm_away",
] as AlarmPanelCardConfigState[];
export const ALARM_MODE_STATE_MAP: Record<
AlarmPanelCardConfigState,
AlarmMode
> = {
arm_home: "armed_home",
arm_away: "armed_away",
arm_night: "armed_night",
arm_vacation: "armed_vacation",
arm_custom_bypass: "armed_custom_bypass",
};
export const filterSupportedAlarmStates = (
stateObj: HassEntity | undefined,
states: AlarmPanelCardConfigState[]
): AlarmPanelCardConfigState[] =>
states.filter(
(s) =>
stateObj &&
supportsFeature(
stateObj,
ALARM_MODES[ALARM_MODE_STATE_MAP[s]].feature || 0
)
);
@customElement("hui-alarm-panel-card") @customElement("hui-alarm-panel-card")
class HuiAlarmPanelCard extends LitElement implements LovelaceCard { class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
public static async getConfigElement() { public static async getConfigElement() {
@ -52,10 +85,13 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
includeDomains includeDomains
); );
const entity = foundEntities[0] || "";
const stateObj = hass.states[entity];
return { return {
type: "alarm-panel", type: "alarm-panel",
states: ["arm_home", "arm_away"], states: filterSupportedAlarmStates(stateObj, DEFAULT_STATES),
entity: foundEntities[0] || "", entity,
}; };
} }
@ -86,11 +122,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
throw new Error("Invalid configuration"); throw new Error("Invalid configuration");
} }
const defaults = { this._config = { ...config };
states: ["arm_away", "arm_home"] as const,
};
this._config = { ...defaults, ...config };
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -138,6 +170,9 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
return nothing; return nothing;
} }
const stateObj = this.hass.states[this._config.entity]; const stateObj = this.hass.states[this._config.entity];
const states =
this._config.states ||
filterSupportedAlarmStates(stateObj, DEFAULT_STATES);
if (!stateObj) { if (!stateObj) {
return html` return html`
@ -170,7 +205,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
</h1> </h1>
<div id="armActions" class="actions"> <div id="armActions" class="actions">
${(stateObj.state === "disarmed" ${(stateObj.state === "disarmed"
? this._config.states! ? states
: (["disarm"] as const) : (["disarm"] as const)
).map( ).map(
(stateAction) => html` (stateAction) => html`

View File

@ -14,10 +14,17 @@ import { HaDurationData } from "../../../components/ha-duration-input";
import { LovelaceTileFeatureConfig } from "../tile-features/types"; import { LovelaceTileFeatureConfig } from "../tile-features/types";
import { ForecastType } from "../../../data/weather"; import { ForecastType } from "../../../data/weather";
export type AlarmPanelCardConfigState =
| "arm_away"
| "arm_home"
| "arm_night"
| "arm_vacation"
| "arm_custom_bypass";
export interface AlarmPanelCardConfig extends LovelaceCardConfig { export interface AlarmPanelCardConfig extends LovelaceCardConfig {
entity: string; entity: string;
name?: string; name?: string;
states?: readonly (keyof TranslationDict["ui"]["card"]["alarm_control_panel"])[]; states?: AlarmPanelCardConfigState[];
theme?: string; theme?: string;
} }

View File

@ -2,14 +2,25 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { array, assert, assign, object, optional, string } from "superstruct"; import { array, assert, assign, object, optional, string } from "superstruct";
import { HassEntity } from "home-assistant-js-websocket";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../common/translations/localize"; import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { AlarmPanelCardConfig } from "../../cards/types"; import type {
AlarmPanelCardConfig,
AlarmPanelCardConfigState,
} from "../../cards/types";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import {
DEFAULT_STATES,
ALARM_MODE_STATE_MAP,
filterSupportedAlarmStates,
} from "../../cards/hui-alarm-panel-card";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import { ALARM_MODES } from "../../../../data/alarm_control_panel";
const cardConfigStruct = assign( const cardConfigStruct = assign(
baseLovelaceCardConfig, baseLovelaceCardConfig,
@ -21,13 +32,7 @@ const cardConfigStruct = assign(
}) })
); );
const states = [ const states = Object.keys(ALARM_MODE_STATE_MAP) as AlarmPanelCardConfigState[];
"arm_home",
"arm_away",
"arm_night",
"arm_vacation",
"arm_custom_bypass",
] as const;
@customElement("hui-alarm-panel-card-editor") @customElement("hui-alarm-panel-card-editor")
export class HuiAlarmPanelCardEditor export class HuiAlarmPanelCardEditor
@ -44,7 +49,11 @@ export class HuiAlarmPanelCardEditor
} }
private _schema = memoizeOne( private _schema = memoizeOne(
(localize: LocalizeFunc) => (
localize: LocalizeFunc,
stateObj: HassEntity | undefined,
config_states: AlarmPanelCardConfigState[]
) =>
[ [
{ {
name: "entity", name: "entity",
@ -60,12 +69,24 @@ export class HuiAlarmPanelCardEditor
], ],
}, },
{ {
type: "multi_select",
name: "states", name: "states",
options: states.map((s) => [ selector: {
s, select: {
localize(`ui.card.alarm_control_panel.${s}`), multiple: true,
]) as [string, string][], mode: "list",
options: states.map((s) => ({
value: s,
label: localize(`ui.card.alarm_control_panel.${s}`),
disabled:
!config_states.includes(s) &&
(!stateObj ||
!supportsFeature(
stateObj,
ALARM_MODES[ALARM_MODE_STATE_MAP[s]].feature || 0
)),
})),
},
},
}, },
] as const ] as const
); );
@ -75,11 +96,18 @@ export class HuiAlarmPanelCardEditor
return nothing; return nothing;
} }
const stateObj = this.hass.states[this._config.entity];
const defaultFilteredStates = filterSupportedAlarmStates(
stateObj,
DEFAULT_STATES
);
const config = { states: defaultFilteredStates, ...this._config };
return html` return html`
<ha-form <ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${this._config} .data=${config}
.schema=${this._schema(this.hass.localize)} .schema=${this._schema(this.hass.localize, stateObj, config.states)}
.computeLabel=${this._computeLabelCallback} .computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-form> ></ha-form>
@ -87,7 +115,26 @@ export class HuiAlarmPanelCardEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const newConfig = ev.detail.value;
// Sort states in a consistent order
if (newConfig.states) {
const sortStates = states.filter((s) => newConfig.states.includes(s));
newConfig.states = sortStates;
}
// When changing entities, clear any states that the new entity does not support
if (newConfig.states && newConfig.entity !== this._config?.entity) {
const newStateObj = this.hass?.states[newConfig.entity];
if (newStateObj) {
newConfig.states = filterSupportedAlarmStates(
newStateObj,
newConfig.states
);
}
}
fireEvent(this, "config-changed", { config: newConfig });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (