Improve open and opening state for lock (#20808)

* Add open and opening color

* Split isAvailable into multiple function

* Use done wording
This commit is contained in:
Paul Bottein 2024-05-21 09:19:36 +02:00 committed by GitHub
parent 79abcca3b3
commit 4cc5d2d04b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 62 additions and 57 deletions

View File

@ -26,6 +26,10 @@ export function isLocked(stateObj: LockEntity) {
return stateObj.state === "locked"; return stateObj.state === "locked";
} }
export function isUnlocked(stateObj: LockEntity) {
return stateObj.state === "unlocked";
}
export function isUnlocking(stateObj: LockEntity) { export function isUnlocking(stateObj: LockEntity) {
return stateObj.state === "unlocking"; return stateObj.state === "unlocking";
} }
@ -38,15 +42,40 @@ export function isJammed(stateObj: LockEntity) {
return stateObj.state === "jammed"; return stateObj.state === "jammed";
} }
export function isAvailable(stateObj: LockEntity) { export function isOpen(stateObj: LockEntity) {
return stateObj.state === "open";
}
export function isOpening(stateObj: LockEntity) {
return stateObj.state === "opening";
}
export function isWaiting(stateObj: LockEntity) {
return ["opening", "unlocking", "locking"].includes(stateObj.state);
}
export function canOpen(stateObj: LockEntity) {
if (stateObj.state === UNAVAILABLE) { if (stateObj.state === UNAVAILABLE) {
return false; return false;
} }
const assumedState = stateObj.attributes.assumed_state === true; const assumedState = stateObj.attributes.assumed_state === true;
return ( return assumedState || (!isOpen(stateObj) && !isWaiting(stateObj));
assumedState || }
(!isLocking(stateObj) && !isUnlocking(stateObj) && !isJammed(stateObj))
); export function canLock(stateObj: LockEntity) {
if (stateObj.state === UNAVAILABLE) {
return false;
}
const assumedState = stateObj.attributes.assumed_state === true;
return assumedState || (!isLocked(stateObj) && !isWaiting(stateObj));
}
export function canUnlock(stateObj: LockEntity) {
if (stateObj.state === UNAVAILABLE) {
return false;
}
const assumedState = stateObj.attributes.assumed_state === true;
return assumedState || (!isUnlocked(stateObj) && !isWaiting(stateObj));
} }
export const callProtectedLockService = async ( export const callProtectedLockService = async (

View File

@ -13,7 +13,7 @@ import {
LockEntity, LockEntity,
LockEntityFeature, LockEntityFeature,
callProtectedLockService, callProtectedLockService,
isAvailable, canOpen,
isJammed, isJammed,
} from "../../../data/lock"; } from "../../../data/lock";
import "../../../state-control/lock/ha-state-control-lock-toggle"; import "../../../state-control/lock/ha-state-control-lock-toggle";
@ -22,9 +22,9 @@ import "../components/ha-more-info-state-header";
import { moreInfoControlStyle } from "../components/more-info-control-style"; import { moreInfoControlStyle } from "../components/more-info-control-style";
const CONFIRM_TIMEOUT_SECOND = 5; const CONFIRM_TIMEOUT_SECOND = 5;
const OPENED_TIMEOUT_SECOND = 3; const DONE_TIMEOUT_SECOND = 2;
type ButtonState = "normal" | "confirm" | "success"; type ButtonState = "normal" | "confirm" | "done";
@customElement("more-info-lock") @customElement("more-info-lock")
class MoreInfoLock extends LitElement { class MoreInfoLock extends LitElement {
@ -54,7 +54,7 @@ class MoreInfoLock extends LitElement {
callProtectedLockService(this, this.hass, this.stateObj!, "open"); callProtectedLockService(this, this.hass, this.stateObj!, "open");
this._setButtonState("success", OPENED_TIMEOUT_SECOND); this._setButtonState("done", DONE_TIMEOUT_SECOND);
} }
private _resetButtonState() { private _resetButtonState() {
@ -115,16 +115,16 @@ class MoreInfoLock extends LitElement {
${supportsOpen ${supportsOpen
? html` ? html`
<div class="buttons"> <div class="buttons">
${this._buttonState === "success" ${this._buttonState === "done"
? html` ? html`
<p class="open-success"> <p class="open-done">
<ha-svg-icon path=${mdiCheck}></ha-svg-icon> <ha-svg-icon path=${mdiCheck}></ha-svg-icon>
${this.hass.localize("ui.card.lock.open_door_success")} ${this.hass.localize("ui.card.lock.open_door_done")}
</p> </p>
` `
: html` : html`
<ha-control-button <ha-control-button
.disabled=${!isAvailable(this.stateObj)} .disabled=${!canOpen(this.stateObj)}
class="open-button ${this._buttonState}" class="open-button ${this._buttonState}"
@click=${this._open} @click=${this._open}
> >
@ -175,7 +175,7 @@ class MoreInfoLock extends LitElement {
.open-button.confirm { .open-button.confirm {
--control-button-background-color: var(--warning-color); --control-button-background-color: var(--warning-color);
} }
.open-success { .open-done {
line-height: 60px; line-height: 60px;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,23 +1,20 @@
import { mdiLock, mdiLockOpenVariant } from "@mdi/js"; import { mdiLock, mdiLockOpenVariant } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-control-button"; import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group"; import "../../../components/ha-control-button-group";
import { forwardHaptic } from "../../../data/haptics";
import { import {
callProtectedLockService, callProtectedLockService,
isAvailable, canLock,
isLocking, canUnlock,
isUnlocking,
isLocked,
} from "../../../data/lock"; } from "../../../data/lock";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature } from "../types"; import { LovelaceCardFeature } from "../types";
import { LockCommandsCardFeatureConfig } from "./types"; import { LockCommandsCardFeatureConfig } from "./types";
import { forwardHaptic } from "../../../data/haptics";
export const supportsLockCommandsCardFeature = (stateObj: HassEntity) => { export const supportsLockCommandsCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id); const domain = computeDomain(stateObj.entity_id);
@ -72,23 +69,17 @@ class HuiLockCommandsCardFeature
<ha-control-button-group> <ha-control-button-group>
<ha-control-button <ha-control-button
.label=${this.hass.localize("ui.card.lock.lock")} .label=${this.hass.localize("ui.card.lock.lock")}
.disabled=${!isAvailable(this.stateObj) || isLocked(this.stateObj)} .disabled=${!canLock(this.stateObj)}
@click=${this._onTap} @click=${this._onTap}
data-service="lock" data-service="lock"
class=${classMap({
pulse: isLocking(this.stateObj) || isUnlocking(this.stateObj),
})}
> >
<ha-svg-icon .path=${mdiLock}></ha-svg-icon> <ha-svg-icon .path=${mdiLock}></ha-svg-icon>
</ha-control-button> </ha-control-button>
<ha-control-button <ha-control-button
.label=${this.hass.localize("ui.card.lock.unlock")} .label=${this.hass.localize("ui.card.lock.unlock")}
.disabled=${!isAvailable(this.stateObj) || !isLocked(this.stateObj)} .disabled=${!canUnlock(this.stateObj)}
@click=${this._onTap} @click=${this._onTap}
data-service="unlock" data-service="unlock"
class=${classMap({
pulse: isLocking(this.stateObj) || isUnlocking(this.stateObj),
})}
> >
<ha-svg-icon .path=${mdiLockOpenVariant}></ha-svg-icon> <ha-svg-icon .path=${mdiLockOpenVariant}></ha-svg-icon>
</ha-control-button> </ha-control-button>
@ -98,20 +89,6 @@ class HuiLockCommandsCardFeature
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.pulse {
animation: pulse 1s infinite;
}
ha-control-button-group { ha-control-button-group {
margin: 0 12px 12px 12px; margin: 0 12px 12px 12px;
--control-button-group-spacing: 12px; --control-button-group-spacing: 12px;

View File

@ -8,9 +8,9 @@ import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-control-button"; import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group"; import "../../../components/ha-control-button-group";
import { import {
LockEntityFeature,
callProtectedLockService, callProtectedLockService,
isAvailable, canOpen,
LockEntityFeature,
} from "../../../data/lock"; } from "../../../data/lock";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature } from "../types"; import { LovelaceCardFeature } from "../types";
@ -22,9 +22,9 @@ export const supportsLockOpenDoorCardFeature = (stateObj: HassEntity) => {
}; };
const CONFIRM_TIMEOUT_SECOND = 5; const CONFIRM_TIMEOUT_SECOND = 5;
const OPENED_TIMEOUT_SECOND = 3; const DONE_TIMEOUT_SECOND = 2;
type ButtonState = "normal" | "confirm" | "success"; type ButtonState = "normal" | "confirm" | "done";
@customElement("hui-lock-open-door-card-feature") @customElement("hui-lock-open-door-card-feature")
class HuiLockOpenDoorCardFeature class HuiLockOpenDoorCardFeature
@ -74,7 +74,7 @@ class HuiLockOpenDoorCardFeature
} }
callProtectedLockService(this, this.hass, this.stateObj!, "open"); callProtectedLockService(this, this.hass, this.stateObj!, "open");
this._setButtonState("success", OPENED_TIMEOUT_SECOND); this._setButtonState("done", DONE_TIMEOUT_SECOND);
} }
protected render() { protected render() {
@ -88,17 +88,17 @@ class HuiLockOpenDoorCardFeature
} }
return html` return html`
${this._buttonState === "success" ${this._buttonState === "done"
? html` ? html`
<p class="open-success"> <p class="open-done">
<ha-svg-icon path=${mdiCheck}></ha-svg-icon> <ha-svg-icon path=${mdiCheck}></ha-svg-icon>
${this.hass.localize("ui.card.lock.open_door_success")} ${this.hass.localize("ui.card.lock.open_door_done")}
</p> </p>
` `
: html` : html`
<ha-control-button-group> <ha-control-button-group>
<ha-control-button <ha-control-button
.disabled=${!isAvailable(this.stateObj)} .disabled=${!canOpen(this.stateObj)}
class="open-button ${this._buttonState}" class="open-button ${this._buttonState}"
@click=${this._open} @click=${this._open}
> >
@ -126,7 +126,7 @@ class HuiLockOpenDoorCardFeature
.open-button.confirm { .open-button.confirm {
--control-button-background-color: var(--warning-color); --control-button-background-color: var(--warning-color);
} }
.open-success { .open-done {
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 14px;
display: flex; display: flex;
@ -140,9 +140,6 @@ class HuiLockOpenDoorCardFeature
height: 40px; height: 40px;
text-align: center; text-align: center;
} }
ha-control-button-group + ha-attributes:not([empty]) {
margin-top: 16px;
}
`; `;
} }
} }

View File

@ -165,6 +165,8 @@ const mainStyles = css`
--state-lock-locked-color: var(--green-color); --state-lock-locked-color: var(--green-color);
--state-lock-pending-color: var(--orange-color); --state-lock-pending-color: var(--orange-color);
--state-lock-unlocked-color: var(--red-color); --state-lock-unlocked-color: var(--red-color);
--state-lock-opening-color: var(--orange-color);
--state-lock-open-color: var(--red-color);
--state-media_player-active-color: var(--light-blue-color); --state-media_player-active-color: var(--light-blue-color);
--state-person-active-color: var(--blue-color); --state-person-active-color: var(--blue-color);
--state-person-home-color: var(--green-color); --state-person-home-color: var(--green-color);

View File

@ -190,7 +190,7 @@
"open": "Open", "open": "Open",
"open_door": "Open door", "open_door": "Open door",
"open_door_confirm": "Really open?", "open_door_confirm": "Really open?",
"open_door_success": "Door open" "open_door_done": "Done"
}, },
"media_player": { "media_player": {
"source": "Source", "source": "Source",