Skip to content

Commit

Permalink
Alarm control panel code dialog (#1437)
Browse files Browse the repository at this point in the history
* Use code dialog to arm and disarm instead of the keypad

* Set the right minimal version for HA
  • Loading branch information
piitaya authored May 30, 2024
1 parent 12e1c8b commit 04db261
Show file tree
Hide file tree
Showing 33 changed files with 309 additions and 413 deletions.
1 change: 0 additions & 1 deletion docs/cards/alarm-control-panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ All the options are available in the lovelace editor but you can use `yaml` if y
| `secondary_info` | `name` `state` `last-changed` `last-updated` `none` | `state` | Info to show as secondary info |
| `icon_type` | `icon` `entity-picture` `none` | `icon` | Type of icon to display |
| `states` | list | `["armed_home", "armed_away"]` | List of arm states to display |
| `show_keypad` | boolean | `false` | Show the keypad |
| `tap_action` | action | `more-info` | Home assistant action to perform on tap |
| `hold_action` | action | `more-info` | Home assistant action to perform on hold |
| `double_tap_action` | action | `more-info` | Home assistant action to perform on double_tap |
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Mushroom",
"filename": "mushroom.js",
"homeassistant": "2023.7.0",
"homeassistant": "2024.3.0",
"render_readme": true
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { array, assign, boolean, object, optional } from "superstruct";
import { array, assign, boolean, deprecated, object, optional } from "superstruct";
import { LovelaceCardConfig } from "../../ha";
import { ActionsSharedConfig, actionsSharedConfigStruct } from "../../shared/config/actions-config";
import {
Expand All @@ -7,20 +7,24 @@ import {
} from "../../shared/config/appearance-config";
import { EntitySharedConfig, entitySharedConfigStruct } from "../../shared/config/entity-config";
import { lovelaceCardConfigStruct } from "../../shared/config/lovelace-card-config";
import { AlarmMode } from "../../ha/data/alarm_control_panel";

export type AlarmControlPanelCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
states?: string[];
show_keypad?: boolean;
states?: AlarmMode[];
};

export const alarmControlPanelCardCardConfigStruct = assign(
lovelaceCardConfigStruct,
assign(entitySharedConfigStruct, appearanceSharedConfigStruct, actionsSharedConfigStruct),
object({
states: optional(array()),
show_keypad: optional(boolean()),
show_keypad: deprecated(optional(boolean()), (_value, ctx) => {
console.warn(
`🍄 "${ctx.path}" option is deprecated and no longer available. Remove it from your YAML configuration or use the built-in Home Assistant alarm panel card if you want keypad.`
);
}),
})
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ const actions: UiAction[] = ["more-info", "navigate", "url", "call-service", "as

const states = ["armed_home", "armed_away", "armed_night", "armed_vacation", "armed_custom_bypass"];

const ALARM_CONTROL_PANEL_LABELS = ["show_keypad"];

const computeSchema = memoizeOne((localize: LocalizeFunc): HaFormSchema[] => [
{ name: "entity", selector: { entity: { domain: ALARM_CONTROl_PANEL_ENTITY_DOMAINS } } },
{ name: "name", selector: { text: {} } },
Expand All @@ -36,7 +34,6 @@ const computeSchema = memoizeOne((localize: LocalizeFunc): HaFormSchema[] => [
localize(`ui.card.alarm_control_panel.${state.replace("armed", "arm")}`),
]) as [string, string][],
},
{ name: "show_keypad", selector: { boolean: {} } },
...computeActionsFormSchema(actions),
]);

Expand Down Expand Up @@ -78,9 +75,6 @@ export class SwitchCardEditor extends MushroomBaseElement implements LovelaceCar
if (GENERIC_LABELS.includes(schema.name)) {
return customLocalize(`editor.card.generic.${schema.name}`);
}
if (ALARM_CONTROL_PANEL_LABELS.includes(schema.name)) {
return customLocalize(`editor.card.alarm_control_panel.${schema.name}`);
}
if (schema.name === "states") {
return this.hass!.localize(
"ui.panel.lovelace.editor.card.alarm-panel.available_states"
Expand Down
145 changes: 10 additions & 135 deletions src/cards/alarm-control-panel-card/alarm-control-panel-card.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, query } from "lit/decorators.js";
import { customElement } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { styleMap } from "lit/directives/style-map.js";
import {
Expand All @@ -12,8 +12,8 @@ import {
HomeAssistant,
LovelaceCard,
LovelaceCardEditor,
LovelaceLayoutOptions,
} from "../../ha";
import { ALARM_MODES, AlarmMode, setProtectedAlarmControlPanelMode } from "../../ha/data/alarm_control_panel";
import "../../shared/badge-icon";
import "../../shared/button";
import "../../shared/button-group";
Expand All @@ -25,22 +25,14 @@ import { computeAppearance } from "../../utils/appearance";
import { MushroomBaseCard } from "../../utils/base-card";
import { cardStyle } from "../../utils/card-styles";
import { registerCustomCard } from "../../utils/custom-cards";
import { alarmPanelIconAction } from "../../utils/icons/alarm-panel-icon";
import { computeEntityPicture } from "../../utils/info";
import { AlarmControlPanelCardConfig } from "./alarm-control-panel-card-config";
import {
ALARM_CONTROl_PANEL_CARD_EDITOR_NAME,
ALARM_CONTROl_PANEL_CARD_NAME,
ALARM_CONTROl_PANEL_ENTITY_DOMAINS,
} from "./const";
import {
getStateColor,
getStateService,
hasCode,
isActionsAvailable,
isDisarmed,
shouldPulse,
} from "./utils";
import { getStateColor, hasCode, isActionsAvailable, isDisarmed, shouldPulse } from "./utils";

registerCustomCard({
type: ALARM_CONTROl_PANEL_CARD_NAME,
Expand All @@ -49,14 +41,10 @@ registerCustomCard({
});

type ActionButtonType = {
state: string;
mode: AlarmMode;
disabled?: boolean;
};

type HaTextField = any;

const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];

/*
* Ref: https://github.com/home-assistant/frontend/blob/dev/src/panels/lovelace/cards/hui-alarm-panel-card.ts
* TODO: customize icon for modes (advanced YAML configuration)
Expand Down Expand Up @@ -88,68 +76,15 @@ export class AlarmControlPanelCard
return Boolean(this._config?.states?.length);
}

public getLayoutOptions(): LovelaceLayoutOptions {
const options = super.getLayoutOptions();
if (this._config?.show_keypad) {
delete options.grid_columns;
delete options.grid_rows;
}
return options;
}

@query("#alarmCode") private _input?: HaTextField;

setConfig(config: AlarmControlPanelCardConfig): void {
super.setConfig(config);
this.loadComponents();
}

protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (this.hass && changedProperties.has("hass")) {
this.loadComponents();
}
}

async loadComponents() {
const stateObj = this._stateObj;

if (stateObj && hasCode(stateObj)) {
void import("../../shared/form/mushroom-textfield");
}
}

private _onTap(e: MouseEvent, state: string): void {
const service = getStateService(state);
if (!service) return;
private _onTap(e: MouseEvent, mode: AlarmMode): void {
e.stopPropagation();
const code = this._input?.value || undefined;
this.hass.callService("alarm_control_panel", service, {
entity_id: this._config?.entity,
code,
});
if (this._input) {
this._input.value = "";
}
}

private _handlePadClick(e: MouseEvent): void {
const val = (e.currentTarget! as any).value;
if (this._input) {
this._input.value = val === "clear" ? "" : this._input!.value + val;
}
setProtectedAlarmControlPanelMode(this, this.hass!, this._stateObj!, mode);
}

private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!);
}

private get _hasCode(): boolean {
const stateObj = this._stateObj;
if (!stateObj) return false;
return hasCode(stateObj) && Boolean(this._config?.show_keypad);
}

protected render() {
if (!this.hass || !this._config || !this._config.entity) {
return nothing;
Expand All @@ -169,8 +104,8 @@ export class AlarmControlPanelCard
const actions: ActionButtonType[] =
this._config.states && this._config.states.length > 0
? isDisarmed(stateObj)
? this._config.states.map((state) => ({ state }))
: [{ state: "disarmed" }]
? this._config.states.map((state) => ({ mode: state }))
: [{ mode: "disarmed" }]
: [];

const isActionEnabled = isActionsAvailable(stateObj);
Expand Down Expand Up @@ -202,10 +137,10 @@ export class AlarmControlPanelCard
${actions.map(
(action) => html`
<mushroom-button
@click=${(e) => this._onTap(e, action.state)}
@click=${(e) => this._onTap(e, action.mode)}
.disabled=${!isActionEnabled}
>
<ha-icon .icon=${alarmPanelIconAction(action.state)}>
<ha-icon .icon=${ALARM_MODES[action.mode].icon}>
</ha-icon>
</mushroom-button>
`
Expand All @@ -214,44 +149,6 @@ export class AlarmControlPanelCard
`
: nothing}
</mushroom-card>
${!this._hasCode
? nothing
: html`
<mushroom-textfield
id="alarmCode"
.label=${this.hass.localize("ui.card.alarm_control_panel.code")}
type="password"
.inputmode=${stateObj.attributes.code_format === "number"
? "numeric"
: "text"}
></mushroom-textfield>
`}
${!(this._hasCode && stateObj.attributes.code_format === "number")
? nothing
: html`
<div id="keypad">
${BUTTONS.map((value) =>
value === ""
? html`<mwc-button disabled></mwc-button>`
: html`
<mwc-button
.value=${value}
@click=${this._handlePadClick}
outlined
class=${classMap({
numberkey: value !== "clear",
})}
>
${value === "clear"
? this.hass!.localize(
`ui.card.alarm_control_panel.clear_code`
)
: value}
</mwc-button>
`
)}
</div>
`}
</ha-card>
`;
}
Expand Down Expand Up @@ -287,31 +184,9 @@ export class AlarmControlPanelCard
mushroom-state-item {
cursor: pointer;
}
.alert {
--main-color: var(--warning-color);
}
mushroom-shape-icon.pulse {
--shape-animation: 1s ease 0s infinite normal none running pulse;
}
mushroom-textfield {
display: block;
margin: 8px auto;
max-width: 150px;
text-align: center;
}
#keypad {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: auto;
width: 100%;
max-width: 300px;
}
#keypad mwc-button {
padding: 8px;
width: 30%;
box-sizing: border-box;
}
`,
];
}
Expand Down
Loading

0 comments on commit 04db261

Please sign in to comment.