mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Migrate state cards from Polymer to Lit (#18257)
This commit is contained in:
parent
f4ee734ea3
commit
4582c3ba0a
@ -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
|
||||
|
3
gallery/src/pages/more-info/input-text.markdown
Normal file
3
gallery/src/pages/more-info/input-text.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Input Text
|
||||
---
|
46
gallery/src/pages/more-info/input-text.ts
Normal file
46
gallery/src/pages/more-info/input-text.ts
Normal 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;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/lock.markdown
Normal file
3
gallery/src/pages/more-info/lock.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Lock
|
||||
---
|
49
gallery/src/pages/more-info/lock.ts
Normal file
49
gallery/src/pages/more-info/lock.ts
Normal 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;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/media-player.markdown
Normal file
3
gallery/src/pages/more-info/media-player.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Media Player
|
||||
---
|
41
gallery/src/pages/more-info/media-player.ts
Normal file
41
gallery/src/pages/more-info/media-player.ts
Normal 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;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/number.markdown
Normal file
3
gallery/src/pages/more-info/number.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Number
|
||||
---
|
78
gallery/src/pages/more-info/number.ts
Normal file
78
gallery/src/pages/more-info/number.ts
Normal 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;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/scene.markdown
Normal file
3
gallery/src/pages/more-info/scene.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Scene
|
||||
---
|
49
gallery/src/pages/more-info/scene.ts
Normal file
49
gallery/src/pages/more-info/scene.ts
Normal 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;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/timer.markdown
Normal file
3
gallery/src/pages/more-info/timer.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Timer
|
||||
---
|
46
gallery/src/pages/more-info/timer.ts
Normal file
46
gallery/src/pages/more-info/timer.ts
Normal 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;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/vacuum.markdown
Normal file
3
gallery/src/pages/more-info/vacuum.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Vacuum
|
||||
---
|
50
gallery/src/pages/more-info/vacuum.ts
Normal file
50
gallery/src/pages/more-info/vacuum.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -125,7 +125,7 @@ class StateInfo extends LitElement {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.name[in-dialog],
|
||||
.name[inDialog],
|
||||
:host([secondary-line]) .name {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
@ -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);
|
111
src/components/ha-vacuum-state.ts
Normal file
111
src/components/ha-vacuum-state.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
@ -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);
|
@ -65,7 +65,7 @@ export class MoreInfoInfo extends LitElement {
|
||||
? ""
|
||||
: html`
|
||||
<state-card-content
|
||||
in-dialog
|
||||
inDialog
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></state-card-content>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
54
src/state-summary/state-card-climate.ts
Normal file
54
src/state-summary/state-card-climate.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
59
src/state-summary/state-card-configurator.ts
Normal file
59
src/state-summary/state-card-configurator.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
65
src/state-summary/state-card-content.ts
Normal file
65
src/state-summary/state-card-content.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
56
src/state-summary/state-card-humidifier.ts
Normal file
56
src/state-summary/state-card-humidifier.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
89
src/state-summary/state-card-input_text.ts
Normal file
89
src/state-summary/state-card-input_text.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
83
src/state-summary/state-card-lock.ts
Normal file
83
src/state-summary/state-card-lock.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
83
src/state-summary/state-card-media_player.ts
Normal file
83
src/state-summary/state-card-media_player.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
168
src/state-summary/state-card-number.ts
Normal file
168
src/state-summary/state-card-number.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
56
src/state-summary/state-card-scene.ts
Normal file
56
src/state-summary/state-card-scene.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
108
src/state-summary/state-card-timer.ts
Normal file
108
src/state-summary/state-card-timer.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
51
src/state-summary/state-card-toggle.ts
Normal file
51
src/state-summary/state-card-toggle.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
44
src/state-summary/state-card-vacuum.ts
Normal file
44
src/state-summary/state-card-vacuum.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
55
src/state-summary/state-card-water_heater.ts
Normal file
55
src/state-summary/state-card-water_heater.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user