Migrate state cards from Polymer to Lit (#18257)

This commit is contained in:
Simon Lamon 2023-11-28 13:30:39 +01:00 committed by GitHub
parent f4ee734ea3
commit 4582c3ba0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1574 additions and 1400 deletions

View File

@ -23,7 +23,7 @@ class DemoMoreInfo extends LitElement {
<state-card-content
.stateObj=${state}
.hass=${this.hass}
in-dialog
inDialog
></state-card-content>
<more-info-content

View File

@ -0,0 +1,3 @@
---
title: Input Text
---

View File

@ -0,0 +1,46 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("input_text", "text", "Inspiration", {
friendly_name: "Text",
mode: "text",
}),
];
@customElement("demo-more-info-input-text")
class DemoMoreInfoInputText extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-input-text": DemoMoreInfoInputText;
}
}

View File

@ -0,0 +1,3 @@
---
title: Lock
---

View File

@ -0,0 +1,49 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("lock", "lock", "locked", {
friendly_name: "Lock",
device_class: "lock",
}),
getEntity("lock", "unavailable", "unavailable", {
friendly_name: "Unavailable lock",
}),
];
@customElement("demo-more-info-lock")
class DemoMoreInfoLock extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-lock": DemoMoreInfoLock;
}
}

View File

@ -0,0 +1,3 @@
---
title: Media Player
---

View File

@ -0,0 +1,41 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { createMediaPlayerEntities } from "../../data/media_players";
const ENTITIES = createMediaPlayerEntities();
@customElement("demo-more-info-media-player")
class DemoMoreInfoMediaPlayer extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-media-player": DemoMoreInfoMediaPlayer;
}
}

View File

@ -0,0 +1,3 @@
---
title: Number
---

View File

@ -0,0 +1,78 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("number", "box1", 0, {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
}),
getEntity("number", "slider1", 0, {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
}),
getEntity("number", "auto1", 0, {
friendly_name: "Auto1",
min: 0,
max: 1000,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
getEntity("number", "auto2", 0, {
friendly_name: "Auto2",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
];
@customElement("demo-more-info-number")
class DemoMoreInfoNumber extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-number": DemoMoreInfoNumber;
}
}

View File

@ -0,0 +1,3 @@
---
title: Scene
---

View File

@ -0,0 +1,49 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
}),
getEntity("scene", "unavailable", "unavailable", {
friendly_name: "Romantic Scene",
}),
];
@customElement("demo-more-info-scene")
class DemoMoreInfoScene extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-scene": DemoMoreInfoScene;
}
}

View File

@ -0,0 +1,3 @@
---
title: Timer
---

View File

@ -0,0 +1,46 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("timer", "timer", "idle", {
friendly_name: "Timer",
duration: "0:05:00",
}),
];
@customElement("demo-more-info-timer")
class DemoMoreInfoTimer extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-timer": DemoMoreInfoTimer;
}
}

View File

@ -0,0 +1,3 @@
---
title: Vacuum
---

View File

@ -0,0 +1,50 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
const ENTITIES = [
getEntity("vacuum", "first_floor_vacuum", "docked", {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
}),
];
@customElement("demo-more-info-vacuum")
class DemoMoreInfoVacuum extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-vacuum": DemoMoreInfoVacuum;
}
}

View File

@ -1,33 +0,0 @@
/**
* Update root's child element to be newElementTag replacing another existing child if any.
* Copy attributes into the child element.
*/
export default function dynamicContentUpdater(root, newElementTag, attributes) {
const rootEl = root;
let customEl;
if (rootEl.lastChild && rootEl.lastChild.tagName === newElementTag) {
customEl = rootEl.lastChild;
} else {
if (rootEl.lastChild) {
rootEl.removeChild(rootEl.lastChild);
}
// Creating an element with upper case works fine in Chrome, but in FF it doesn't immediately
// become a defined Custom Element. Polymer does that in some later pass.
customEl = document.createElement(newElementTag.toLowerCase());
}
if (customEl.setProperties) {
customEl.setProperties(attributes);
} else {
// If custom element definition wasn't loaded yet - setProperties would be
// missing, but no harm in setting attributes one-by-one then.
Object.keys(attributes).forEach((key) => {
customEl[key] = attributes[key];
});
}
if (customEl.parentNode === null) {
rootEl.appendChild(customEl);
}
}

View File

@ -125,7 +125,7 @@ class StateInfo extends LitElement {
text-overflow: ellipsis;
}
.name[in-dialog],
.name[inDialog],
:host([secondary-line]) .name {
line-height: 20px;
}

View File

@ -1,89 +0,0 @@
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import LocalizeMixin from "../mixins/localize-mixin";
const STATES_INTERCEPTABLE = {
cleaning: {
action: "return_to_base",
service: "return_to_base",
},
docked: {
action: "start_cleaning",
service: "start",
},
idle: {
action: "start_cleaning",
service: "start",
},
off: {
action: "turn_on",
service: "turn_on",
},
on: {
action: "turn_off",
service: "turn_off",
},
paused: {
action: "resume_cleaning",
service: "start",
},
};
/*
* @appliesMixin LocalizeMixin
*/
class HaVacuumState extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style>
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
mwc-button[disabled] {
background-color: transparent;
color: var(--secondary-text-color);
}
</style>
<mwc-button on-click="_callService" disabled="[[!_interceptable]]"
>[[_computeLabel(stateObj.state, _interceptable)]]</mwc-button
>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
_interceptable: {
type: Boolean,
computed:
"_computeInterceptable(stateObj.state, stateObj.attributes.supported_features)",
},
};
}
_computeInterceptable(state, supportedFeatures) {
return state in STATES_INTERCEPTABLE && supportedFeatures !== 0;
}
_computeLabel(state, interceptable) {
return interceptable
? this.localize(
`ui.card.vacuum.actions.${STATES_INTERCEPTABLE[state].action}`
)
: this.localize(`component.vacuum.entity_component._.state.${state}`);
}
_callService(ev) {
ev.stopPropagation();
const stateObj = this.stateObj;
const service = STATES_INTERCEPTABLE[stateObj.state].service;
this.hass.callService("vacuum", service, { entity_id: stateObj.entity_id });
}
}
customElements.define("ha-vacuum-state", HaVacuumState);

View File

@ -0,0 +1,111 @@
import "@material/mwc-button";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { HassEntity } from "home-assistant-js-websocket";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
const STATES_INTERCEPTABLE: {
[state: string]: {
action:
| "return_to_base"
| "start_cleaning"
| "turn_on"
| "turn_off"
| "resume_cleaning";
service: string;
};
} = {
cleaning: {
action: "return_to_base",
service: "return_to_base",
},
docked: {
action: "start_cleaning",
service: "start",
},
idle: {
action: "start_cleaning",
service: "start",
},
off: {
action: "turn_on",
service: "turn_on",
},
on: {
action: "turn_off",
service: "turn_off",
},
paused: {
action: "resume_cleaning",
service: "start",
},
};
@customElement("ha-vacuum-state")
export class HaVacuumState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
protected render(): TemplateResult {
const interceptable = this._computeInterceptable(
this.stateObj.state,
this.stateObj.attributes.supported_features
);
return html`
<mwc-button @click=${this._callService} .disabled=${!interceptable}>
${this._computeLabel(this.stateObj.state, interceptable)}
</mwc-button>
`;
}
private _computeInterceptable(
state: string,
supportedFeatures: number | undefined
) {
return state in STATES_INTERCEPTABLE && supportedFeatures !== 0;
}
private _computeLabel(state: string, interceptable: boolean) {
return interceptable
? this.hass.localize(
`ui.card.vacuum.actions.${STATES_INTERCEPTABLE[state].action}`
)
: this.hass.localize(
`component.vacuum.entity_component._.state.${state}`
);
}
private async _callService(ev) {
ev.stopPropagation();
const stateObj = this.stateObj;
const service = STATES_INTERCEPTABLE[stateObj.state].service;
await this.hass.callService("vacuum", service, {
entity_id: stateObj.entity_id,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
mwc-button[disabled] {
background-color: transparent;
color: var(--secondary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-vacuum-state": HaVacuumState;
}
}

View File

@ -1,135 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { EventsMixin } from "../mixins/events-mixin";
import "./ha-icon";
import "./ha-icon-button";
/*
* @appliesMixin EventsMixin
*/
class HaWaterHeaterControl extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
/* local DOM styles go here */
:host {
@apply --layout-flex;
@apply --layout-horizontal;
@apply --layout-justified;
}
.in-flux#target_temperature {
color: var(--error-color);
}
#target_temperature {
@apply --layout-self-center;
font-size: 200%;
direction: ltr;
}
.control-buttons {
font-size: 200%;
text-align: right;
}
ha-icon-button {
height: 48px;
width: 48px;
}
</style>
<!-- local DOM goes here -->
<div id="target_temperature">[[value]] [[units]]</div>
<div class="control-buttons">
<div>
<ha-icon-button on-click="incrementValue">
<ha-icon icon="hass:chevron-up"></ha-icon>
</ha-icon-button>
</div>
<div>
<ha-icon-button on-click="decrementValue">
<ha-icon icon="hass:chevron-down"></ha-icon>
</ha-icon-button>
</div>
</div>
`;
}
static get properties() {
return {
value: {
type: Number,
observer: "valueChanged",
},
units: {
type: String,
},
min: {
type: Number,
},
max: {
type: Number,
},
step: {
type: Number,
value: 1,
},
};
}
temperatureStateInFlux(inFlux) {
this.$.target_temperature.classList.toggle("in-flux", inFlux);
}
incrementValue() {
const newval = this.value + this.step;
if (this.value < this.max) {
this.last_changed = Date.now();
this.temperatureStateInFlux(true);
}
if (newval <= this.max) {
// If no initial target_temp
// this forces control to start
// from the min configured instead of 0
if (newval <= this.min) {
this.value = this.min;
} else {
this.value = newval;
}
} else {
this.value = this.max;
}
}
decrementValue() {
const newval = this.value - this.step;
if (this.value > this.min) {
this.last_changed = Date.now();
this.temperatureStateInFlux(true);
}
if (newval >= this.min) {
this.value = newval;
} else {
this.value = this.min;
}
}
valueChanged() {
// when the last_changed timestamp is changed,
// trigger a potential event fire in
// the future, as long as last changed is far enough in the
// past.
if (this.last_changed) {
window.setTimeout(() => {
const now = Date.now();
if (now - this.last_changed >= 2000) {
this.fire("change");
this.temperatureStateInFlux(false);
this.last_changed = null;
}
}, 2010);
}
}
}
customElements.define("ha-water_heater-control", HaWaterHeaterControl);

View File

@ -1,62 +1,30 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { customElement, property } from "lit/decorators";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { HassEntity } from "home-assistant-js-websocket";
import { formatNumber } from "../common/number/format_number";
import LocalizeMixin from "../mixins/localize-mixin";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
/*
* @appliesMixin LocalizeMixin
*/
class HaWaterHeaterState extends LocalizeMixin(PolymerElement) {
static get template() {
@customElement("ha-water_heater-state")
export class HaWaterHeaterState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
protected render(): TemplateResult {
return html`
<style>
:host {
display: flex;
flex-direction: column;
justify-content: center;
white-space: nowrap;
}
.target {
color: var(--primary-text-color);
}
.current {
color: var(--secondary-text-color);
}
.state-label {
font-weight: bold;
}
.label {
direction: ltr;
display: inline-block;
}
</style>
<div class="target">
<span class="state-label label"> [[_localizeState(stateObj)]] </span>
<span class="label">[[computeTarget(hass, stateObj)]]</span>
<span class="state-label label">
${this.hass.formatEntityState(this.stateObj)}
</span>
<span class="label"
>${this._computeTarget(this.hass, this.stateObj)}</span
>
</div>
<template is="dom-if" if="[[currentStatus]]">
<div class="current">
[[localize('ui.card.water_heater.currently')]]: [[currentStatus]]
</div>
</template>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
};
}
computeTarget(hass, stateObj) {
private _computeTarget(hass: HomeAssistant, stateObj: HassEntity) {
if (!hass || !stateObj) return null;
// We're using "!= null" on purpose so that we match both null and undefined.
@ -85,5 +53,41 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) {
_localizeState(stateObj) {
return this.hass.formatEntityState(stateObj);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
display: flex;
flex-direction: column;
justify-content: center;
white-space: nowrap;
}
.target {
color: var(--primary-text-color);
}
.current {
color: var(--secondary-text-color);
}
.state-label {
font-weight: bold;
}
.label {
direction: ltr;
display: inline-block;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-water_heater-state": HaWaterHeaterState;
}
}
customElements.define("ha-water_heater-state", HaWaterHeaterState);

View File

@ -65,7 +65,7 @@ export class MoreInfoInfo extends LitElement {
? ""
: html`
<state-card-content
in-dialog
inDialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>

View File

@ -1,13 +1,14 @@
import { HassEntity } from "home-assistant-js-websocket";
import { PropertyValues, ReactiveElement } from "lit";
import { property } from "lit/decorators";
import dynamicContentUpdater from "../../common/dom/dynamic_content_updater";
import { LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { ExtEntityRegistryEntry } from "../../data/entity_registry";
import { importMoreInfoControl } from "../../panels/lovelace/custom-card-helpers";
import { HomeAssistant } from "../../types";
import { stateMoreInfoType } from "./state_more_info_control";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
class MoreInfoContent extends ReactiveElement {
@customElement("more-info-content")
class MoreInfoContent extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public stateObj?: HassEntity;
@ -16,62 +17,31 @@ class MoreInfoContent extends ReactiveElement {
@property({ attribute: false }) public editMode?: boolean;
private _detachedChild?: ChildNode;
protected createRenderRoot() {
return this;
}
// This is not a lit element, but an updating element, so we implement update
protected update(changedProps: PropertyValues): void {
super.update(changedProps);
const stateObj = this.stateObj;
const entry = this.entry;
const hass = this.hass;
const editMode = this.editMode;
if (!stateObj || !hass) {
if (this.lastChild) {
this._detachedChild = this.lastChild;
// Detach child to prevent it from doing work.
this.removeChild(this.lastChild);
}
return;
}
if (this._detachedChild) {
this.appendChild(this._detachedChild);
this._detachedChild = undefined;
}
protected render() {
let moreInfoType: string | undefined;
if (stateObj.attributes && "custom_ui_more_info" in stateObj.attributes) {
moreInfoType = stateObj.attributes.custom_ui_more_info;
if (!this.stateObj || !this.hass) return nothing;
if (
this.stateObj.attributes &&
"custom_ui_more_info" in this.stateObj.attributes
) {
moreInfoType = this.stateObj.attributes.custom_ui_more_info;
} else {
const type = stateMoreInfoType(stateObj);
const type = stateMoreInfoType(this.stateObj);
importMoreInfoControl(type);
moreInfoType = type === "hidden" ? undefined : `more-info-${type}`;
}
if (!moreInfoType) {
if (this.lastChild) {
this.removeChild(this.lastChild);
}
return;
}
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
hass,
stateObj,
entry,
editMode,
if (!moreInfoType) return nothing;
return dynamicElement(moreInfoType, {
hass: this.hass,
stateObj: this.stateObj,
entry: this.entry,
editMode: this.editMode,
});
}
}
customElements.define("more-info-content", MoreInfoContent);
declare global {
interface HTMLElementTagNameMap {
"more-info-content": MoreInfoContent;

View File

@ -8,7 +8,7 @@ import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
@customElement("state-card-alert")
export class StateCardAlert extends LitElement {
class StateCardAlert extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@ -78,3 +78,9 @@ export class StateCardAlert extends LitElement {
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-alert": StateCardAlert;
}
}

View File

@ -9,7 +9,7 @@ import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@customElement("state-card-button")
export class StateCardButton extends LitElement {
class StateCardButton extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: HassEntity;

View File

@ -1,55 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-climate-state";
class StateCardClimate extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
:host {
@apply --paper-font-body1;
line-height: 1.5;
}
ha-climate-state {
margin-left: 16px;
text-align: right;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-climate-state
hass="[[hass]]"
state-obj="[[stateObj]]"
></ha-climate-state>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
}
customElements.define("state-card-climate", StateCardClimate);

View File

@ -0,0 +1,54 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "../components/entity/state-info";
import "../components/ha-climate-state";
import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-climate")
class StateCardClimate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
<ha-climate-state
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-climate-state>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
line-height: 1.5;
}
ha-climate-state {
margin-left: 16px;
text-align: right;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-climate": StateCardClimate;
}
}

View File

@ -1,69 +0,0 @@
import "@material/mwc-button";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import "../components/entity/state-info";
import LocalizeMixin from "../mixins/localize-mixin";
/*
* @appliesMixin LocalizeMixin
*/
class StateCardConfigurator extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<mwc-button hidden$="[[inDialog]]"
>[[_localizeState(stateObj)]]</mwc-button
>
</div>
<!-- pre load the image so the dialog is rendered the proper size -->
<template is="dom-if" if="[[stateObj.attributes.description_image]]">
<img hidden="" alt="" src="[[stateObj.attributes.description_image]]" />
</template>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
_localizeState(stateObj) {
return computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.locale,
this.hass.entities
);
}
}
customElements.define("state-card-configurator", StateCardConfigurator);

View File

@ -0,0 +1,59 @@
import "@material/mwc-button";
import { HassEntity } from "home-assistant-js-websocket";
import "../components/entity/state-info";
import { customElement, property } from "lit/decorators";
import {
CSSResultGroup,
LitElement,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-configurator")
class StateCardConfigurator extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
${this.inDialog
? html`<mwc-button
>${this.hass.formatEntityState(this.stateObj)}</mwc-button
>`
: nothing}
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-configurator": StateCardConfigurator;
}
}

View File

@ -1,62 +0,0 @@
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import dynamicContentUpdater from "../common/dom/dynamic_content_updater";
import { stateCardType } from "../common/entity/state_card_type";
import "./state-card-alert";
import "./state-card-button";
import "./state-card-climate";
import "./state-card-configurator";
import "./state-card-cover";
import "./state-card-display";
import "./state-card-event";
import "./state-card-humidifier";
import "./state-card-input_button";
import "./state-card-input_number";
import "./state-card-input_select";
import "./state-card-input_text";
import "./state-card-lawn_mower";
import "./state-card-lock";
import "./state-card-media_player";
import "./state-card-number";
import "./state-card-scene";
import "./state-card-script";
import "./state-card-select";
import "./state-card-text";
import "./state-card-timer";
import "./state-card-toggle";
import "./state-card-update";
import "./state-card-vacuum";
import "./state-card-water_heater";
class StateCardContent extends PolymerElement {
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
static get observers() {
return ["inputChanged(hass, inDialog, stateObj)"];
}
inputChanged(hass, inDialog, stateObj) {
let stateCard;
if (!stateObj || !hass) return;
if (stateObj.attributes && "custom_ui_state_card" in stateObj.attributes) {
stateCard = stateObj.attributes.custom_ui_state_card;
} else {
stateCard = "state-card-" + stateCardType(hass, stateObj);
}
dynamicContentUpdater(this, stateCard.toUpperCase(), {
hass: hass,
stateObj: stateObj,
inDialog: inDialog,
});
}
}
customElements.define("state-card-content", StateCardContent);

View File

@ -0,0 +1,65 @@
import { LitElement, nothing } from "lit";
import { HassEntity } from "home-assistant-js-websocket";
import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../types";
import { dynamicElement } from "../common/dom/dynamic-element-directive";
import { stateCardType } from "../common/entity/state_card_type";
import "./state-card-alert";
import "./state-card-button";
import "./state-card-climate";
import "./state-card-configurator";
import "./state-card-cover";
import "./state-card-display";
import "./state-card-event";
import "./state-card-humidifier";
import "./state-card-input_button";
import "./state-card-input_number";
import "./state-card-input_select";
import "./state-card-input_text";
import "./state-card-lawn_mower";
import "./state-card-lock";
import "./state-card-media_player";
import "./state-card-number";
import "./state-card-scene";
import "./state-card-script";
import "./state-card-select";
import "./state-card-text";
import "./state-card-timer";
import "./state-card-toggle";
import "./state-card-update";
import "./state-card-vacuum";
import "./state-card-water_heater";
@customElement("state-card-content")
class StateCardContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render() {
let stateCard: string;
if (!this.stateObj || !this.hass) return nothing;
if (
this.stateObj.attributes &&
"custom_ui_state_card" in this.stateObj.attributes
) {
stateCard = this.stateObj.attributes.custom_ui_state_card;
} else {
stateCard = "state-card-" + stateCardType(this.hass, this.stateObj);
}
return dynamicElement(stateCard, {
hass: this.hass,
stateObj: this.stateObj,
inDialog: this.inDialog,
});
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-content": StateCardContent;
}
}

View File

@ -12,7 +12,7 @@ import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
@customElement("state-card-display")
export class StateCardDisplay extends LitElement {
class StateCardDisplay extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@ -93,3 +93,9 @@ export class StateCardDisplay extends LitElement {
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-display": StateCardDisplay;
}
}

View File

@ -7,7 +7,7 @@ import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@customElement("state-card-event")
export class StateCardEvent extends LitElement {
class StateCardEvent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: HassEntity;

View File

@ -1,55 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-humidifier-state";
class StateCardHumidifier extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
:host {
@apply --paper-font-body1;
line-height: 1.5;
}
ha-humidifier-state {
margin-left: 16px;
text-align: right;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-humidifier-state
hass="[[hass]]"
state-obj="[[stateObj]]"
></ha-humidifier-state>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
}
customElements.define("state-card-humidifier", StateCardHumidifier);

View File

@ -0,0 +1,56 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import "../components/ha-humidifier-state";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-humidifier")
class StateCardHumidifier extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
>
</state-info>
<ha-humidifier-state
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-humidifier-state>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
line-height: 1.5;
}
ha-humidifier-state {
margin-left: 16px;
text-align: right;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-humidifier": StateCardHumidifier;
}
}

View File

@ -9,7 +9,7 @@ import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@customElement("state-card-input_button")
export class StateCardInputButton extends LitElement {
class StateCardInputButton extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: HassEntity;

View File

@ -1,90 +0,0 @@
/* eslint-plugin-disable lit */
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-textfield";
class StateCardInputText extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
ha-textfield {
margin-left: 16px;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-textfield
minlength="[[stateObj.attributes.min]]"
maxlength="[[stateObj.attributes.max]]"
value="[[value]]"
auto-validate="[[stateObj.attributes.pattern]]"
pattern="[[stateObj.attributes.pattern]]"
type="[[stateObj.attributes.mode]]"
on-input="onInput"
on-change="selectedValueChanged"
on-click="stopPropagation"
placeholder="(empty value)"
>
</ha-textfield>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
inDialog: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
observer: "stateObjectChanged",
},
pattern: String,
value: String,
};
}
stateObjectChanged(newVal) {
this.value = newVal.state;
}
onInput(ev) {
this.value = ev.target.value;
}
selectedValueChanged() {
if (this.value === this.stateObj.state) {
return;
}
this.hass.callService("input_text", "set_value", {
value: this.value,
entity_id: this.stateObj.entity_id,
});
}
stopPropagation(ev) {
ev.stopPropagation();
}
}
customElements.define("state-card-input_text", StateCardInputText);

View File

@ -0,0 +1,89 @@
import type { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "../components/entity/state-info";
import "../components/ha-textfield";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
import { stopPropagation } from "../common/dom/stop_propagation";
@customElement("state-card-input_text")
class StateCardInputText extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
@state() public value: string = "";
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info
><ha-textfield
.minlength=${this.stateObj.attributes.min}
.maxlength=${this.stateObj.attributes.max}
.value=${this.value}
.auto-validate=${this.stateObj.attributes.pattern}
.pattern=${this.stateObj.attributes.pattern}
.type=${this.stateObj.attributes.mode}
@input=${this._onInput}
@change=${this._selectedValueChanged}
@click=${stopPropagation}
placeholder="(empty value)"
>
</ha-textfield>
</div>
`;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj")) {
this.value = this.stateObj.state;
}
}
private _onInput(ev) {
this.value = ev.target.value;
}
private async _selectedValueChanged() {
if (this.value === this.stateObj.state) {
return;
}
await this.hass.callService("input_text", "set_value", {
value: this.value,
entity_id: this.stateObj.entity_id,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-textfield {
margin-left: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-input_text": StateCardInputText;
}
}

View File

@ -1,95 +0,0 @@
import "@material/mwc-button";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { supportsFeature } from "../common/entity/supports-feature";
import "../components/entity/state-info";
import LocalizeMixin from "../mixins/localize-mixin";
import { LockEntityFeature } from "../data/lock";
/*
* @appliesMixin LocalizeMixin
*/
class StateCardLock extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
[hidden] {
display: none !important;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<mwc-button
on-click="_callService"
data-service="open"
hidden$="[[!supportsOpen]]"
>[[localize('ui.card.lock.open')]]</mwc-button
>
<mwc-button
on-click="_callService"
data-service="unlock"
hidden$="[[!isLocked]]"
>[[localize('ui.card.lock.unlock')]]</mwc-button
>
<mwc-button
on-click="_callService"
data-service="lock"
hidden$="[[isLocked]]"
>[[localize('ui.card.lock.lock')]]</mwc-button
>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: {
type: Object,
observer: "_stateObjChanged",
},
inDialog: {
type: Boolean,
value: false,
},
isLocked: Boolean,
supportsOpen: Boolean,
};
}
_stateObjChanged(newVal) {
if (newVal) {
this.isLocked = newVal.state === "locked";
this.supportsOpen = supportsFeature(newVal, LockEntityFeature.OPEN);
}
}
_callService(ev) {
ev.stopPropagation();
const service = ev.target.dataset.service;
const data = {
entity_id: this.stateObj.entity_id,
};
this.hass.callService("lock", service, data);
}
}
customElements.define("state-card-lock", StateCardLock);

View File

@ -0,0 +1,83 @@
import "@material/mwc-button";
import type { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { supportsFeature } from "../common/entity/supports-feature";
import "../components/entity/state-info";
import { LockEntityFeature } from "../data/lock";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-lock")
class StateCardLock extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
const isLocked = this.stateObj.state === "locked";
const supportsOpen = supportsFeature(this.stateObj, LockEntityFeature.OPEN);
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
${!supportsOpen
? html`<mwc-button @click=${this._callService} data-service="open"
>${this.hass.localize("ui.card.lock.open")}</mwc-button
>`
: nothing}
${isLocked
? html` <mwc-button @click=${this._callService} data-service="unlock"
>${this.hass.localize("ui.card.lock.unlock")}</mwc-button
>`
: nothing}
${!isLocked
? html`<mwc-button @click=${this._callService} data-service="lock"
>${this.hass.localize("ui.card.lock.lock")}</mwc-button
>`
: nothing}
</div>
`;
}
private async _callService(ev) {
ev.stopPropagation();
const service = ev.target.dataset.service;
const data = {
entity_id: this.stateObj.entity_id,
};
await this.hass.callService("lock", service, data);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-lock": StateCardLock;
}
}

View File

@ -1,97 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import "../components/entity/state-info";
import LocalizeMixin from "../mixins/localize-mixin";
import HassMediaPlayerEntity from "../util/hass-media-player-model";
/*
* @appliesMixin LocalizeMixin
*/
class StateCardMediaPlayer extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
:host {
line-height: 1.5;
}
.state {
@apply --paper-font-common-nowrap;
@apply --paper-font-body1;
margin-left: 16px;
text-align: right;
}
.main-text {
@apply --paper-font-common-nowrap;
color: var(--primary-text-color);
}
.main-text[take-height] {
line-height: 40px;
}
.secondary-text {
@apply --paper-font-common-nowrap;
color: var(--secondary-text-color);
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<div class="state">
<div class="main-text" take-height$="[[!playerObj.secondaryTitle]]">
[[computePrimaryText(localize, playerObj)]]
</div>
<div class="secondary-text">[[playerObj.secondaryTitle]]</div>
</div>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
playerObj: {
type: Object,
computed: "computePlayerObj(hass, stateObj)",
},
};
}
computePlayerObj(hass, stateObj) {
return new HassMediaPlayerEntity(hass, stateObj);
}
computePrimaryText(localize, playerObj) {
return (
playerObj.primaryTitle ||
computeStateDisplay(
localize,
playerObj.stateObj,
this.hass.locale,
this.hass.entities
)
);
}
}
customElements.define("state-card-media_player", StateCardMediaPlayer);

View File

@ -0,0 +1,83 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import "../components/entity/state-info";
import HassMediaPlayerEntity from "../util/hass-media-player-model";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-media_player")
class StateCardMediaPlayer extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
const playerObj = new HassMediaPlayerEntity(this.hass, this.stateObj);
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
<div class="state">
<div class="main-text" take-height=${!playerObj.secondaryTitle}>
${this._computePrimaryText(this.hass.localize, playerObj)}
</div>
<div class="secondary-text">${playerObj.secondaryTitle}</div>
</div>
</div>
`;
}
private _computePrimaryText(localize, playerObj) {
return (
playerObj.primaryTitle ||
computeStateDisplay(
localize,
playerObj.stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities
)
);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
line-height: 1.5;
}
.state {
margin-left: 16px;
text-align: right;
}
.main-text {
color: var(--primary-text-color);
}
.main-text[take-height] {
line-height: 40px;
}
.secondary-text {
color: var(--secondary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-media_player": StateCardMediaPlayer;
}
}

View File

@ -1,201 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior";
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-slider";
import "../components/ha-textfield";
class StateCardNumber extends mixinBehaviors(
[IronResizableBehavior],
PolymerElement
) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
ha-slider {
margin-left: auto;
flex-grow: 1;
}
.state {
@apply --paper-font-body1;
color: var(--primary-text-color);
display: flex;
align-items: center;
justify-content: end;
}
.sliderstate {
min-width: 45px;
}
[hidden] {
display: none !important;
}
ha-textfield {
text-align: right;
margin-left: auto;
}
</style>
<div class="horizontal justified layout" id="number_card">
${this.stateInfoTemplate}
<ha-slider
labeled
min="[[min]]"
max="[[max]]"
value="{{value}}"
step="[[step]]"
hidden="[[hiddenslider]]"
on-change="selectedValueChanged"
on-click="stopPropagation"
id="slider"
>
</ha-slider>
<ha-textfield
auto-validate=""
pattern="[0-9]+([\\.][0-9]+)?"
step="[[step]]"
min="[[min]]"
max="[[max]]"
value="[[value]]"
type="number"
on-input="onInput"
on-change="selectedValueChanged"
on-click="stopPropagation"
hidden="[[hiddenbox]]"
>
</ha-textfield>
<div class="state" hidden="[[hiddenbox]]">
[[stateObj.attributes.unit_of_measurement]]
</div>
<div
id="sliderstate"
class="state sliderstate"
hidden="[[hiddenslider]]"
>
[[value]] [[stateObj.attributes.unit_of_measurement]]
</div>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
ready() {
super.ready();
if (typeof ResizeObserver === "function") {
const ro = new ResizeObserver((entries) => {
entries.forEach(() => {
this.hiddenState();
});
});
ro.observe(this.$.number_card);
} else {
this.addEventListener("iron-resize", () => this.hiddenState());
}
}
static get properties() {
return {
hass: Object,
hiddenbox: {
type: Boolean,
value: true,
},
hiddenslider: {
type: Boolean,
value: true,
},
inDialog: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
observer: "stateObjectChanged",
},
min: {
type: Number,
value: 0,
},
max: {
type: Number,
value: 100,
},
maxlength: {
type: Number,
value: 3,
},
step: Number,
value: Number,
mode: String,
};
}
hiddenState() {
if (this.mode !== "slider") return;
const sliderwidth = this.$.slider.offsetWidth;
if (sliderwidth < 100) {
this.$.sliderstate.hidden = true;
} else if (sliderwidth >= 145) {
this.$.sliderstate.hidden = false;
}
}
stateObjectChanged(newVal) {
const prevMode = this.mode;
const min = Number(newVal.attributes.min);
const max = Number(newVal.attributes.max);
const step = Number(newVal.attributes.step);
const range = (max - min) / step;
this.setProperties({
min: min,
max: max,
step: step,
value: Number(newVal.state),
mode: String(newVal.attributes.mode),
maxlength: String(newVal.attributes.max).length,
hiddenbox:
newVal.attributes.mode === "slider" ||
(newVal.attributes.mode === "auto" && range <= 256),
hiddenslider:
newVal.attributes.mode === "box" ||
(newVal.attributes.mode === "auto" && range > 256),
});
if (this.mode === "slider" && prevMode !== "slider") {
this.hiddenState();
}
}
onInput(ev) {
this.value = ev.target.value;
}
selectedValueChanged(ev) {
if (ev.target.value === Number(this.stateObj.state)) {
return;
}
this.hass.callService("number", "set_value", {
value: ev.target.value,
entity_id: this.stateObj.entity_id,
});
}
stopPropagation(ev) {
ev.stopPropagation();
}
}
customElements.define("state-card-number", StateCardNumber);

View File

@ -0,0 +1,168 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import "../components/ha-slider";
import "../components/ha-textfield";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
import { loadPolyfillIfNeeded } from "../resources/resize-observer.polyfill";
import { computeRTLDirection } from "../common/util/compute_rtl";
import { isUnavailableState } from "../data/entity";
import { debounce } from "../common/util/debounce";
@customElement("state-card-number")
class StateCardNumber extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
private _loaded?: boolean;
private _updated?: boolean;
private _resizeObserver?: ResizeObserver;
public connectedCallback(): void {
super.connectedCallback();
if (this._updated && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._resizeObserver?.disconnect();
}
protected firstUpdated(): void {
this._updated = true;
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
this._attachObserver();
}
protected render() {
const range = this.stateObj.attributes.max - this.stateObj.attributes.min;
return html`
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
${this.stateObj.attributes.mode === "slider" ||
(this.stateObj.attributes.mode === "auto" && range <= 256)
? html`
<div class="flex">
<ha-slider
labeled
.dir=${computeRTLDirection(this.hass)}
.disabled=${isUnavailableState(this.stateObj.state)}
.step=${Number(this.stateObj.attributes.step)}
.min=${Number(this.stateObj.attributes.min)}
.max=${Number(this.stateObj.attributes.max)}
.value=${this.stateObj.state}
@change=${this._selectedValueChanged}
>
</ha-slider>
<span class="state">
${this.hass.formatEntityState(this.stateObj)}
</span>
</div>
`
: html` <div class="flex state">
<ha-textfield
.disabled=${isUnavailableState(this.stateObj.state)}
pattern="[0-9]+([\\.][0-9]+)?"
.step=${Number(this.stateObj.attributes.step)}
.min=${Number(this.stateObj.attributes.min)}
.max=${Number(this.stateObj.attributes.max)}
.value=${Number(this.stateObj.state).toString()}
.suffix=${this.stateObj.attributes.unit_of_measurement || ""}
type="number"
@change=${this._selectedValueChanged}
>
</ha-textfield>
</div>`}
`;
}
private async _initialLoad(): Promise<void> {
this._loaded = true;
await this.updateComplete;
this._measureCard();
}
private _measureCard() {
if (!this.isConnected) {
return;
}
const element = this.shadowRoot!.querySelector(".state") as HTMLElement;
if (!element) {
return;
}
element.hidden = this.clientWidth <= 300;
}
private async _attachObserver(): Promise<void> {
if (!this._resizeObserver) {
await loadPolyfillIfNeeded();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
}
if (this.isConnected) {
this._resizeObserver.observe(this);
}
}
private async _selectedValueChanged(ev: Event) {
const value = (ev.target as HTMLInputElement).value;
if (value === this.stateObj.state) {
return;
}
await this.hass.callService("number", "set_value", {
value: value,
entity_id: this.stateObj.entity_id,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
display: flex;
}
.flex {
display: flex;
align-items: center;
justify-content: flex-end;
flex-grow: 2;
}
.state {
min-width: 45px;
text-align: end;
}
ha-textfield {
text-align: end;
}
ha-slider {
width: 100%;
max-width: 200px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-number": StateCardNumber;
}
}

View File

@ -1,60 +0,0 @@
import "@material/mwc-button";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import { activateScene } from "../data/scene";
import LocalizeMixin from "../mixins/localize-mixin";
/*
* @appliesMixin LocalizeMixin
*/
class StateCardScene extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<mwc-button on-click="_activateScene"
>[[localize('ui.card.scene.activate')]]</mwc-button
>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
_activateScene(ev) {
ev.stopPropagation();
activateScene(this.hass, this.stateObj.entity_id);
}
}
customElements.define("state-card-scene", StateCardScene);

View File

@ -0,0 +1,56 @@
import "@material/mwc-button";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import { activateScene } from "../data/scene";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-scene")
class StateCardScene extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render() {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
<mwc-button @click=${this._activateScene}
>${this.hass.localize("ui.card.scene.activate")}</mwc-button
>
</div>
`;
}
private _activateScene(ev) {
ev.stopPropagation();
activateScene(this.hass, this.stateObj.entity_id);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-scene": StateCardScene;
}
}

View File

@ -10,7 +10,7 @@ import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@customElement("state-card-script")
export class StateCardScript extends LitElement {
class StateCardScript extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: HassEntity;
@ -69,3 +69,9 @@ export class StateCardScript extends LitElement {
return haStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-script": StateCardScript;
}
}

View File

@ -1,97 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import { computeDisplayTimer, timerTimeRemaining } from "../data/timer";
class StateCardTimer extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
.state {
@apply --paper-font-body1;
color: var(--primary-text-color);
margin-left: 16px;
text-align: right;
line-height: 40px;
white-space: nowrap;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<div class="state">[[_displayState(timeRemaining, stateObj)]]</div>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: {
type: Object,
observer: "stateObjChanged",
},
timeRemaining: Number,
inDialog: {
type: Boolean,
value: false,
},
};
}
connectedCallback() {
super.connectedCallback();
this.startInterval(this.stateObj);
}
disconnectedCallback() {
super.disconnectedCallback();
this.clearInterval();
}
stateObjChanged(stateObj) {
this.startInterval(stateObj);
}
clearInterval() {
if (this._updateRemaining) {
clearInterval(this._updateRemaining);
this._updateRemaining = null;
}
}
startInterval(stateObj) {
this.clearInterval();
this.calculateRemaining(stateObj);
if (stateObj.state === "active") {
this._updateRemaining = setInterval(
() => this.calculateRemaining(this.stateObj),
1000
);
}
}
calculateRemaining(stateObj) {
this.timeRemaining = timerTimeRemaining(stateObj);
}
_displayState(timeRemaining, stateObj) {
return computeDisplayTimer(this.hass, stateObj, timeRemaining);
}
}
customElements.define("state-card-timer", StateCardTimer);

View File

@ -0,0 +1,108 @@
import type { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import { computeDisplayTimer, timerTimeRemaining } from "../data/timer";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-timer")
class StateCardTimer extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
@property() public timeRemaining?: number;
private _updateRemaining: any;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
></state-info>
<div class="state">
${this._displayState(this.timeRemaining, this.stateObj)}
</div>
</div>
`;
}
connectedCallback() {
super.connectedCallback();
this._startInterval(this.stateObj);
}
disconnectedCallback() {
super.disconnectedCallback();
this._clearInterval();
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj")) {
this._startInterval(this.stateObj);
}
}
private _clearInterval() {
if (this._updateRemaining) {
clearInterval(this._updateRemaining);
this._updateRemaining = null;
}
}
private _startInterval(stateObj) {
this._clearInterval();
this._calculateRemaining(stateObj);
if (stateObj.state === "active") {
this._updateRemaining = setInterval(
() => this._calculateRemaining(this.stateObj),
1000
);
}
}
private _calculateRemaining(stateObj) {
this.timeRemaining = timerTimeRemaining(stateObj);
}
private _displayState(timeRemaining, stateObj) {
return computeDisplayTimer(this.hass, stateObj, timeRemaining);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.state {
color: var(--primary-text-color);
margin-left: 16px;
text-align: right;
line-height: 40px;
white-space: nowrap;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-timer": StateCardTimer;
}
}

View File

@ -1,50 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/ha-entity-toggle";
import "../components/entity/state-info";
class StateCardToggle extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
ha-entity-toggle {
margin: -4px -16px -4px 0;
padding: 4px 16px;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-entity-toggle
state-obj="[[stateObj]]"
hass="[[hass]]"
></ha-entity-toggle>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
}
customElements.define("state-card-toggle", StateCardToggle);

View File

@ -0,0 +1,51 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/ha-entity-toggle";
import "../components/entity/state-info";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-toggle")
class StateCardToggle extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
>
</state-info>
<ha-entity-toggle
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-entity-toggle>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-entity-toggle {
margin: -4px -16px -4px 0;
padding: 4px 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-toggle": StateCardToggle;
}
}

View File

@ -1,44 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-vacuum-state";
class StateCardVacuum extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-vacuum-state
hass="[[hass]]"
state-obj="[[stateObj]]"
></ha-vacuum-state>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
}
customElements.define("state-card-vacuum", StateCardVacuum);

View File

@ -0,0 +1,44 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import "../components/ha-vacuum-state";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
@customElement("state-card-vacuum")
class StateCardVacuum extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
>
</state-info>
<ha-vacuum-state
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-vacuum-state>
</div>
`;
}
static get styles(): CSSResultGroup {
return [haStyle];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-vacuum": StateCardVacuum;
}
}

View File

@ -1,55 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/entity/state-info";
import "../components/ha-water_heater-state";
class StateCardWaterHeater extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
:host {
@apply --paper-font-body1;
line-height: 1.5;
}
ha-water_heater-state {
margin-left: 16px;
text-align: right;
}
</style>
<div class="horizontal justified layout">
${this.stateInfoTemplate}
<ha-water_heater-state
hass="[[hass]]"
state-obj="[[stateObj]]"
></ha-water_heater-state>
</div>
`;
}
static get stateInfoTemplate() {
return html`
<state-info
hass="[[hass]]"
state-obj="[[stateObj]]"
in-dialog="[[inDialog]]"
></state-info>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: false,
},
};
}
}
customElements.define("state-card-water_heater", StateCardWaterHeater);

View File

@ -0,0 +1,55 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import "../components/ha-water_heater-state";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@customElement("state-card-water_heater")
class StateCardWaterHeater extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${this.stateObj}
.inDialog=${this.inDialog}
>
</state-info>
<ha-water_heater-state
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-water_heater-state>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
line-height: 1.5;
}
ha-water_heater-state {
margin-left: 16px;
text-align: right;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-water_heater": StateCardWaterHeater;
}
}

View File

@ -1,12 +1,19 @@
import { HassEntity } from "home-assistant-js-websocket";
import { supportsFeature } from "../common/entity/supports-feature";
import { cleanupMediaTitle } from "../data/media-player";
import { HomeAssistant } from "../types";
export default class MediaPlayerEntity {
constructor(hass, stateObj) {
public hass: HomeAssistant;
public stateObj: HassEntity;
private _attr: { [key: string]: any };
constructor(hass: HomeAssistant, stateObj: HassEntity) {
this.hass = hass;
this.stateObj = stateObj;
this._attr = stateObj.attributes;
this._feat = this._attr.supported_features;
}
get isOff() {
@ -219,7 +226,7 @@ export default class MediaPlayerEntity {
// helper method
callService(service, data = {}) {
callService(service, data: any = {}) {
data.entity_id = this.stateObj.entity_id;
this.hass.callService("media_player", service, data);
}