Skip to content

Commit

Permalink
Add user condition to conditional card (#18265)
Browse files Browse the repository at this point in the history
* Add user condition to conditional card

* Refactor user fetch

* Add validate ui

* Use ha-check-list-item
  • Loading branch information
piitaya authored Oct 23, 2023
1 parent 4354ad3 commit eedb42b
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 12 deletions.
3 changes: 2 additions & 1 deletion src/panels/lovelace/common/icon-condition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { mdiResponsive, mdiStateMachine } from "@mdi/js";
import { mdiAccount, mdiResponsive, mdiStateMachine } from "@mdi/js";
import { Condition } from "./validate-condition";

export const ICON_CONDITION: Record<Condition["condition"], string> = {
state: mdiStateMachine,
screen: mdiResponsive,
user: mdiAccount,
};
44 changes: 33 additions & 11 deletions src/panels/lovelace/common/validate-condition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types";

export type Condition = StateCondition | ScreenCondition;
export type Condition = StateCondition | ScreenCondition | UserCondition;

export type LegacyCondition = {
entity?: string;
Expand All @@ -21,6 +21,11 @@ export type ScreenCondition = {
media_query?: string;
};

export type UserCondition = {
condition: "user";
users?: string[];
};

function checkStateCondition(
condition: StateCondition | LegacyCondition,
hass: HomeAssistant
Expand All @@ -35,30 +40,38 @@ function checkStateCondition(
: state !== condition.state_not;
}

function checkScreenCondition(
condition: ScreenCondition,
_hass: HomeAssistant
) {
function checkScreenCondition(condition: ScreenCondition, _: HomeAssistant) {
return condition.media_query
? matchMedia(condition.media_query).matches
: false;
}

function checkUserCondition(condition: UserCondition, hass: HomeAssistant) {
return condition.users && hass.user?.id
? condition.users.includes(hass.user.id)
: false;
}

export function checkConditionsMet(
conditions: (Condition | LegacyCondition)[],
hass: HomeAssistant
): boolean {
return conditions.every((c) => {
if ("condition" in c) {
if (c.condition === "screen") {
return checkScreenCondition(c, hass);
switch (c.condition) {
case "screen":
return checkScreenCondition(c, hass);
case "user":
return checkUserCondition(c, hass);
default:
return checkStateCondition(c, hass);
}
}
return checkStateCondition(c, hass);
});
}

function valideStateCondition(condition: StateCondition | LegacyCondition) {
function validateStateCondition(condition: StateCondition | LegacyCondition) {
return (
condition.entity != null &&
(condition.state != null || condition.state_not != null)
Expand All @@ -69,15 +82,24 @@ function validateScreenCondition(condition: ScreenCondition) {
return condition.media_query != null;
}

function validateUserCondition(condition: UserCondition) {
return condition.users != null;
}

export function validateConditionalConfig(
conditions: (Condition | LegacyCondition)[]
): boolean {
return conditions.every((c) => {
if ("condition" in c) {
if (c.condition === "screen") {
return validateScreenCondition(c);
switch (c.condition) {
case "screen":
return validateScreenCondition(c);
case "user":
return validateUserCondition(c);
default:
return validateStateCondition(c);
}
}
return valideStateCondition(c);
return validateStateCondition(c);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import "./ha-card-condition-editor";
import { LovelaceConditionEditorConstructor } from "./types";
import "./types/ha-card-condition-screen";
import "./types/ha-card-condition-state";
import "./types/ha-card-condition-user";

const UI_CONDITION = [
"state",
"screen",
"user",
] as const satisfies readonly Condition["condition"][];

@customElement("ha-card-conditions-editor")
Expand Down
122 changes: 122 additions & 0 deletions src/panels/lovelace/editor/conditions/types/ha-card-condition-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import "@material/mwc-list";
import { LitElement, PropertyValues, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { array, assert, literal, object, string } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { stringCompare } from "../../../../../common/string/compare";
import "../../../../../components/ha-check-list-item";
import "../../../../../components/ha-switch";
import "../../../../../components/user/ha-user-badge";
import { User, fetchUsers } from "../../../../../data/user";
import type { HomeAssistant } from "../../../../../types";
import { UserCondition } from "../../../common/validate-condition";

const userConditionStruct = object({
condition: literal("user"),
users: array(string()),
});

@customElement("ha-card-condition-user")
export class HaCardConditionUser extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property({ attribute: false }) public condition!: UserCondition;

@property({ type: Boolean }) public disabled = false;

public static get defaultConfig(): UserCondition {
return { condition: "user", users: [] };
}

@state() private _users: User[] = [];

protected static validateUIConfig(condition: UserCondition) {
return assert(condition, userConditionStruct);
}

private _sortedUsers = memoizeOne((users: User[]) =>
users.sort((a, b) =>
stringCompare(a.name, b.name, this.hass.locale.language)
)
);

protected async firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchUsers();
}

private async _fetchUsers() {
const users = await fetchUsers(this.hass);
this._users = users.filter((user) => !user.system_generated);
}

protected render() {
const selectedUsers = this.condition.users ?? [];

return html`
<mwc-list>
${this._sortedUsers(this._users).map(
(user) => html`
<ha-check-list-item
graphic="avatar"
hasMeta
.userId=${user.id}
.selected=${selectedUsers.includes(user.id)}
@request-selected=${this._userChanged}
>
<ha-user-badge
slot="graphic"
.hass=${this.hass}
.user=${user}
></ha-user-badge>
<span>${user.name}</span>
</ha-check-list-item>
`
)}
</mwc-list>
`;
}

private _userChanged(ev) {
ev.stopPropagation();
const selectedUsers = this.condition.users ?? [];
const userId = ev.currentTarget.userId as string;
const checked = ev.detail.selected as boolean;

if (checked === selectedUsers.includes(userId)) {
return;
}

let users = selectedUsers;
if (checked) {
users = [...users, userId];
} else {
users = users.filter((user) => user !== userId);
}

const condition: UserCondition = {
...this.condition,
users,
};

fireEvent(this, "value-changed", { value: condition });
}

static get styles() {
return css`
:host {
display: block;
}
mwc-list {
--mdc-list-vertical-padding: 0;
}
`;
}
}

declare global {
interface HTMLElementTagNameMap {
"ha-card-condition-user": HaCardConditionUser;
}
}
3 changes: 3 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4774,6 +4774,9 @@
"state_equal": "State is equal to",
"state_not_equal": "State is not equal to",
"current_state": "current"
},
"user": {
"label": "User"
}
}
},
Expand Down

0 comments on commit eedb42b

Please sign in to comment.