Add Text entity (#14447)

This commit is contained in:
Franck Nijhof 2022-11-24 21:23:50 +01:00 committed by GitHub
parent 0aa2c9044a
commit a9d44fcb61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 241 additions and 30 deletions

View File

@ -138,7 +138,7 @@ if (!window.cardTools) {
return cardTools.createThing("row", config); return cardTools.createThing("row", config);
const domain = config.entity.split(".", 1)[0]; const domain = config.entity.split(".", 1)[0];
Object.assign(config, { type: DEFAULT_ROWS[domain] || "text" }); Object.assign(config, { type: DEFAULT_ROWS[domain] || "simple" });
return cardTools.createThing("entity-row", config); return cardTools.createThing("entity-row", config);
}; };

View File

@ -98,6 +98,9 @@ const ENTITIES = [
minimum: 0, minimum: 0,
maximum: 10, maximum: 10,
}), }),
getEntity("text", "message", "Hello!", {
friendly_name: "Message",
}),
getEntity("light", "unavailable", "unavailable", { getEntity("light", "unavailable", "unavailable", {
friendly_name: "Bed Light", friendly_name: "Bed Light",
@ -129,6 +132,9 @@ const ENTITIES = [
friendly_name: "Who cooks", friendly_name: "Who cooks",
icon: "mdi:cheff", icon: "mdi:cheff",
}), }),
getEntity("text", "unavailable", "unavailable", {
friendly_name: "Message",
}),
]; ];
const CONFIGS = [ const CONFIGS = [
@ -147,6 +153,7 @@ const CONFIGS = [
- climate.ecobee - climate.ecobee
- input_number.number - input_number.number
- sensor.humidity - sensor.humidity
- text.message
`, `,
}, },
{ {
@ -219,6 +226,7 @@ const CONFIGS = [
- climate.unavailable - climate.unavailable
- input_number.unavailable - input_number.unavailable
- input_select.unavailable - input_select.unavailable
- text.unavailable
`, `,
}, },
{ {

View File

@ -114,6 +114,7 @@ export const FIXED_DOMAIN_ICONS = {
siren: mdiBullhorn, siren: mdiBullhorn,
simple_alarm: mdiBell, simple_alarm: mdiBell,
sun: mdiWhiteBalanceSunny, sun: mdiWhiteBalanceSunny,
text: mdiFormTextbox,
timer: mdiTimerOutline, timer: mdiTimerOutline,
updater: mdiCloudUpload, updater: mdiCloudUpload,
vacuum: mdiRobotVacuum, vacuum: mdiRobotVacuum,
@ -182,6 +183,7 @@ export const DOMAINS_WITH_CARD = [
"script", "script",
"select", "select",
"timer", "timer",
"text",
"vacuum", "vacuum",
"water_heater", "water_heater",
]; ];
@ -214,6 +216,7 @@ export const DOMAINS_INPUT_ROW = [
"script", "script",
"select", "select",
"switch", "switch",
"text",
"vacuum", "vacuum",
]; ];

19
src/data/text.ts Normal file
View File

@ -0,0 +1,19 @@
import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
interface TextEntityAttributes extends HassEntityAttributeBase {
min?: number;
max?: number;
pattern?: string;
mode?: "text" | "password";
}
export interface TextEntity extends HassEntityBase {
attributes: TextEntityAttributes;
}
export const setValue = (hass: HomeAssistant, entity: string, value: string) =>
hass.callService("text", "set_value", { value }, { entity_id: entity });

View File

@ -50,8 +50,9 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_text", "input_text",
"number", "number",
"scene", "scene",
"update",
"select", "select",
"text",
"update",
]; ];
/** Domains that should have the history hidden in the more info dialog. */ /** Domains that should have the history hidden in the more info dialog. */

View File

@ -2,7 +2,7 @@ import "../entity-rows/hui-media-player-entity-row";
import "../entity-rows/hui-scene-entity-row"; import "../entity-rows/hui-scene-entity-row";
import "../entity-rows/hui-script-entity-row"; import "../entity-rows/hui-script-entity-row";
import "../entity-rows/hui-sensor-entity-row"; import "../entity-rows/hui-sensor-entity-row";
import "../entity-rows/hui-text-entity-row"; import "../entity-rows/hui-simple-entity-row";
import "../entity-rows/hui-toggle-entity-row"; import "../entity-rows/hui-toggle-entity-row";
import { LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceRowConfig } from "../entity-rows/types";
import "../special-rows/hui-attribute-row"; import "../special-rows/hui-attribute-row";
@ -18,7 +18,7 @@ const ALWAYS_LOADED_TYPES = new Set([
"scene-entity", "scene-entity",
"script-entity", "script-entity",
"sensor-entity", "sensor-entity",
"text-entity", "simple-entity",
"toggle-entity", "toggle-entity",
"button", "button",
"call-service", "call-service",
@ -41,6 +41,7 @@ const LAZY_LOAD_TYPES = {
"lock-entity": () => import("../entity-rows/hui-lock-entity-row"), "lock-entity": () => import("../entity-rows/hui-lock-entity-row"),
"number-entity": () => import("../entity-rows/hui-number-entity-row"), "number-entity": () => import("../entity-rows/hui-number-entity-row"),
"select-entity": () => import("../entity-rows/hui-select-entity-row"), "select-entity": () => import("../entity-rows/hui-select-entity-row"),
"text-entity": () => import("../entity-rows/hui-text-entity-row"),
"timer-entity": () => import("../entity-rows/hui-timer-entity-row"), "timer-entity": () => import("../entity-rows/hui-timer-entity-row"),
conditional: () => import("../special-rows/hui-conditional-row"), conditional: () => import("../special-rows/hui-conditional-row"),
"weather-entity": () => import("../entity-rows/hui-weather-entity-row"), "weather-entity": () => import("../entity-rows/hui-weather-entity-row"),
@ -53,7 +54,7 @@ const LAZY_LOAD_TYPES = {
text: () => import("../special-rows/hui-text-row"), text: () => import("../special-rows/hui-text-row"),
}; };
const DOMAIN_TO_ELEMENT_TYPE = { const DOMAIN_TO_ELEMENT_TYPE = {
_domain_not_found: "text", _domain_not_found: "simple",
alert: "toggle", alert: "toggle",
automation: "toggle", automation: "toggle",
button: "button", button: "button",
@ -78,6 +79,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
sensor: "sensor", sensor: "sensor",
siren: "toggle", siren: "toggle",
switch: "toggle", switch: "toggle",
text: "text",
timer: "timer", timer: "timer",
vacuum: "toggle", vacuum: "toggle",
// Temporary. Once climate is rewritten, // Temporary. Once climate is rewritten,

View File

@ -0,0 +1,73 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import { LovelaceRow } from "./types";
@customElement("hui-simple-entity-row")
class HuiSimpleEntityRow extends LitElement implements LovelaceRow {
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: EntitiesCardEntityConfig;
public setConfig(config: EntitiesCardEntityConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult {
if (!this._config || !this.hass) {
return html``;
}
const stateObj = this.hass.states[this._config.entity];
if (!stateObj) {
return html`
<hui-warning>
${createEntityNotFoundWarning(this.hass, this._config.entity)}
</hui-warning>
`;
}
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
${computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale)}
</hui-generic-entity-row>
`;
}
static get styles(): CSSResultGroup {
return css`
div {
text-align: right;
}
.pointer {
cursor: pointer;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-simple-entity-row": HuiSimpleEntityRow;
}
}

View File

@ -1,27 +1,22 @@
import { import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { TextEntity, setValue } from "../../../data/text";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row"; import "../components/hui-generic-entity-row";
import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createEntityNotFoundWarning } from "../components/hui-warning";
import { LovelaceRow } from "./types"; import { EntityConfig, LovelaceRow } from "./types";
import "../../../components/ha-textfield";
import { computeStateName } from "../../../common/entity/compute_state_name";
@customElement("hui-text-entity-row") @customElement("hui-text-entity-row")
class HuiTextEntityRow extends LitElement implements LovelaceRow { class HuiTextEntityRow extends LitElement implements LovelaceRow {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: EntitiesCardEntityConfig; @state() private _config?: EntityConfig;
public setConfig(config: EntitiesCardEntityConfig): void { public setConfig(config: EntityConfig): void {
if (!config) { if (!config) {
throw new Error("Invalid configuration"); throw new Error("Invalid configuration");
} }
@ -37,7 +32,9 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
return html``; return html``;
} }
const stateObj = this.hass.states[this._config.entity]; const stateObj = this.hass.states[this._config.entity] as
| TextEntity
| undefined;
if (!stateObj) { if (!stateObj) {
return html` return html`
@ -48,23 +45,54 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
} }
return html` return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}> <hui-generic-entity-row
${computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale)} .hass=${this.hass}
.config=${this._config}
hideName
>
<ha-textfield
.label=${this._config.name || computeStateName(stateObj)}
.disabled=${stateObj.state === UNAVAILABLE}
.value=${stateObj.state}
.minlength=${stateObj.attributes.min}
.maxlength=${stateObj.attributes.max}
.autoValidate=${stateObj.attributes.pattern}
.pattern=${stateObj.attributes.pattern}
.type=${stateObj.attributes.mode}
@change=${this._valueChanged}
placeholder=${this.hass!.localize("ui.card.text.emtpy_value")}
></ha-textfield>
</hui-generic-entity-row> </hui-generic-entity-row>
`; `;
} }
static get styles(): CSSResultGroup { private _valueChanged(ev): void {
return css` const stateObj = this.hass!.states[this._config!.entity] as TextEntity;
div { const newValue = ev.target.value;
text-align: right;
// Filter out invalid text states
if (newValue && UNAVAILABLE_STATES.includes(newValue)) {
ev.target.value = stateObj.state;
return;
} }
.pointer {
cursor: pointer; if (newValue !== stateObj.state) {
setValue(this.hass!, stateObj.entity_id, newValue);
}
ev.target.blur();
}
static styles = css`
hui-generic-entity-row {
display: flex;
align-items: center;
}
ha-textfield {
width: 100%;
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

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

View File

@ -0,0 +1,73 @@
import "../components/ha-textfield";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { computeStateName } from "../common/entity/compute_state_name";
import { stopPropagation } from "../common/dom/stop_propagation";
import "../components/entity/state-badge";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../data/entity";
import { TextEntity, setValue } from "../data/text";
import type { HomeAssistant } from "../types";
@customElement("state-card-text")
class StateCardText extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: TextEntity;
protected render(): TemplateResult {
return html`
<state-badge .stateObj=${this.stateObj}></state-badge>
<ha-textfield
.label=${computeStateName(this.stateObj)}
.disabled=${this.stateObj.state === UNAVAILABLE}
.value=${this.stateObj.state}
.minlength=${this.stateObj.attributes.min}
.maxlength=${this.stateObj.attributes.max}
.autoValidate=${this.stateObj.attributes.pattern}
.pattern=${this.stateObj.attributes.pattern}
.type=${this.stateObj.attributes.mode}
@change=${this._valueChanged}
@click=${stopPropagation}
placeholder=${this.hass.localize("ui.card.text.emtpy_value")}
></ha-textfield>
`;
}
private _valueChanged(ev): void {
const value = ev.target.value;
// Filter out invalid text states
if (value && UNAVAILABLE_STATES.includes(value)) {
ev.target.value = this.stateObj.state;
return;
}
if (value === this.stateObj.state) {
return;
}
setValue(this.hass!, this.stateObj.entity_id, value);
}
static get styles(): CSSResultGroup {
return css`
:host {
display: flex;
}
state-badge {
float: left;
margin-top: 10px;
}
ha-textfield {
width: 100%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-text": StateCardText;
}
}

View File

@ -235,6 +235,9 @@
"installing_with_progress": "Installing ({progress}%)", "installing_with_progress": "Installing ({progress}%)",
"up_to_date": "Up-to-date" "up_to_date": "Up-to-date"
}, },
"text": {
"emtpy_value": "(empty value)"
},
"timer": { "timer": {
"actions": { "actions": {
"start": "start", "start": "start",