Add lock features for tile card (#20539)
* Added lock features for tile card * Change success label
This commit is contained in:
parent
5fc950f09f
commit
d9b71e754d
|
@ -2,6 +2,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
|||
import { customElement, query } from "lit/decorators";
|
||||
import { CoverEntityFeature } from "../../../../src/data/cover";
|
||||
import { LightColorMode } from "../../../../src/data/light";
|
||||
import { LockEntityFeature } from "../../../../src/data/lock";
|
||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
|
@ -20,6 +21,11 @@ const ENTITIES = [
|
|||
getEntity("light", "unavailable", "unavailable", {
|
||||
friendly_name: "Unavailable entity",
|
||||
}),
|
||||
getEntity("lock", "front_door", "locked", {
|
||||
friendly_name: "Front Door Lock",
|
||||
device_class: "lock",
|
||||
supported_features: LockEntityFeature.OPEN,
|
||||
}),
|
||||
getEntity("climate", "thermostat", "heat", {
|
||||
current_temperature: 73,
|
||||
min_temp: 45,
|
||||
|
@ -138,6 +144,24 @@ const CONFIGS = [
|
|||
- type: "color-temp"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Lock commands feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: lock.front_door
|
||||
features:
|
||||
- type: "lock-commands"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Lock open door feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: lock.front_door
|
||||
features:
|
||||
- type: "lock-open-door"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Vacuum commands feature",
|
||||
config: `
|
||||
|
|
|
@ -5,9 +5,7 @@ import {
|
|||
import { getExtendedEntityRegistryEntry } from "./entity_registry";
|
||||
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const FORMAT_TEXT = "text";
|
||||
export const FORMAT_NUMBER = "number";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export const enum LockEntityFeature {
|
||||
OPEN = 1,
|
||||
|
@ -24,6 +22,33 @@ export interface LockEntity extends HassEntityBase {
|
|||
|
||||
type ProtectedLockService = "lock" | "unlock" | "open";
|
||||
|
||||
export function isLocked(stateObj: LockEntity) {
|
||||
return stateObj.state === "locked";
|
||||
}
|
||||
|
||||
export function isUnlocking(stateObj: LockEntity) {
|
||||
return stateObj.state === "unlocking";
|
||||
}
|
||||
|
||||
export function isLocking(stateObj: LockEntity) {
|
||||
return stateObj.state === "locking";
|
||||
}
|
||||
|
||||
export function isJammed(stateObj: LockEntity) {
|
||||
return stateObj.state === "jammed";
|
||||
}
|
||||
|
||||
export function isAvailable(stateObj: LockEntity) {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return (
|
||||
assumedState ||
|
||||
(!isLocking(stateObj) && !isUnlocking(stateObj) && !isJammed(stateObj))
|
||||
);
|
||||
}
|
||||
|
||||
export const callProtectedLockService = async (
|
||||
element: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
|
|
|
@ -9,11 +9,12 @@ import "../../../components/ha-control-button";
|
|||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-outlined-icon-button";
|
||||
import "../../../components/ha-state-icon";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
LockEntity,
|
||||
LockEntityFeature,
|
||||
callProtectedLockService,
|
||||
isAvailable,
|
||||
isJammed,
|
||||
} from "../../../data/lock";
|
||||
import "../../../state-control/lock/ha-state-control-lock-toggle";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
@ -85,15 +86,13 @@ class MoreInfoLock extends LitElement {
|
|||
"--state-color": color,
|
||||
};
|
||||
|
||||
const isJammed = this.stateObj.state === "jammed";
|
||||
|
||||
return html`
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls" style=${styleMap(style)}>
|
||||
${this.stateObj.state === "jammed"
|
||||
${isJammed(this.stateObj)
|
||||
? html`
|
||||
<div class="status">
|
||||
<span></span>
|
||||
|
@ -125,7 +124,7 @@ class MoreInfoLock extends LitElement {
|
|||
`
|
||||
: html`
|
||||
<ha-control-button
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.disabled=${!isAvailable(this.stateObj)}
|
||||
class="open-button ${this._buttonState}"
|
||||
@click=${this._open}
|
||||
>
|
||||
|
@ -139,7 +138,7 @@ class MoreInfoLock extends LitElement {
|
|||
: nothing}
|
||||
</div>
|
||||
<div>
|
||||
${isJammed
|
||||
${isJammed(this.stateObj)
|
||||
? html`
|
||||
<ha-control-button-group class="jammed">
|
||||
<ha-control-button @click=${this._unlock}>
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
import { mdiLock, mdiLockOpen } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import {
|
||||
callProtectedLockService,
|
||||
isAvailable,
|
||||
isLocking,
|
||||
isUnlocking,
|
||||
isLocked,
|
||||
} from "../../../data/lock";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardFeature } from "../types";
|
||||
import { LockCommandsCardFeatureConfig } from "./types";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
|
||||
export const supportsLockCommandsCardFeature = (stateObj: HassEntity) => {
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return domain === "lock";
|
||||
};
|
||||
|
||||
@customElement("hui-lock-commands-card-feature")
|
||||
class HuiLockCommandsCardFeature
|
||||
extends LitElement
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
@state() private _config?: LockCommandsCardFeatureConfig;
|
||||
|
||||
static getStubConfig(): LockCommandsCardFeatureConfig {
|
||||
return {
|
||||
type: "lock-commands",
|
||||
};
|
||||
}
|
||||
|
||||
public setConfig(config: LockCommandsCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _onTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
const service = ev.target.dataset.service;
|
||||
if (!this.hass || !this.stateObj || !service) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
callProtectedLockService(this, this.hass, this.stateObj, service);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.stateObj ||
|
||||
!supportsLockCommandsCardFeature(this.stateObj)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-control-button-group>
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.lock.lock")}
|
||||
.disabled=${!isAvailable(this.stateObj) || isLocked(this.stateObj)}
|
||||
@click=${this._onTap}
|
||||
data-service="lock"
|
||||
class=${classMap({
|
||||
pulse: isLocking(this.stateObj) || isUnlocking(this.stateObj),
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiLock}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.lock.unlock")}
|
||||
.disabled=${!isAvailable(this.stateObj) || !isLocked(this.stateObj)}
|
||||
@click=${this._onTap}
|
||||
data-service="unlock"
|
||||
class=${classMap({
|
||||
pulse: isLocking(this.stateObj) || isUnlocking(this.stateObj),
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiLockOpen}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.pulse {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
ha-control-button-group {
|
||||
margin: 0 12px 12px 12px;
|
||||
--control-button-group-spacing: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-lock-commands-card-feature": HuiLockCommandsCardFeature;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
import { mdiCheck } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import {
|
||||
LockEntityFeature,
|
||||
callProtectedLockService,
|
||||
isAvailable,
|
||||
} from "../../../data/lock";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardFeature } from "../types";
|
||||
import { LockOpenDoorCardFeatureConfig } from "./types";
|
||||
|
||||
export const supportsLockOpenDoorCardFeature = (stateObj: HassEntity) => {
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return domain === "lock" && supportsFeature(stateObj, LockEntityFeature.OPEN);
|
||||
};
|
||||
|
||||
const CONFIRM_TIMEOUT_SECOND = 5;
|
||||
const OPENED_TIMEOUT_SECOND = 3;
|
||||
|
||||
type ButtonState = "normal" | "confirm" | "success";
|
||||
|
||||
@customElement("hui-lock-open-door-card-feature")
|
||||
class HuiLockOpenDoorCardFeature
|
||||
extends LitElement
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
@state() public _buttonState: ButtonState = "normal";
|
||||
|
||||
@state() private _config?: LockOpenDoorCardFeatureConfig;
|
||||
|
||||
private _buttonTimeout?: number;
|
||||
|
||||
static getStubConfig(): LockOpenDoorCardFeatureConfig {
|
||||
return {
|
||||
type: "lock-open-door",
|
||||
};
|
||||
}
|
||||
|
||||
public setConfig(config: LockOpenDoorCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _setButtonState(buttonState: ButtonState, timeoutSecond?: number) {
|
||||
clearTimeout(this._buttonTimeout);
|
||||
this._buttonState = buttonState;
|
||||
if (timeoutSecond) {
|
||||
this._buttonTimeout = window.setTimeout(() => {
|
||||
this._buttonState = "normal";
|
||||
}, timeoutSecond * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private async _open() {
|
||||
if (this._buttonState !== "confirm") {
|
||||
this._setButtonState("confirm", CONFIRM_TIMEOUT_SECOND);
|
||||
return;
|
||||
}
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
callProtectedLockService(this, this.hass, this.stateObj!, "open");
|
||||
|
||||
this._setButtonState("success", OPENED_TIMEOUT_SECOND);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.stateObj ||
|
||||
!supportsLockOpenDoorCardFeature(this.stateObj)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this._buttonState === "success"
|
||||
? html`
|
||||
<div class="buttons">
|
||||
<p class="open-success">
|
||||
<ha-svg-icon path=${mdiCheck}></ha-svg-icon>
|
||||
${this.hass.localize("ui.card.lock.open_door_success")}
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-control-button-group>
|
||||
<ha-control-button
|
||||
.disabled=${!isAvailable(this.stateObj)}
|
||||
class="open-button ${this._buttonState}"
|
||||
@click=${this._open}
|
||||
>
|
||||
${this._buttonState === "confirm"
|
||||
? this.hass.localize("ui.card.lock.open_door_confirm")
|
||||
: this.hass.localize("ui.card.lock.open_door")}
|
||||
</ha-control-button>
|
||||
</ha-control-button-group>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-control-button {
|
||||
font-size: 14px;
|
||||
}
|
||||
ha-control-button-group {
|
||||
margin: 0 12px 12px 12px;
|
||||
--control-button-group-spacing: 12px;
|
||||
}
|
||||
.open-button {
|
||||
width: 130px;
|
||||
}
|
||||
.open-button.confirm {
|
||||
--control-button-background-color: var(--warning-color);
|
||||
}
|
||||
.open-success {
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
color: var(--success-color);
|
||||
}
|
||||
ha-control-button-group + ha-attributes:not([empty]) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-lock-open-door-card-feature": HuiLockOpenDoorCardFeature;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,14 @@ export interface LightColorTempCardFeatureConfig {
|
|||
type: "light-color-temp";
|
||||
}
|
||||
|
||||
export interface LockCommandsCardFeatureConfig {
|
||||
type: "lock-commands";
|
||||
}
|
||||
|
||||
export interface LockOpenDoorCardFeatureConfig {
|
||||
type: "lock-open-door";
|
||||
}
|
||||
|
||||
export interface FanPresetModesCardFeatureConfig {
|
||||
type: "fan-preset-modes";
|
||||
style?: "dropdown" | "icons";
|
||||
|
@ -143,6 +151,8 @@ export type LovelaceCardFeatureConfig =
|
|||
| LawnMowerCommandsCardFeatureConfig
|
||||
| LightBrightnessCardFeatureConfig
|
||||
| LightColorTempCardFeatureConfig
|
||||
| LockCommandsCardFeatureConfig
|
||||
| LockOpenDoorCardFeatureConfig
|
||||
| NumericInputCardFeatureConfig
|
||||
| SelectOptionsCardFeatureConfig
|
||||
| TargetHumidityCardFeatureConfig
|
||||
|
|
|
@ -14,6 +14,8 @@ import "../card-features/hui-humidifier-toggle-card-feature";
|
|||
import "../card-features/hui-lawn-mower-commands-card-feature";
|
||||
import "../card-features/hui-light-brightness-card-feature";
|
||||
import "../card-features/hui-light-color-temp-card-feature";
|
||||
import "../card-features/hui-lock-commands-card-feature";
|
||||
import "../card-features/hui-lock-open-door-card-feature";
|
||||
import "../card-features/hui-numeric-input-card-feature";
|
||||
import "../card-features/hui-select-options-card-feature";
|
||||
import "../card-features/hui-target-temperature-card-feature";
|
||||
|
@ -45,6 +47,8 @@ const TYPES: Set<LovelaceCardFeatureConfig["type"]> = new Set([
|
|||
"lawn-mower-commands",
|
||||
"light-brightness",
|
||||
"light-color-temp",
|
||||
"lock-commands",
|
||||
"lock-open-door",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
"target-humidity",
|
||||
|
|
|
@ -35,6 +35,8 @@ import { supportsHumidifierToggleCardFeature } from "../../card-features/hui-hum
|
|||
import { supportsLawnMowerCommandCardFeature } from "../../card-features/hui-lawn-mower-commands-card-feature";
|
||||
import { supportsLightBrightnessCardFeature } from "../../card-features/hui-light-brightness-card-feature";
|
||||
import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature";
|
||||
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
|
||||
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
|
||||
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
|
||||
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
|
||||
import { supportsTargetHumidityCardFeature } from "../../card-features/hui-target-humidity-card-feature";
|
||||
|
@ -56,8 +58,8 @@ const UI_FEATURE_TYPES = [
|
|||
"climate-preset-modes",
|
||||
"cover-open-close",
|
||||
"cover-position",
|
||||
"cover-tilt-position",
|
||||
"cover-tilt",
|
||||
"cover-tilt-position",
|
||||
"fan-preset-modes",
|
||||
"fan-speed",
|
||||
"humidifier-modes",
|
||||
|
@ -65,6 +67,8 @@ const UI_FEATURE_TYPES = [
|
|||
"lawn-mower-commands",
|
||||
"light-brightness",
|
||||
"light-color-temp",
|
||||
"lock-commands",
|
||||
"lock-open-door",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
"target-humidity",
|
||||
|
@ -111,6 +115,8 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
|||
"lawn-mower-commands": supportsLawnMowerCommandCardFeature,
|
||||
"light-brightness": supportsLightBrightnessCardFeature,
|
||||
"light-color-temp": supportsLightColorTempCardFeature,
|
||||
"lock-commands": supportsLockCommandsCardFeature,
|
||||
"lock-open-door": supportsLockOpenDoorCardFeature,
|
||||
"numeric-input": supportsNumericInputCardFeature,
|
||||
"select-options": supportsSelectOptionsCardFeature,
|
||||
"target-humidity": supportsTargetHumidityCardFeature,
|
||||
|
|
|
@ -5948,6 +5948,12 @@
|
|||
"light-color-temp": {
|
||||
"label": "Light color temperature"
|
||||
},
|
||||
"lock-commands": {
|
||||
"label": "Lock commands"
|
||||
},
|
||||
"lock-open-door": {
|
||||
"label": "Lock open door"
|
||||
},
|
||||
"vacuum-commands": {
|
||||
"label": "Vacuum commands",
|
||||
"commands": "Commands",
|
||||
|
|
Loading…
Reference in New Issue