Add Select entity (#9422)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Franck Nijhof 2021-06-19 13:26:19 +02:00 committed by GitHub
parent cecb66451c
commit c1d571de42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 316 additions and 0 deletions

View File

@ -42,6 +42,7 @@ export const FIXED_DOMAIN_ICONS = {
remote: "hass:remote",
scene: "hass:palette",
script: "hass:script-text",
select: "hass:format-list-bulleted",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
@ -83,6 +84,7 @@ export const DOMAINS_WITH_CARD = [
"number",
"scene",
"script",
"select",
"timer",
"vacuum",
"water_heater",
@ -121,6 +123,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_text",
"number",
"scene",
"select",
];
/** Domains that should have the history hidden in the more info dialog. */

25
src/data/select.ts Normal file
View File

@ -0,0 +1,25 @@
import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
interface SelectEntityAttributes extends HassEntityAttributeBase {
options: string[];
}
export interface SelectEntity extends HassEntityBase {
attributes: SelectEntityAttributes;
}
export const setSelectOption = (
hass: HomeAssistant,
entity: string,
option: string
) =>
hass.callService(
"select",
"select_option",
{ option },
{ entity_id: entity }
);

View File

@ -37,6 +37,7 @@ const LAZY_LOAD_TYPES = {
"input-text-entity": () => import("../entity-rows/hui-input-text-entity-row"),
"lock-entity": () => import("../entity-rows/hui-lock-entity-row"),
"number-entity": () => import("../entity-rows/hui-number-entity-row"),
"select-entity": () => import("../entity-rows/hui-select-entity-row"),
"timer-entity": () => import("../entity-rows/hui-timer-entity-row"),
conditional: () => import("../special-rows/hui-conditional-row"),
"weather-entity": () => import("../entity-rows/hui-weather-entity-row"),
@ -68,6 +69,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
remote: "toggle",
scene: "scene",
script: "script",
select: "select",
sensor: "sensor",
timer: "timer",
switch: "toggle",

View File

@ -0,0 +1,186 @@
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/entity/state-badge";
import "../../../components/ha-paper-dropdown-menu";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import { SelectEntity, setSelectOption } from "../../../data/select";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { LovelaceRow } from "./types";
@customElement("hui-select-entity-row")
class HuiSelectEntityRow extends LitElement implements LovelaceRow {
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: EntitiesCardEntityConfig;
public setConfig(config: EntitiesCardEntityConfig): void {
if (!config || !config.entity) {
throw new Error("Entity must be specified");
}
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
const stateObj = this.hass.states[this._config.entity] as
| SelectEntity
| undefined;
if (!stateObj) {
return html`
<hui-warning>
${createEntityNotFoundWarning(this.hass, this._config.entity)}
</hui-warning>
`;
}
const pointer =
(this._config.tap_action && this._config.tap_action.action !== "none") ||
(this._config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
return html`
<state-badge
.stateObj=${stateObj}
.overrideIcon=${this._config.icon}
.overrideImage=${this._config.image}
class=${classMap({
pointer,
})}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex=${ifDefined(pointer ? "0" : undefined)}
></state-badge>
<ha-paper-dropdown-menu
.label=${this._config.name || computeStateName(stateObj)}
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
@iron-select=${this._selectedChanged}
@click=${stopPropagation}
>
<paper-listbox slot="dropdown-content">
${stateObj.attributes.options
? stateObj.attributes.options.map(
(option) =>
html`
<paper-item .option=${option}
>${(stateObj.attributes.device_class &&
this.hass!.localize(
`component.select.state.${stateObj.attributes.device_class}.${option}`
)) ||
this.hass!.localize(
`component.select.state._.${option}`
) ||
option}</paper-item
>
`
)
: ""}
</paper-listbox>
</ha-paper-dropdown-menu>
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this.hass || !this._config) {
return;
}
const stateObj = this.hass.states[this._config.entity] as
| SelectEntity
| undefined;
if (!stateObj) {
return;
}
// Update selected after rendering the items or else it won't work in Firefox
if (stateObj.attributes.options) {
this.shadowRoot!.querySelector(
"paper-listbox"
)!.selected = stateObj.attributes.options.indexOf(stateObj.state);
}
}
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!);
}
static get styles(): CSSResultGroup {
return css`
:host {
display: flex;
align-items: center;
}
ha-paper-dropdown-menu {
margin-left: 16px;
flex: 1;
}
paper-item {
cursor: pointer;
min-width: 200px;
}
.pointer {
cursor: pointer;
}
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`;
}
private _selectedChanged(ev): void {
const stateObj = this.hass!.states[this._config!.entity];
const option = ev.target.selectedItem.option;
if (option === stateObj.state) {
return;
}
forwardHaptic("light");
setSelectOption(this.hass!, stateObj.entity_id, option);
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-select-entity-row": HuiSelectEntityRow;
}
}

View File

@ -14,6 +14,7 @@ import "./state-card-media_player";
import "./state-card-number";
import "./state-card-scene";
import "./state-card-script";
import "./state-card-select";
import "./state-card-timer";
import "./state-card-toggle";
import "./state-card-vacuum";

View File

@ -0,0 +1,99 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeStateName } from "../common/entity/compute_state_name";
import "../components/entity/state-badge";
import { SelectEntity, setSelectOption } from "../data/select";
import type { HomeAssistant } from "../types";
@customElement("state-card-select")
class StateCardSelect extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: SelectEntity;
protected render(): TemplateResult {
return html`
<state-badge .stateObj=${this.stateObj}></state-badge>
<paper-dropdown-menu-light
.label=${computeStateName(this.stateObj)}
@iron-select=${this._selectedOptionChanged}
@click=${stopPropagation}
>
<paper-listbox slot="dropdown-content">
${this.stateObj.attributes.options.map(
(option) =>
html`
<paper-item .option=${option}
>${(this.stateObj.attributes.device_class &&
this.hass.localize(
`component.select.state.${this.stateObj.attributes.device_class}.${option}`
)) ||
this.hass.localize(`component.select.state._.${option}`) ||
option}</paper-item
>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!changedProps.has("stateObj")) {
return;
}
// Update selected after rendering the items or else it won't work in Firefox
this.shadowRoot!.querySelector(
"paper-listbox"
)!.selected = this.stateObj.attributes.options.indexOf(this.stateObj.state);
}
private _selectedOptionChanged(ev) {
const option = ev.target.selectedItem.option;
if (option === this.stateObj.state) {
return;
}
setSelectOption(this.hass, this.stateObj.entity_id, option);
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
}
state-badge {
float: left;
margin-top: 10px;
}
paper-dropdown-menu-light {
display: block;
margin-left: 53px;
}
paper-item {
cursor: pointer;
min-width: 200px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-select": StateCardSelect;
}
}