mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Add more info alarm control panel (#15893)
* Add more info alarm control panel * Improve buttons sizes * Add triggered, arming and pending state * Add keypad * Improve alarm code dialog * Fix code condition * Clean code * Fix mode selection with code * Use nothing
This commit is contained in:
parent
3a700aebcc
commit
48c74c8660
@ -85,6 +85,7 @@ export class HaControlButton extends LitElement {
|
||||
--control-button-background-opacity: 0.2;
|
||||
--control-button-border-radius: 10px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--primary-text-color);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@ -110,6 +111,8 @@ export class HaControlButton extends LitElement {
|
||||
--mdc-ripple-color: var(--control-button-background-color);
|
||||
/* For safari border-radius overflow */
|
||||
z-index: 0;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
.button::before {
|
||||
content: "";
|
||||
|
@ -100,10 +100,11 @@ export class HaControlSelect extends LitElement {
|
||||
|
||||
private _handleKeydown(ev: KeyboardEvent) {
|
||||
if (!this.options || this._activeIndex == null || this.disabled) return;
|
||||
const value = this.options[this._activeIndex].value;
|
||||
switch (ev.key) {
|
||||
case " ":
|
||||
this.value = this.options[this._activeIndex].value;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
this.value = value;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
break;
|
||||
case "ArrowUp":
|
||||
case "ArrowLeft":
|
||||
@ -132,7 +133,7 @@ export class HaControlSelect extends LitElement {
|
||||
if (this.disabled) return;
|
||||
const value = (ev.target as any).value;
|
||||
this.value = value;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _handleOptionMouseDown(ev: MouseEvent) {
|
||||
|
@ -1,3 +1,7 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const FORMAT_TEXT = "text";
|
||||
@ -12,6 +16,16 @@ export const enum AlarmControlPanelEntityFeature {
|
||||
ARM_VACATION = 32,
|
||||
}
|
||||
|
||||
interface AlarmControlPanelEntityAttributes extends HassEntityAttributeBase {
|
||||
code_format?: "text" | "number";
|
||||
changed_by?: string | null;
|
||||
code_arm_required?: boolean;
|
||||
}
|
||||
|
||||
export interface AlarmControlPanelEntity extends HassEntityBase {
|
||||
attributes: AlarmControlPanelEntityAttributes;
|
||||
}
|
||||
|
||||
export const callAlarmAction = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
|
@ -0,0 +1,248 @@
|
||||
import "@material/web/button/filled-button";
|
||||
import "@material/web/iconbutton/filled-icon-button";
|
||||
import { mdiCheck, mdiClose } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-control-button";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HassDialog } from "../../../make-dialog-manager";
|
||||
import { EnterCodeDialogParams } from "./show-enter-code-dialog";
|
||||
|
||||
const BUTTONS = [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"0",
|
||||
"clear",
|
||||
"submit",
|
||||
];
|
||||
|
||||
@customElement("dialog-enter-code")
|
||||
export class DialogEnterCode
|
||||
extends LitElement
|
||||
implements HassDialog<EnterCodeDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _dialogParams?: EnterCodeDialogParams;
|
||||
|
||||
@query("#code") private _input?: HaTextField;
|
||||
|
||||
@state() private _showClearButton = false;
|
||||
|
||||
public async showDialog(dialogParams: EnterCodeDialogParams): Promise<void> {
|
||||
this._dialogParams = dialogParams;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
if (this._dialogParams?.cancel) {
|
||||
this._dialogParams.cancel();
|
||||
}
|
||||
this._dialogParams = undefined;
|
||||
this._showClearButton = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _submit(): void {
|
||||
this._dialogParams?.submit?.(this._input?.value ?? "");
|
||||
this._dialogParams = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _numberClick(e: MouseEvent): void {
|
||||
const val = (e.currentTarget! as any).value;
|
||||
this._input!.value = this._input!.value + val;
|
||||
this._showClearButton = true;
|
||||
}
|
||||
|
||||
private _clear(): void {
|
||||
this._input!.value = "";
|
||||
this._showClearButton = false;
|
||||
}
|
||||
|
||||
private _inputValueChange(e) {
|
||||
const val = (e.currentTarget! as any).value;
|
||||
this._showClearButton = !!val;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._dialogParams || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const isText = this._dialogParams.codeFormat === "text";
|
||||
|
||||
if (isText) {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
defaultAction="ignore"
|
||||
.heading=${this._dialogParams.title ??
|
||||
this.hass.localize("ui.dialogs.enter_code.title")}
|
||||
>
|
||||
<ha-textfield
|
||||
class="input"
|
||||
dialogInitialFocus
|
||||
id="code"
|
||||
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
|
||||
type="password"
|
||||
input-mode="text"
|
||||
></ha-textfield>
|
||||
<ha-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
${this._dialogParams.cancelText ??
|
||||
this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button @click=${this._submit} slot="primaryAction">
|
||||
${this._dialogParams.submitText ??
|
||||
this.hass.localize("ui.common.submit")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._dialogParams.title ?? "Enter code"
|
||||
)}
|
||||
@closed=${this.closeDialog}
|
||||
hideActions
|
||||
>
|
||||
<div class="container">
|
||||
<ha-textfield
|
||||
@input=${this._inputValueChange}
|
||||
id="code"
|
||||
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
|
||||
type="password"
|
||||
input-mode="numeric"
|
||||
></ha-textfield>
|
||||
<div class="keypad">
|
||||
${BUTTONS.map((value) =>
|
||||
value === ""
|
||||
? html`<span></span>`
|
||||
: value === "clear"
|
||||
? html`
|
||||
<ha-control-button
|
||||
@click=${this._clear}
|
||||
class="clear"
|
||||
.disabled=${!this._showClearButton}
|
||||
.label=${this.hass!.localize("ui.common.clear")}
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: value === "submit"
|
||||
? html`
|
||||
<ha-control-button
|
||||
@click=${this._submit}
|
||||
class="submit"
|
||||
.label=${this._dialogParams!.submitText ??
|
||||
this.hass!.localize("ui.common.submit")}
|
||||
>
|
||||
<ha-svg-icon path=${mdiCheck}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: html`
|
||||
<ha-control-button
|
||||
.value=${value}
|
||||
@click=${this._numberClick}
|
||||
.label=${value}
|
||||
>
|
||||
${value}
|
||||
</ha-control-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-heading-ink-color: var(--primary-text-color);
|
||||
--mdc-dialog-content-ink-color: var(--primary-text-color);
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
}
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.keypad {
|
||||
--keypad-columns: 3;
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--keypad-columns), auto);
|
||||
grid-auto-rows: auto;
|
||||
grid-gap: 24px;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
.clear {
|
||||
grid-row-start: 4;
|
||||
grid-column-start: 0;
|
||||
}
|
||||
@media all and (max-height: 450px) {
|
||||
.keypad {
|
||||
--keypad-columns: 6;
|
||||
}
|
||||
.clear {
|
||||
grid-row-start: 1;
|
||||
grid-column-start: 6;
|
||||
}
|
||||
}
|
||||
|
||||
ha-control-button {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
--control-button-border-radius: 28px;
|
||||
--mdc-icon-size: 24px;
|
||||
font-size: 24px;
|
||||
}
|
||||
.submit {
|
||||
--control-button-background-color: var(--green-color);
|
||||
--control-button-icon-color: var(--green-color);
|
||||
}
|
||||
.clear {
|
||||
--control-button-background-color: var(--red-color);
|
||||
--control-button-icon-color: var(--red-color);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.buttons {
|
||||
margin-top: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-enter-code": DialogEnterCode;
|
||||
}
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
import {
|
||||
mdiAirplane,
|
||||
mdiHome,
|
||||
mdiLock,
|
||||
mdiMoonWaningCrescent,
|
||||
mdiShield,
|
||||
mdiShieldOff,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeAttributeNameDisplay } from "../../../../common/entity/compute_attribute_display";
|
||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||
import "../../../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../../../components/ha-control-select";
|
||||
import "../../../../components/ha-control-slider";
|
||||
import {
|
||||
AlarmControlPanelEntity,
|
||||
AlarmControlPanelEntityFeature,
|
||||
} from "../../../../data/alarm_control_panel";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { showEnterCodeDialogDialog } from "./show-enter-code-dialog";
|
||||
|
||||
type AlarmMode =
|
||||
| "away"
|
||||
| "home"
|
||||
| "night"
|
||||
| "vacation"
|
||||
| "custom_bypass"
|
||||
| "disarmed";
|
||||
|
||||
type AlarmConfig = {
|
||||
service: string;
|
||||
feature?: AlarmControlPanelEntityFeature;
|
||||
state: string;
|
||||
path: string;
|
||||
};
|
||||
const ALARM_MODES: Record<AlarmMode, AlarmConfig> = {
|
||||
away: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_AWAY,
|
||||
service: "alarm_arm_away",
|
||||
state: "armed_away",
|
||||
path: mdiLock,
|
||||
},
|
||||
home: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_HOME,
|
||||
service: "alarm_arm_home",
|
||||
state: "armed_home",
|
||||
path: mdiHome,
|
||||
},
|
||||
custom_bypass: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
|
||||
service: "alarm_arm_custom_bypass",
|
||||
state: "armed_custom_bypass",
|
||||
path: mdiShield,
|
||||
},
|
||||
night: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_NIGHT,
|
||||
service: "alarm_arm_night",
|
||||
state: "armed_night",
|
||||
path: mdiMoonWaningCrescent,
|
||||
},
|
||||
vacation: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_VACATION,
|
||||
service: "alarm_arm_vacation",
|
||||
state: "armed_vacation",
|
||||
path: mdiAirplane,
|
||||
},
|
||||
disarmed: {
|
||||
service: "alarm_disarm",
|
||||
state: "disarmed",
|
||||
path: mdiShieldOff,
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-more-info-alarm_control_panel-modes")
|
||||
export class HaMoreInfoAlarmControlPanelModes extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: AlarmControlPanelEntity;
|
||||
|
||||
@state() _currentMode?: AlarmMode;
|
||||
|
||||
private _modes = memoizeOne((stateObj: AlarmControlPanelEntity) => {
|
||||
const modes = Object.keys(ALARM_MODES) as AlarmMode[];
|
||||
return modes.filter((mode) => {
|
||||
const feature = ALARM_MODES[mode as AlarmMode].feature;
|
||||
return !feature || supportsFeature(stateObj, feature);
|
||||
});
|
||||
});
|
||||
|
||||
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
|
||||
super.updated(changedProp);
|
||||
if (changedProp.has("stateObj") && this.stateObj) {
|
||||
const oldStateObj = changedProp.get("stateObj") as HassEntity | undefined;
|
||||
|
||||
if (!oldStateObj || this.stateObj.state !== oldStateObj.state) {
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getCurrentMode(stateObj: AlarmControlPanelEntity) {
|
||||
return this._modes(stateObj).find(
|
||||
(mode) => ALARM_MODES[mode].state === stateObj.state
|
||||
);
|
||||
}
|
||||
|
||||
private async _valueChanged(ev: CustomEvent) {
|
||||
const mode = (ev.detail as any).value as AlarmMode;
|
||||
|
||||
this._currentMode = mode;
|
||||
|
||||
const { state: modeState, service } = ALARM_MODES[mode];
|
||||
|
||||
if (modeState === this.stateObj.state) return;
|
||||
|
||||
let code: string | undefined;
|
||||
|
||||
if (
|
||||
(mode !== "disarmed" &&
|
||||
this.stateObj.attributes.code_arm_required &&
|
||||
this.stateObj.attributes.code_format) ||
|
||||
(mode === "disarmed" && this.stateObj.attributes.code_format)
|
||||
) {
|
||||
const disarm = mode === "disarmed";
|
||||
|
||||
const response = await showEnterCodeDialogDialog(this, {
|
||||
codeFormat: this.stateObj.attributes.code_format,
|
||||
title: this.hass.localize(
|
||||
`ui.dialogs.more_info_control.alarm_control_panel.${
|
||||
disarm ? "disarm_title" : "arm_title"
|
||||
}`
|
||||
),
|
||||
submitText: this.hass.localize(
|
||||
`ui.dialogs.more_info_control.alarm_control_panel.${
|
||||
disarm ? "disarm_action" : "arm_action"
|
||||
}`
|
||||
),
|
||||
});
|
||||
if (!response) {
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
return;
|
||||
}
|
||||
code = response;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.hass.callService("alarm_control_panel", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
code,
|
||||
});
|
||||
} catch (_err) {
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const color = stateColorCss(this.stateObj);
|
||||
|
||||
const modes = this._modes(this.stateObj);
|
||||
|
||||
const options = modes.map<ControlSelectOption>((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.localize(
|
||||
`ui.dialogs.more_info_control.alarm_control_panel.modes.${mode}`
|
||||
),
|
||||
path: ALARM_MODES[mode].path,
|
||||
}));
|
||||
|
||||
return html`
|
||||
<ha-control-select
|
||||
vertical
|
||||
.options=${options}
|
||||
.value=${this._currentMode}
|
||||
@value-changed=${this._valueChanged}
|
||||
no-optimistic-update
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.entities,
|
||||
"percentage"
|
||||
)}
|
||||
style=${styleMap({
|
||||
"--control-select-color": color,
|
||||
"--modes-count": modes.length.toString(),
|
||||
})}
|
||||
>
|
||||
</ha-control-select>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-control-select {
|
||||
height: 45vh;
|
||||
max-height: max(320px, var(--modes-count, 1) * 80px);
|
||||
min-height: max(200px, var(--modes-count, 1) * 80px);
|
||||
--control-select-thickness: 100px;
|
||||
--control-select-border-radius: 24px;
|
||||
--control-select-color: var(--primary-color);
|
||||
--control-select-background: var(--disabled-color);
|
||||
--control-select-background-opacity: 0.2;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-alarm_control_panel-modes": HaMoreInfoAlarmControlPanelModes;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
export interface EnterCodeDialogParams {
|
||||
codeFormat: "text" | "number";
|
||||
submitText?: string;
|
||||
cancelText?: string;
|
||||
title?: string;
|
||||
submit?: (code?: string) => void;
|
||||
cancel?: () => void;
|
||||
}
|
||||
|
||||
export const showEnterCodeDialogDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: EnterCodeDialogParams
|
||||
) =>
|
||||
new Promise<string | null>((resolve) => {
|
||||
const origCancel = dialogParams.cancel;
|
||||
const origSubmit = dialogParams.submit;
|
||||
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-enter-code",
|
||||
dialogImport: () => import("./dialog-enter-code"),
|
||||
dialogParams: {
|
||||
...dialogParams,
|
||||
cancel: () => {
|
||||
resolve(null);
|
||||
if (origCancel) {
|
||||
origCancel();
|
||||
}
|
||||
},
|
||||
submit: (code: string) => {
|
||||
resolve(code);
|
||||
if (origSubmit) {
|
||||
origSubmit(code);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
@ -17,6 +17,7 @@ export const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
export const EDITABLE_DOMAINS_WITH_UNIQUE_ID = ["script"];
|
||||
/** Domains with with new more info design. */
|
||||
export const DOMAINS_WITH_NEW_MORE_INFO = [
|
||||
"alarm_control_panel",
|
||||
"cover",
|
||||
"fan",
|
||||
"input_boolean",
|
||||
|
@ -1,70 +1,46 @@
|
||||
import "@material/mwc-button";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, PropertyValues, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
import {
|
||||
AlarmControlPanelEntityFeature,
|
||||
callAlarmAction,
|
||||
FORMAT_NUMBER,
|
||||
} from "../../../data/alarm_control_panel";
|
||||
import "@material/web/button/outlined-button";
|
||||
import { mdiShieldOff } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
|
||||
const DISARM_ACTIONS = ["disarm"];
|
||||
import "../components/alarm_control_panel/ha-more-info-alarm_control_panel-modes";
|
||||
import { showEnterCodeDialogDialog } from "../components/alarm_control_panel/show-enter-code-dialog";
|
||||
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||
import "../components/ha-more-info-state-header";
|
||||
|
||||
@customElement("more-info-alarm_control_panel")
|
||||
export class MoreInfoAlarmControlPanel extends LitElement {
|
||||
class MoreInfoAlarmControlPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
@property({ attribute: false }) public stateObj?: AlarmControlPanelEntity;
|
||||
|
||||
@state() private _armActions: string[] = [];
|
||||
private async _disarm() {
|
||||
let code: string | undefined;
|
||||
|
||||
@query("#alarmCode") private _input?: HaTextField;
|
||||
|
||||
public willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.stateObj || !changedProps.has("stateObj")) {
|
||||
return;
|
||||
if (this.stateObj!.attributes.code_format) {
|
||||
const response = await showEnterCodeDialogDialog(this, {
|
||||
codeFormat: this.stateObj!.attributes.code_format,
|
||||
title: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.alarm_control_panel.disarm_title"
|
||||
),
|
||||
submitText: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.alarm_control_panel.disarm_action"
|
||||
),
|
||||
});
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
code = response;
|
||||
}
|
||||
|
||||
this._armActions = [];
|
||||
if (
|
||||
supportsFeature(this.stateObj, AlarmControlPanelEntityFeature.ARM_HOME)
|
||||
) {
|
||||
this._armActions.push("arm_home");
|
||||
}
|
||||
if (
|
||||
supportsFeature(this.stateObj, AlarmControlPanelEntityFeature.ARM_AWAY)
|
||||
) {
|
||||
this._armActions.push("arm_away");
|
||||
}
|
||||
if (
|
||||
supportsFeature(this.stateObj, AlarmControlPanelEntityFeature.ARM_NIGHT)
|
||||
) {
|
||||
this._armActions.push("arm_night");
|
||||
}
|
||||
if (
|
||||
supportsFeature(
|
||||
this.stateObj,
|
||||
AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
)
|
||||
) {
|
||||
this._armActions.push("arm_custom_bypass");
|
||||
}
|
||||
if (
|
||||
supportsFeature(
|
||||
this.stateObj,
|
||||
AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
)
|
||||
) {
|
||||
this._armActions.push("arm_vacation");
|
||||
}
|
||||
this.hass.callService("alarm_control_panel", "alarm_disarm", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
code,
|
||||
});
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@ -72,134 +48,104 @@ export class MoreInfoAlarmControlPanel extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const color = stateColorCss(this.stateObj);
|
||||
const style = {
|
||||
"--icon-color": color,
|
||||
};
|
||||
return html`
|
||||
${!this.stateObj.attributes.code_format
|
||||
? ""
|
||||
: html`
|
||||
<div class="center">
|
||||
<ha-textfield
|
||||
id="alarmCode"
|
||||
.label=${this.hass.localize("ui.card.alarm_control_panel.code")}
|
||||
type="password"
|
||||
.inputMode=${this.stateObj.attributes.code_format ===
|
||||
FORMAT_NUMBER
|
||||
? "numeric"
|
||||
: "text"}
|
||||
></ha-textfield>
|
||||
</div>
|
||||
`}
|
||||
${this.stateObj.attributes.code_format !== FORMAT_NUMBER
|
||||
? ""
|
||||
: 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>
|
||||
`}
|
||||
<div class="actions">
|
||||
${(this.stateObj.state === "disarmed"
|
||||
? this._armActions
|
||||
: DISARM_ACTIONS
|
||||
).map(
|
||||
(stateAction) => html`
|
||||
<mwc-button
|
||||
.action=${stateAction}
|
||||
@click=${this._handleActionClick}
|
||||
outlined
|
||||
>
|
||||
${this.hass!.localize(
|
||||
`ui.card.alarm_control_panel.${stateAction}`
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
)}
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls" style=${styleMap(style)}>
|
||||
${["triggered", "arming", "pending"].includes(this.stateObj.state)
|
||||
? html`
|
||||
<div class="status">
|
||||
<span></span>
|
||||
<div class="icon">
|
||||
<ha-svg-icon
|
||||
.path=${domainIcon("alarm_control_panel", this.stateObj)}
|
||||
></ha-svg-icon>
|
||||
</div>
|
||||
<md-outlined-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.alarm_control_panel.disarm_action"
|
||||
)}
|
||||
@click=${this._disarm}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiShieldOff}></ha-svg-icon>
|
||||
</md-outlined-button>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-more-info-alarm_control_panel-modes
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</ha-more-info-alarm_control_panel-modes>
|
||||
`}
|
||||
</div>
|
||||
<span></span>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handlePadClick(e: MouseEvent): void {
|
||||
const val = (e.currentTarget! as any).value;
|
||||
this._input!.value = val === "clear" ? "" : this._input!.value + val;
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
moreInfoControlStyle,
|
||||
css`
|
||||
:host {
|
||||
--icon-color: var(--primary-color);
|
||||
}
|
||||
md-outlined-button {
|
||||
--ha-icon-display: block;
|
||||
--md-sys-color-primary: var(--primary-text-color);
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.status .icon {
|
||||
position: relative;
|
||||
--mdc-icon-size: 80px;
|
||||
animation: pulse 1s infinite;
|
||||
color: var(--icon-color);
|
||||
border-radius: 50%;
|
||||
width: 144px;
|
||||
height: 144px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.status .icon::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: var(--icon-color);
|
||||
transition: background-color 180ms ease-in-out;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.status md-outlined-button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private _handleActionClick(e: MouseEvent): void {
|
||||
const input = this._input;
|
||||
callAlarmAction(
|
||||
this.hass!,
|
||||
this.stateObj!.entity_id,
|
||||
(e.currentTarget! as any).action,
|
||||
input?.value || undefined
|
||||
);
|
||||
if (input) {
|
||||
input.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin: 8px;
|
||||
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;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.actions mwc-button {
|
||||
margin: 0 4px 4px;
|
||||
}
|
||||
|
||||
mwc-button#disarm {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
mwc-button.numberkey {
|
||||
--mdc-typography-button-font-size: var(--keypad-font-size, 0.875rem);
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -342,7 +342,8 @@ export const haStyleDialog = css`
|
||||
--ha-dialog-border-radius: 0px;
|
||||
}
|
||||
}
|
||||
mwc-button.warning {
|
||||
mwc-button.warning,
|
||||
ha-button.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
.error {
|
||||
|
@ -924,6 +924,20 @@
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
}
|
||||
},
|
||||
"alarm_control_panel": {
|
||||
"modes": {
|
||||
"away": "Away",
|
||||
"home": "Home",
|
||||
"night": "Night",
|
||||
"vacation": "Vacation",
|
||||
"custom_bypass": "Custom",
|
||||
"disarmed": "Disarmed"
|
||||
},
|
||||
"disarm_title": "Disarm",
|
||||
"disarm_action": "Disarm",
|
||||
"arm_title": "Arm",
|
||||
"arm_action": "Arm"
|
||||
}
|
||||
},
|
||||
"entity_registry": {
|
||||
@ -1283,6 +1297,10 @@
|
||||
"release_items": "This includes beta releases for:",
|
||||
"view_documentation": "View documentation",
|
||||
"join": "Join"
|
||||
},
|
||||
"enter_code": {
|
||||
"title": "Enter code",
|
||||
"input_label": "Code"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user