Add state selector (#13411)

This commit is contained in:
Franck Nijhof 2022-08-19 15:20:13 +02:00 committed by GitHub
parent 196456d0c4
commit d2a19e04ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 278 additions and 3 deletions

View File

@ -136,6 +136,11 @@ const SCHEMAS: {
schema: [
{ name: "addon", selector: { addon: {} } },
{ name: "entity", selector: { entity: {} } },
{
name: "State",
selector: { state: { entity_id: "" } },
context: { filter_entity: "entity" },
},
{
name: "Attribute",
selector: { attribute: { entity_id: "" } },

View File

@ -115,6 +115,10 @@ const SCHEMAS: {
name: "One of each",
input: {
entity: { name: "Entity", selector: { entity: {} } },
state: {
name: "State",
selector: { state: { entity_id: "alarm_control_panel.alarm" } },
},
attribute: {
name: "Attribute",
selector: { attribute: { entity_id: "" } },

View File

@ -0,0 +1,89 @@
import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE_STATES } from "../../data/entity";
const FIXED_DOMAIN_STATES = {
alarm_control_panel: [
"armed_away",
"armed_custom_bypass",
"armed_home",
"armed_night",
"armed_vacation",
"arming",
"disarmed",
"disarming",
"pending",
"triggered",
],
automation: ["on", "off"],
binary_sensor: ["on", "off"],
button: [],
calendar: ["on", "off"],
camera: ["idle", "recording", "streaming"],
cover: ["closed", "closing", "open", "opening"],
device_tracker: ["home", "not_home"],
fan: ["on", "off"],
humidifier: ["on", "off"],
input_boolean: ["on", "off"],
input_button: [],
light: ["on", "off"],
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
media_player: ["idle", "off", "paused", "playing", "standby"],
person: ["home", "not_home"],
remote: ["on", "off"],
scene: [],
schedule: ["on", "off"],
script: ["on", "off"],
siren: ["on", "off"],
sun: ["above_horizon", "below_horizon"],
switch: ["on", "off"],
update: ["on", "off"],
vacuum: ["cleaning", "docked", "error", "idle", "paused", "returning"],
weather: [
"clear-night",
"cloudy",
"exceptional",
"fog",
"hail",
"lightning-rainy",
"lightning",
"partlycloudy",
"pouring",
"rainy",
"snowy-rainy",
"snowy",
"sunny",
"windy-variant",
"windy",
],
};
export const getStates = (state: HassEntity): string[] => {
const domain = computeStateDomain(state);
const result: string[] = [];
if (domain in FIXED_DOMAIN_STATES) {
result.push(...FIXED_DOMAIN_STATES[domain]);
} else {
// If not fixed, we at least know the current state
result.push(state.state);
}
// Dynamic values based on the entities
switch (domain) {
case "climate":
result.push(...state.attributes.hvac_modes);
break;
case "input_select":
case "select":
result.push(...state.attributes.options);
break;
case "water_heater":
result.push(...state.attributes.operation_list);
break;
}
// All entities can have unavailable states
result.push(...UNAVAILABLE_STATES);
return [...new Set(result)];
};

View File

@ -0,0 +1,107 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { computeStateDisplay } from "../../common/entity/compute_state_display";
import { PolymerChangedEvent } from "../../polymer-types";
import { getStates } from "../../common/entity/get_states";
import { HomeAssistant } from "../../types";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@customElement("ha-entity-state-picker")
class HaEntityStatePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId?: string;
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean, attribute: "allow-custom-value" })
public allowCustomValue;
@property() public label?: string;
@property() public value?: string;
@property() public helper?: string;
@property({ type: Boolean }) private _opened = false;
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
protected shouldUpdate(changedProps: PropertyValues) {
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items =
this.entityId && state
? getStates(state).map((key) => ({
value: key,
label: computeStateDisplay(
this.hass.localize,
state,
this.hass.locale,
key
),
}))
: [];
}
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-combo-box
.hass=${this.hass}
.value=${this.value
? this.entityId && this.hass.states[this.entityId]
? computeStateDisplay(
this.hass.localize,
this.hass.states[this.entityId],
this.hass.locale,
this.value
)
: this.value
: ""}
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize("ui.components.entity.entity-state-picker.state")}
.disabled=${this.disabled || !this.entityId}
.required=${this.required}
.helper=${this.helper}
.allowCustomValue=${this.allowCustomValue}
item-value-path="value"
item-label-path="label"
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
</ha-combo-box>
`;
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
this.value = ev.detail.value;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-entity-state-picker": HaEntityStatePicker;
}
}

View File

@ -0,0 +1,49 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { StateSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../entity/ha-entity-state-picker";
@customElement("ha-selector-state")
export class HaSelectorState extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public selector!: StateSelector;
@property() public value?: any;
@property() public label?: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@property() public context?: {
filter_entity?: string;
};
protected render() {
return html`
<ha-entity-state-picker
.hass=${this.hass}
.entityId=${this.selector.state.entity_id ||
this.context?.filter_entity}
.value=${this.value}
.label=${this.label}
.helper=${this.helper}
.disabled=${this.disabled}
.required=${this.required}
allow-custom-value
></ha-entity-state-picker>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-state": HaSelectorState;
}
}

View File

@ -18,6 +18,7 @@ import "./ha-selector-file";
import "./ha-selector-number";
import "./ha-selector-object";
import "./ha-selector-select";
import "./ha-selector-state";
import "./ha-selector-target";
import "./ha-selector-template";
import "./ha-selector-text";

View File

@ -23,6 +23,7 @@ export type Selector =
| NumberSelector
| ObjectSelector
| SelectSelector
| StateSelector
| StringSelector
| TargetSelector
| TemplateSelector
@ -191,6 +192,12 @@ export interface SelectSelector {
};
}
export interface StateSelector {
state: {
entity_id?: string;
};
}
export interface StringSelector {
text: {
multiline?: boolean;

View File

@ -37,7 +37,7 @@ export class HaStateCondition extends LitElement implements ConditionElement {
name: "attribute",
selector: { attribute: { entity_id: entityId } },
},
{ name: "state", selector: { text: {} } },
{ name: "state", selector: { state: { entity_id: entityId } } },
{ name: "for", selector: { duration: {} } },
] as const
);

View File

@ -56,8 +56,18 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
name: "attribute",
selector: { attribute: { entity_id: entityId } },
},
{ name: "from", selector: { text: {} } },
{ name: "to", selector: { text: {} } },
{
name: "from",
selector: {
state: { entity_id: entityId ? entityId[0] : undefined },
},
},
{
name: "to",
selector: {
state: { entity_id: entityId ? entityId[0] : undefined },
},
},
{ name: "for", selector: { duration: {} } },
] as const
);

View File

@ -400,6 +400,9 @@
"entity-attribute-picker": {
"attribute": "Attribute",
"show_attributes": "Show attributes"
},
"entity-state-picker": {
"state": "State"
}
},
"target-picker": {