Merge pull request #3030 from home-assistant/dev

20190327.0
This commit is contained in:
Paulus Schoutsen 2019-03-27 21:31:53 -07:00 committed by GitHub
commit 4e6d00cf5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 4090 additions and 1446 deletions

View File

@ -17,9 +17,9 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@material/mwc-button": "^0.3.6",
"@material/mwc-ripple": "^0.3.6",
"@mdi/svg": "^3.0.39",
"@material/mwc-button": "^0.5.0",
"@material/mwc-ripple": "^0.5.0",
"@mdi/svg": "3.5.95",
"@polymer/app-layout": "^3.0.1",
"@polymer/app-localize-behavior": "^3.0.1",
"@polymer/app-route": "^3.0.2",
@ -34,6 +34,7 @@
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-media-query": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-pages": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/neon-animation": "^3.0.1",
@ -79,7 +80,7 @@
"jquery": "^3.3.1",
"js-yaml": "^3.12.0",
"leaflet": "^1.3.4",
"lit-element": "^2.0.0",
"lit-element": "^2.1.0",
"lit-html": "^1.0.0",
"marked": "^0.6.0",
"mdn-polyfills": "^5.12.0",
@ -167,7 +168,8 @@
"@webcomponents/webcomponentsjs": "^2.2.6",
"@webcomponents/shadycss": "^1.9.0",
"@vaadin/vaadin-overlay": "3.2.2",
"@vaadin/vaadin-lumo-styles": "1.3.0"
"@vaadin/vaadin-lumo-styles": "1.3.0",
"@polymer/iron-overlay-behavior": "^3.0.2"
},
"main": "src/home-assistant.js",
"husky": {

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20190321.0",
version="20190327.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@ -2,6 +2,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import "../components/ha-form";
import "../components/ha-markdown";
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
class HaAuthFlow extends localizeLiteMixin(PolymerElement) {
@ -121,6 +122,12 @@ class HaAuthFlow extends localizeLiteMixin(PolymerElement) {
const data = await response.json();
if (response.ok) {
// allow auth provider bypass the login form
if (data.type === "create_entry") {
this._redirect(data.result);
return;
}
this._updateStep(data);
} else {
this.setProperties({
@ -138,6 +145,24 @@ class HaAuthFlow extends localizeLiteMixin(PolymerElement) {
}
}
_redirect(authCode) {
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
let url = this.redirectUri;
if (!url.includes("?")) {
url += "?";
} else if (!url.endsWith("&")) {
url += "&";
}
url += `code=${encodeURIComponent(authCode)}`;
if (this.oauth2State) {
url += `&state=${encodeURIComponent(this.oauth2State)}`;
}
document.location = url;
}
_updateStep(step) {
const props = {
_step: step,
@ -229,21 +254,7 @@ class HaAuthFlow extends localizeLiteMixin(PolymerElement) {
const newStep = await response.json();
if (newStep.type === "create_entry") {
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
let url = this.redirectUri;
if (!url.includes("?")) {
url += "?";
} else if (!url.endsWith("&")) {
url += "&";
}
url += `code=${encodeURIComponent(newStep.result)}`;
if (this.oauth2State) {
url += `&state=${encodeURIComponent(this.oauth2State)}`;
}
document.location = url;
this._redirect(newStep.result);
return;
}
this._updateStep(newStep);

View File

@ -132,6 +132,10 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
height: 44px;
}
.playback-controls {
direction: ltr;
}
paper-icon-button {
opacity: var(--dark-primary-opacity);
}
@ -186,7 +190,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
class="self-center secondary"
></paper-icon-button>
<div>
<div class="playback-controls">
<paper-icon-button
icon="hass:skip-previous"
invisible$="[[!playerObj.supportsPreviousTrack]]"

View File

@ -2,11 +2,17 @@ import {
LitElement,
html,
PropertyValues,
PropertyDeclarations,
TemplateResult,
css,
CSSResult,
customElement,
property,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket";
import { classMap } from "lit-html/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import { HomeAssistant } from "../../types";
import computeStateDomain from "../../common/entity/compute_state_domain";
import computeStateName from "../../common/entity/compute_state_name";
@ -14,21 +20,20 @@ import domainIcon from "../../common/entity/domain_icon";
import stateIcon from "../../common/entity/state_icon";
import timerTimeRemaining from "../../common/entity/timer_time_remaining";
import secondsToDuration from "../../common/datetime/seconds_to_duration";
import { fireEvent } from "../../common/dom/fire_event";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
/*
* @appliesMixin LocalizeMixin
* @appliesMixin EventsMixin
*/
@customElement("ha-state-label-badge")
export class HaStateLabelBadge extends LitElement {
public hass?: HomeAssistant;
public state?: HassEntity;
@property() public hass?: HomeAssistant;
@property() public state?: HassEntity;
@property() private _timerTimeRemaining?: number;
private _connected?: boolean;
private _updateRemaining?: number;
private _timerTimeRemaining?: number;
public connectedCallback(): void {
super.connectedCallback();
@ -47,15 +52,20 @@ export class HaStateLabelBadge extends LitElement {
if (!state) {
return html`
${this.renderStyle()}
<ha-label-badge label="not found"></ha-label-badge>
<ha-label-badge
class="warning"
label="${this.hass!.localize("state_badge.default.error")}"
icon="hass:alert"
description="${this.hass!.localize(
"state_badge.default.entity_not_found"
)}"
></ha-label-badge>
`;
}
const domain = computeStateDomain(state);
return html`
${this.renderStyle()}
<ha-label-badge
class="${classMap({
[domain]: true,
@ -70,14 +80,6 @@ export class HaStateLabelBadge extends LitElement {
`;
}
static get properties(): PropertyDeclarations {
return {
hass: {},
state: {},
_timerTimeRemaining: {},
};
}
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this.addEventListener("click", (ev) => {
@ -201,9 +203,8 @@ export class HaStateLabelBadge extends LitElement {
this._timerTimeRemaining = timerTimeRemaining(stateObj);
}
private renderStyle(): TemplateResult {
return html`
<style>
static get styles(): CSSResult {
return css`
:host {
cursor: pointer;
}
@ -237,12 +238,12 @@ export class HaStateLabelBadge extends LitElement {
}
.grey {
--ha-label-badge-color: var(
--label-badge-grey,
var(--paper-grey-500)
);
--ha-label-badge-color: var(--label-badge-grey, var(--paper-grey-500));
}
.warning {
--ha-label-badge-color: var(--label-badge-yellow, #fce588);
}
</style>
`;
}
}
@ -252,5 +253,3 @@ declare global {
"ha-state-label-badge": HaStateLabelBadge;
}
}
customElements.define("ha-state-label-badge", HaStateLabelBadge);

View File

@ -25,6 +25,7 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
#target_temperature {
@apply --layout-self-center;
font-size: 200%;
direction: ltr;
}
.control-buttons {
font-size: 200%;

View File

@ -29,18 +29,24 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
font-weight: bold;
text-transform: capitalize;
}
.unit {
display: inline-block;
direction: ltr;
}
</style>
<div class="target">
<template is="dom-if" if="[[_hasKnownState(stateObj.state)]]">
<span class="state-label"> [[_localizeState(stateObj.state)]] </span>
</template>
[[computeTarget(hass, stateObj)]]
<div class="unit">[[computeTarget(hass, stateObj)]]</div>
</div>
<template is="dom-if" if="[[currentStatus]]">
<div class="current">
[[localize('ui.card.climate.currently')]]: [[currentStatus]]
[[localize('ui.card.climate.currently')]]:
<div class="unit">[[currentStatus]]</div>
</div>
</template>
`;

View File

@ -58,6 +58,11 @@ class HaPaperSlider extends PaperSliderClass {
-webkit-transform: scale(1) translate(0, -10px);
transform: scale(1) translate(0, -10px);
}
:host([dir="rtl"]) .pin.expand > .slider-knob > .slider-knob-inner::after {
-webkit-transform: scale(1) translate(0, -17px) scaleX(-1) !important;
transform: scale(1) translate(0, -17px) scaleX(-1) !important;
}
`;
tpl.content.appendChild(styleEl);
return tpl;

View File

@ -26,19 +26,13 @@ const computePanels = (hass: HomeAssistant) => {
if (!panels) {
return [];
}
const isAdmin = hass.user.is_admin;
const sortValue = {
map: 1,
logbook: 2,
history: 3,
};
const result: Panel[] = [];
Object.values(panels).forEach((panel) => {
if (panel.title && (panel.component_name !== "config" || isAdmin)) {
result.push(panel);
}
});
const result: Panel[] = Object.values(panels).filter((panel) => panel.title);
result.sort((a, b) => {
const aBuiltIn = a.component_name in sortValue;
@ -133,9 +127,8 @@ class HaSidebar extends LitElement {
: html``}
</paper-listbox>
${!hass.user.is_admin
? ""
: html`
${hass.user && hass.user.is_admin
? html`
<div>
<div class="divider"></div>
@ -192,7 +185,8 @@ class HaSidebar extends LitElement {
</a>
</div>
</div>
`}
`
: ""}
`;
}

8
src/data/panel_custom.ts Normal file
View File

@ -0,0 +1,8 @@
export interface CustomPanelConfig {
name: string;
embed_iframe: boolean;
trust_external: boolean;
js_url?: string;
module_url?: string;
html_url?: string;
}

View File

@ -16,6 +16,7 @@ export interface ZHADevice {
manufacturer_code: number;
device_reg_id: string;
user_given_name: string;
area_id: string;
}
export interface Attribute {
@ -42,7 +43,7 @@ export interface ReadAttributeServiceData {
cluster_id: number;
cluster_type: string;
attribute: number;
manufacturer: number;
manufacturer?: number;
}
export const reconfigureNode = (

View File

@ -17,6 +17,7 @@ import { supportsFeature } from "../../../common/entity/supports-feature";
import EventsMixin from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
/*
* @appliesMixin EventsMixin
@ -84,6 +85,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
width: 90px;
font-size: 200%;
margin: auto;
direction: ltr;
}
ha-climate-control.range-control-left,
@ -181,6 +183,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
value="[[stateObj.attributes.humidity]]"
on-change="targetHumiditySliderChanged"
ignore-bar-touch=""
dir="[[rtl]]"
>
</ha-paper-slider>
</div>
@ -314,6 +317,12 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
awayToggleChecked: Boolean,
auxToggleChecked: Boolean,
onToggleChecked: Boolean,
rtl: {
type: String,
value: "ltr",
computed: "_computeRTLDirection(hass)",
},
};
}
@ -557,6 +566,10 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
_localizeFanMode(localize, mode) {
return localize(`state_attributes.climate.fan_mode.${mode}`) || mode;
}
_computeRTLDirection(hass) {
return computeRTLDirection(hass);
}
}
customElements.define("more-info-climate", MoreInfoClimate);

View File

@ -14,6 +14,7 @@ import attributeClassNames from "../../../common/entity/attribute_class_names";
import isComponentLoaded from "../../../common/config/is_component_loaded";
import EventsMixin from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
/*
* @appliesMixin LocalizeMixin
@ -137,6 +138,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
on-change="volumeSliderChanged"
class="flex"
ignore-bar-touch=""
dir="{{rtl}}"
>
</ha-paper-slider>
</div>
@ -233,6 +235,11 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
type: String,
value: "",
},
rtl: {
type: String,
computed: "_computeRTLDirection(hass)",
},
};
}
@ -425,6 +432,10 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
this.ttsMessage = "";
this.$.ttsInput.focus();
}
_computeRTLDirection(hass) {
return computeRTLDirection(hass);
}
}
customElements.define("more-info-media_player", MoreInfoMediaPlayer);

View File

@ -1,80 +0,0 @@
import { loadJS } from "../common/dom/load_resource";
import loadCustomPanel from "../util/custom-panel/load-custom-panel";
import createCustomPanelElement from "../util/custom-panel/create-custom-panel-element";
import setCustomPanelProperties from "../util/custom-panel/set-custom-panel-properties";
const webComponentsSupported =
"customElements" in window &&
"import" in document.createElement("link") &&
"content" in document.createElement("template");
let es5Loaded = null;
window.loadES5Adapter = () => {
if (!es5Loaded) {
es5Loaded = Promise.all([
loadJS(`${__STATIC_PATH__}custom-elements-es5-adapter.js`).catch(),
import(/* webpackChunkName: "compat" */ "./compatibility"),
]);
}
return es5Loaded;
};
let root = null;
function setProperties(properties) {
if (root === null) return;
setCustomPanelProperties(root, properties);
}
function initialize(panel, properties) {
const style = document.createElement("style");
style.innerHTML = "body{margin:0}";
document.head.appendChild(style);
const config = panel.config._panel_custom;
let start = Promise.resolve();
if (!webComponentsSupported) {
start = start.then(() => loadJS("/static/webcomponents-bundle.js"));
}
if (__BUILD__ === "es5") {
// Load ES5 adapter. Swallow errors as it raises errors on old browsers.
start = start.then(() => window.loadES5Adapter());
}
start
.then(() => loadCustomPanel(config))
// If our element is using es5, let it finish loading that and define element
// This avoids elements getting upgraded after being added to the DOM
.then(() => es5Loaded || Promise.resolve())
.then(
() => {
root = createCustomPanelElement(config);
const forwardEvent = (ev) =>
window.parent.customPanel.fire(ev.type, ev.detail);
root.addEventListener("hass-toggle-menu", forwardEvent);
window.addEventListener("location-changed", (ev) =>
window.parent.customPanel.navigate(
window.location.pathname,
ev.detail ? ev.detail.replace : false
)
);
setProperties(Object.assign({ panel }, properties));
document.body.appendChild(root);
},
(err) => {
// eslint-disable-next-line
console.error(err, panel);
alert(`Unable to load the panel source: ${err}.`);
}
);
}
document.addEventListener(
"DOMContentLoaded",
() => window.parent.customPanel.registerIframe(initialize, setProperties),
{ once: true }
);

View File

@ -0,0 +1,97 @@
import { loadJS } from "../common/dom/load_resource";
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
import { createCustomPanelElement } from "../util/custom-panel/create-custom-panel-element";
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
import { fireEvent } from "../common/dom/fire_event";
import { navigate } from "../common/navigate";
import { PolymerElement } from "@polymer/polymer";
import { Panel } from "../types";
import { CustomPanelConfig } from "../data/panel_custom";
declare global {
interface Window {
loadES5Adapter: () => Promise<unknown>;
}
}
const webComponentsSupported =
"customElements" in window &&
"import" in document.createElement("link") &&
"content" in document.createElement("template");
let es5Loaded: Promise<unknown> | undefined;
window.loadES5Adapter = () => {
if (!es5Loaded) {
es5Loaded = Promise.all([
loadJS(`${__STATIC_PATH__}custom-elements-es5-adapter.js`).catch(),
import(/* webpackChunkName: "compat" */ "./compatibility"),
]);
}
return es5Loaded;
};
let panelEl: HTMLElement | PolymerElement | undefined;
function setProperties(properties) {
if (!panelEl) {
return;
}
setCustomPanelProperties(panelEl, properties);
}
function initialize(panel: Panel, properties: {}) {
const style = document.createElement("style");
style.innerHTML = "body{margin:0}";
document.head.appendChild(style);
const config = panel.config!._panel_custom as CustomPanelConfig;
let start: Promise<unknown> = Promise.resolve();
if (!webComponentsSupported) {
start = start.then(() => loadJS("/static/webcomponents-bundle.js"));
}
if (__BUILD__ === "es5") {
// Load ES5 adapter. Swallow errors as it raises errors on old browsers.
start = start.then(() => window.loadES5Adapter());
}
start
.then(() => loadCustomPanel(config))
// If our element is using es5, let it finish loading that and define element
// This avoids elements getting upgraded after being added to the DOM
.then(() => es5Loaded || Promise.resolve())
.then(
() => {
panelEl = createCustomPanelElement(config);
const forwardEvent = (ev) => {
if (window.parent.customPanel) {
fireEvent(window.parent.customPanel, ev.type, ev.detail);
}
};
panelEl!.addEventListener("hass-toggle-menu", forwardEvent);
window.addEventListener("location-changed", (ev: any) =>
navigate(
window.parent.customPanel,
window.location.pathname,
ev.detail ? ev.detail.replace : false
)
);
setProperties({ panel, ...properties });
document.body.appendChild(panelEl!);
},
(err) => {
// tslint:disable-next-line
console.error(err, panel);
alert(`Unable to load the panel source: ${err}.`);
}
);
}
document.addEventListener(
"DOMContentLoaded",
() => window.parent.customPanel!.registerIframe(initialize, setProperties),
{ once: true }
);

View File

@ -73,9 +73,9 @@ class HaPanelConfig extends HassRouterPage {
import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"),
},
zha: {
tag: "ha-config-zha",
tag: "zha-config-panel",
load: () =>
import(/* webpackChunkName: "panel-config-zha" */ "./zha/ha-config-zha"),
import(/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"),
},
zwave: {
tag: "ha-config-zwave",

View File

@ -180,9 +180,9 @@ class HaConfigPerson extends LitElement {
},
removeEntry: async () => {
if (
!confirm(`Are you sure you want to delete this area?
!confirm(`Are you sure you want to delete this person?
All devices in this area will become unassigned.`)
All devices belonging to this person will become unassigned.`)
) {
return false;
}

View File

@ -1,26 +1,26 @@
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "../../../components/ha-paper-icon-button-arrow-prev";
import "../../../layouts/hass-subpage";
import "./zha-binding";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-network";
import "./zha-node";
import "@polymer/paper-icon-button/paper-icon-button";
import {
CSSResult,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
CSSResult,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { Cluster, ZHADevice, fetchBindableDevices } from "../../../data/zha";
import "../../../layouts/ha-app-layout";
import "../../../components/ha-paper-icon-button-arrow-prev";
import { Cluster, fetchBindableDevices, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-network";
import "./zha-node";
import "./zha-binding";
export class HaConfigZha extends LitElement {
@property() public hass?: HomeAssistant;
@ -38,16 +38,7 @@ export class HaConfigZha extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-app-layout>
<app-header slot="header">
<app-toolbar>
<ha-paper-icon-button-arrow-prev
@click="${this._onBackTapped}"
></ha-paper-icon-button-arrow-prev>
<div main-title>Zigbee Home Automation</div>
</app-toolbar>
</app-header>
<hass-subpage header="Zigbee Home Automation">
<zha-network
.isWide="${this.isWide}"
.hass="${this.hass}"
@ -86,7 +77,7 @@ export class HaConfigZha extends LitElement {
></zha-binding-control>
`
: ""}
</ha-app-layout>
</hass-subpage>
`;
}
@ -117,10 +108,6 @@ export class HaConfigZha extends LitElement {
static get styles(): CSSResult[] {
return [haStyle];
}
private _onBackTapped(): void {
history.back();
}
}
declare global {

View File

@ -8,6 +8,12 @@ export interface ItemSelectedEvent {
target?: PickerTarget;
}
export interface ZHADeviceRemovedEvent {
detail?: {
device?: ZHADevice;
};
}
export interface ChangeEvent {
detail?: {
value?: any;
@ -22,7 +28,7 @@ export interface SetAttributeServiceData {
cluster_type: string;
attribute: number;
value: any;
manufacturer: number;
manufacturer?: number;
}
export interface IssueCommandServiceData {

View File

@ -0,0 +1,246 @@
import "../../../components/ha-service-description";
import "../../../components/ha-textarea";
import "../../../layouts/hass-subpage";
import "./zha-device-card";
import "@material/mwc-button";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-spinner/paper-spinner";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
@customElement("zha-add-devices-page")
class ZHAAddDevicesPage extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() private _error?: string;
@property() private _discoveredDevices: ZHADevice[] = [];
@property() private _formattedEvents: string = "";
@property() private _active: boolean = false;
@property() private _showHelp: boolean = false;
private _addDevicesTimeoutHandle: any = undefined;
private _subscribed?: Promise<() => Promise<void>>;
public connectedCallback(): void {
super.connectedCallback();
this._subscribe();
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._unsubscribe();
this._error = undefined;
this._discoveredDevices = [];
this._formattedEvents = "";
}
protected render(): TemplateResult | void {
return html`
<hass-subpage
header="${this.hass!.localize(
"ui.panel.config.zha.add_device_page.header"
)}"
>
${this._active
? html`
<h2>
<paper-spinner
?active="${this._active}"
alt="Searching"
></paper-spinner>
${this.hass!.localize(
"ui.panel.config.zha.add_device_page.spinner"
)}
</h2>
`
: html`
<div class="card-actions">
<mwc-button @click=${this._subscribe} class="search-button">
Search again
</mwc-button>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
></paper-icon-button>
${this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="permit"
class="help-text"
/>
`
: ""}
</div>
`}
${this._error
? html`
<div class="error">${this._error}</div>
`
: ""}
<div class="content-header"></div>
<div class="content">
${this._discoveredDevices.length < 1
? html`
<div class="discovery-text">
<h4>
${this.hass!.localize(
"ui.panel.config.zha.add_device_page.discovery_text"
)}
</h4>
</div>
`
: html`
${this._discoveredDevices.map(
(device) => html`
<zha-device-card
class="card"
.hass="${this.hass}"
.device="${device}"
.narrow="${!this.isWide}"
.showHelp="${this._showHelp}"
.showActions="${!this._active}"
.isJoinPage="${true}"
></zha-device-card>
`
)}
`}
</div>
<ha-textarea class="events" value="${this._formattedEvents}">
</ha-textarea>
</hass-subpage>
`;
}
private _handleMessage(message: any): void {
if (message.type === "log_output") {
this._formattedEvents += message.log_entry.message + "\n";
if (this.shadowRoot) {
const textArea = this.shadowRoot.querySelector("ha-textarea");
if (textArea) {
textArea.scrollTop = textArea.scrollHeight;
}
}
}
if (message.type && message.type === "device_fully_initialized") {
this._discoveredDevices.push(message.device_info);
}
}
private _unsubscribe(): void {
this._active = false;
if (this._addDevicesTimeoutHandle) {
clearTimeout(this._addDevicesTimeoutHandle);
}
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
this._subscribed = undefined;
}
}
private _subscribe(): void {
this._subscribed = this.hass!.connection.subscribeMessage(
(message) => this._handleMessage(message),
{ type: "zha/devices/permit" }
);
this._active = true;
this._addDevicesTimeoutHandle = setTimeout(
() => this._unsubscribe(),
60000
);
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.discovery-text,
.content-header {
margin: 16px;
}
.content {
border-top: 1px solid var(--light-primary-color);
min-height: 500px;
display: flex;
flex-wrap: wrap;
padding: 4px;
justify-content: left;
overflow: scroll;
}
.error {
color: var(--google-red-500);
}
paper-spinner {
display: none;
margin-right: 20px;
margin-left: 16px;
}
paper-spinner[active] {
display: block;
float: left;
margin-right: 20px;
margin-left: 16px;
}
.card {
margin-left: 16px;
margin-right: 16px;
margin-bottom: 0px;
margin-top: 10px;
}
.events {
margin: 16px;
border-top: 1px solid var(--light-primary-color);
padding-top: 16px;
min-height: 200px;
max-height: 200px;
overflow: scroll;
}
.toggle-help-icon {
position: absolute;
margin-top: 16px;
margin-right: 16px;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
margin-top: 16px;
margin-left: 16px;
display: block;
color: grey;
}
.search-button {
margin-top: 16px;
margin-left: 16px;
}
.help-text {
color: grey;
padding-left: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-add-devices-page": ZHAAddDevicesPage;
}
}

View File

@ -1,20 +1,26 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../ha-config-section";
import "@material/mwc-button/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
CSSResult,
css,
customElement,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import { ZHADevice, bindDevices, unbindDevices } from "../../../data/zha";
import { bindDevices, unbindDevices, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ItemSelectedEvent } from "./types";
@customElement("zha-binding-control")

View File

@ -1,17 +1,24 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../ha-config-section";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Attribute,
Cluster,
@ -22,13 +29,12 @@ import {
} from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { formatAsPaddedHex } from "./functions";
import {
ChangeEvent,
ItemSelectedEvent,
SetAttributeServiceData,
} from "./types";
import { formatAsPaddedHex } from "./functions";
export class ZHAClusterAttributes extends LitElement {
public hass?: HomeAssistant;
@ -115,7 +121,7 @@ export class ZHAClusterAttributes extends LitElement {
</div>
${this.showHelp
? html`
<div style="color: grey; padding: 16px">
<div class="help-text">
Select an attribute to view or set its value
</div>
`
@ -152,6 +158,13 @@ export class ZHAClusterAttributes extends LitElement {
<mwc-button @click="${this._onGetZigbeeAttributeClick}"
>Get Zigbee Attribute</mwc-button
>
${this.showHelp
? html`
<div class="help-text2">
Get the value for the selected attribute
</div>
`
: ""}
<ha-call-service-button
.hass="${this.hass}"
domain="zha"
@ -165,6 +178,7 @@ export class ZHAClusterAttributes extends LitElement {
.hass="${this.hass}"
domain="zha"
service="set_zigbee_cluster_attribute"
class="help-text2"
></ha-service-description>
`
: ""}
@ -201,7 +215,7 @@ export class ZHAClusterAttributes extends LitElement {
attribute: this._attributes[this._selectedAttributeIndex].id,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.manufacturer_code,
: undefined,
};
}
@ -220,7 +234,7 @@ export class ZHAClusterAttributes extends LitElement {
value: this._attributeValue,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.manufacturer_code,
: undefined,
};
}
@ -312,6 +326,16 @@ export class ZHAClusterAttributes extends LitElement {
[hidden] {
display: none;
}
.help-text {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 16px;
}
.help-text2 {
color: grey;
padding: 16px;
}
`,
];
}

View File

@ -1,15 +1,23 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../ha-config-section";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Cluster,
Command,
@ -18,13 +26,12 @@ import {
} from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { formatAsPaddedHex } from "./functions";
import {
ChangeEvent,
IssueCommandServiceData,
ItemSelectedEvent,
} from "./types";
import { formatAsPaddedHex } from "./functions";
export class ZHAClusterCommands extends LitElement {
public hass?: HomeAssistant;
@ -107,7 +114,7 @@ export class ZHAClusterCommands extends LitElement {
</div>
${this._showHelp
? html`
<div class="helpText">Select a command to interact with</div>
<div class="help-text">Select a command to interact with</div>
`
: ""}
${this._selectedCommandIndex !== -1
@ -135,6 +142,7 @@ export class ZHAClusterCommands extends LitElement {
.hass="${this.hass}"
domain="zha"
service="issue_zigbee_cluster_command"
class="help-text2"
></ha-service-description>
`
: ""}
@ -242,7 +250,14 @@ export class ZHAClusterCommands extends LitElement {
position: relative;
}
.helpText {
.help-text {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 16px;
}
.help-text2 {
color: grey;
padding: 16px;
}

View File

@ -1,22 +1,27 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../ha-config-section";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ItemSelectedEvent } from "./types";
import { formatAsPaddedHex } from "./functions";
import { ItemSelectedEvent } from "./types";
declare global {
// for fire event
@ -90,7 +95,7 @@ export class ZHAClusters extends LitElement {
</div>
${this.showHelp
? html`
<div class="helpText">
<div class="help-text">
Select cluster to view attributes and commands
</div>
`
@ -143,9 +148,11 @@ export class ZHAClusters extends LitElement {
padding-right: 28px;
padding-bottom: 10px;
}
.helpText {
.help-text {
color: grey;
padding: 16px;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 16px;
}
`,
];

View File

@ -0,0 +1,70 @@
import "../../../layouts/hass-loading-screen";
import { customElement, property } from "lit-element";
import { listenMediaQuery } from "../../../common/dom/media_query";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { HomeAssistant } from "../../../types";
@customElement("zha-config-panel")
class ZHAConfigPanel extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public _wideSidebar: boolean = false;
@property() public _wide: boolean = false;
protected routerOptions: RouterOptions = {
defaultPage: "configuration",
cacheAll: true,
preloadAll: true,
routes: {
configuration: {
tag: "ha-config-zha",
load: () =>
import(/* webpackChunkName: "zha-configuration-page" */ "./ha-config-zha"),
},
add: {
tag: "zha-add-devices-page",
load: () =>
import(/* webpackChunkName: "zha-add-devices-page" */ "./zha-add-devices-page"),
},
},
};
private _listeners: Array<() => void> = [];
public connectedCallback(): void {
super.connectedCallback();
this._listeners.push(
listenMediaQuery("(min-width: 1040px)", (matches) => {
this._wide = matches;
})
);
this._listeners.push(
listenMediaQuery("(min-width: 1296px)", (matches) => {
this._wideSidebar = matches;
})
);
}
public disconnectedCallback(): void {
super.disconnectedCallback();
while (this._listeners.length) {
this._listeners.pop()!();
}
}
protected updatePageEl(el): void {
el.route = this.routeTail;
el.hass = this.hass;
el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-config-panel": ZHAConfigPanel;
}
}

View File

@ -1,40 +1,123 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/entity/state-badge";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { fireEvent } from "../../../common/dom/fire_event";
import compare from "../../../common/string/compare";
import {
AreaRegistryEntry,
fetchAreaRegistry,
} from "../../../data/area_registry";
import {
DeviceRegistryEntryMutableParams,
updateDeviceRegistryEntry,
} from "../../../data/device_registry";
import { reconfigureNode, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { ItemSelectedEvent, NodeServiceData } from "./types";
import "../../../components/entity/state-badge";
import { ZHADevice } from "../../../data/zha";
declare global {
// for fire event
interface HASSDomEvents {
"zha-device-removed": {
device?: ZHADevice;
};
}
}
@customElement("zha-device-card")
class ZHADeviceCard extends LitElement {
@property() public hass?: HomeAssistant;
@property() public narrow?: boolean;
@property() public device?: ZHADevice;
@property() public showHelp: boolean = false;
@property() public showActions?: boolean;
@property() public isJoinPage?: boolean;
@property() private _serviceData?: NodeServiceData;
@property() private _areas: AreaRegistryEntry[] = [];
@property() private _selectedAreaIndex: number = -1;
public firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
this._serviceData = {
ieee_address: this.device!.ieee,
};
fetchAreaRegistry(this.hass!).then((areas) => {
this._areas = areas.sort((a, b) => compare(a.name, b.name));
});
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("device")) {
this._selectedAreaIndex =
this._areas.findIndex((area) => area.area_id === this.device!.area_id) +
1;
}
super.update(changedProperties);
}
protected serviceCalled(ev): void {
// Check if this is for us
if (ev.detail.success && ev.detail.service === "remove") {
fireEvent(this, "zha-device-removed", {
device: this.device,
});
}
}
protected render(): TemplateResult | void {
return html`
<paper-card>
<paper-card heading="${this.isJoinPage ? this.device!.name : ""}">
${
this.isJoinPage
? html`
<div class="info">
<div class="model">${this.device!.model}</div>
<div class="manuf">
${this.hass!.localize(
"ui.panel.config.integrations.config_entry.manuf",
"manufacturer",
this.device!.manufacturer
)}
</div>
</div>
`
: ""
}
<div class="card-content">
<dl>
<dt class="label">IEEE:</dt>
<dd class="info">${this.device!.ieee}</dd>
<dt class="label">Quirk applied:</dt>
<dd class="info">${this.device!.quirk_applied}</dd>
<dt class="label">Quirk:</dt>
<dd class="info">${this.device!.quirk_class}</dd>
<dt>IEEE:</dt>
<dd class="zha-info">${this.device!.ieee}</dd>
${
this.device!.quirk_applied
? html`
<dt>Quirk:</dt>
<dd class="zha-info">${this.device!.quirk_class}</dd>
`
: ""
}
</dl>
</div>
@ -49,24 +132,145 @@ class ZHADeviceCard extends LitElement {
.stateObj="${this.hass!.states[entity.entity_id]}"
slot="item-icon"
></state-badge>
${!this.isJoinPage
? html`
<paper-item-body>
<div class="name">${entity.name}</div>
<div class="secondary entity-id">${entity.entity_id}</div>
<div class="secondary entity-id">
${entity.entity_id}
</div>
</paper-item-body>
`
: ""}
</paper-icon-item>
`
)}
</div>
<div class="editable">
<paper-input
type="string"
@change="${this._saveCustomName}"
placeholder="${this.hass!.localize(
"ui.panel.config.zha.device_card.device_name_placeholder"
)}"
></paper-input>
</div>
<div class="node-picker">
<paper-dropdown-menu
label="${this.hass!.localize(
"ui.panel.config.zha.device_card.area_picker_label"
)}"
class="flex"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedAreaIndex}"
@iron-select="${this._selectedAreaChanged}"
>
<paper-item>
${this.hass!.localize(
"ui.panel.config.integrations.config_entry.no_area"
)}
</paper-item>
${this._areas.map(
(entry) => html`
<paper-item area="${entry}">${entry.name}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${
this.showActions
? html`
<div class="card-actions">
<mwc-button @click="${this._onReconfigureNodeClick}"
>Reconfigure Device</mwc-button
>
${this.showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.services.reconfigure"
)}
</div>
`
: ""}
<ha-call-service-button
.hass="${this.hass}"
domain="zha"
service="remove"
.serviceData="${this._serviceData}"
>Remove Device</ha-call-service-button
>
${this.showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.services.remove"
)}
</div>
`
: ""}
</div>
`
: ""
}
</div>
</paper-card>
`;
}
private async _onReconfigureNodeClick(): Promise<void> {
if (this.hass) {
await reconfigureNode(this.hass, this.device!.ieee);
}
}
private async _saveCustomName(event): Promise<void> {
if (this.hass) {
const values: DeviceRegistryEntryMutableParams = {
name_by_user: event.target.value,
area_id: this.device!.area_id ? this.device!.area_id : undefined,
};
await updateDeviceRegistryEntry(
this.hass,
this.device!.device_reg_id,
values
);
this.device!.user_given_name = event.target.value;
}
}
private _openMoreInfo(ev: MouseEvent): void {
fireEvent(this, "hass-more-info", {
entityId: (ev.currentTarget as any).entity.entity_id,
});
}
private async _selectedAreaChanged(event: ItemSelectedEvent) {
if (!this.device || !this._areas) {
return;
}
this._selectedAreaIndex = event!.target!.selected;
const area = this._areas[this._selectedAreaIndex - 1]; // account for No Area
if (
(!area && !this.device.area_id) ||
(area && area.area_id === this.device.area_id)
) {
return;
}
await updateDeviceRegistryEntry(this.hass!, this.device.device_reg_id, {
area_id: area ? area.area_id : undefined,
name_by_user: this.device!.user_given_name,
});
}
static get styles(): CSSResult[] {
return [
haStyle,
@ -74,29 +278,43 @@ class ZHADeviceCard extends LitElement {
:host(:not([narrow])) .device-entities {
max-height: 225px;
overflow: auto;
display: flex;
flex-wrap: wrap;
padding: 4px;
justify-content: left;
}
paper-card {
flex: 1 0 100%;
padding-bottom: 10px;
min-width: 0;
min-width: 425px;
}
.device {
width: 30%;
}
.label {
.device .name {
font-weight: bold;
}
.device .manuf {
color: var(--secondary-text-color);
}
.extra-info {
margin-top: 8px;
}
.manuf,
.zha-info,
.entity-id {
color: var(--secondary-text-color);
}
.info {
color: var(--secondary-text-color);
font-weight: bold;
margin-left: 16px;
}
dl dt {
padding-left: 12px;
float: left;
width: 100px;
width: 50px;
text-align: left;
}
dt dd {
margin-left: 10px;
text-align: left;
}
paper-icon-item {
@ -104,6 +322,36 @@ class ZHADeviceCard extends LitElement {
padding-top: 4px;
padding-bottom: 4px;
}
.editable {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.help-text {
color: grey;
padding: 16px;
}
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
.node-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
`,
];
}
@ -114,5 +362,3 @@ declare global {
"zha-device-card": ZHADeviceCard;
}
}
customElements.define("zha-device-card", ZHADeviceCard);

View File

@ -1,19 +1,22 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../ha-config-section";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import {
css,
CSSResult,
html,
LitElement,
PropertyDeclarations,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import { navigate } from "../../../common/navigate";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
export class ZHANetwork extends LitElement {
public hass?: HomeAssistant;
@ -30,6 +33,7 @@ export class ZHANetwork extends LitElement {
hass: {},
isWide: {},
_showHelp: {},
_joinParams: {},
};
}
@ -38,28 +42,30 @@ export class ZHANetwork extends LitElement {
<ha-config-section .isWide="${this.isWide}">
<div style="position: relative" slot="header">
<span>Network Management</span>
<paper-icon-button class="toggle-help-icon" @click="${
this._onHelpTap
}" icon="hass:help-circle"></paper-icon-button>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
></paper-icon-button>
</div>
<span slot="introduction">Commands that affect entire network</span>
<paper-card class="content">
<div class="card-actions">
<ha-call-service-button .hass="${
this.hass
}" domain="zha" service="permit">Permit</ha-call-service-button>
${
this._showHelp
<mwc-button @click=${this._onAddDevicesClick}>
Add Devices
</mwc-button>
${this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="permit"
class="help-text2"
/>
`
: ""
}
: ""}
</div>
</paper-card>
</ha-config-section>
`;
@ -69,6 +75,10 @@ export class ZHANetwork extends LitElement {
this._showHelp = !this._showHelp;
}
private _onAddDevicesClick() {
navigate(this, "add");
}
static get styles(): CSSResult[] {
return [
haStyle,
@ -102,6 +112,11 @@ export class ZHANetwork extends LitElement {
[hidden] {
display: none;
}
.help-text2 {
color: grey;
padding: 16px;
}
`,
];
}

View File

@ -1,32 +1,30 @@
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
CSSResult,
PropertyValues,
css,
} from "lit-element";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../ha-config-section";
import "./zha-clusters";
import "./zha-device-card";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import { fetchDevices, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ItemSelectedEvent, NodeServiceData, ChangeEvent } from "./types";
import "./zha-clusters";
import "./zha-device-card";
import {
updateDeviceRegistryEntry,
DeviceRegistryEntryMutableParams,
} from "../../../data/device_registry";
import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha";
import { ItemSelectedEvent, ZHADeviceRemovedEvent } from "./types";
declare global {
// for fire event
@ -37,60 +35,25 @@ declare global {
}
}
@customElement("zha-node")
export class ZHANode extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
private _showHelp: boolean;
private _selectedNodeIndex: number;
private _selectedNode?: ZHADevice;
private _serviceData?: {};
private _nodes: ZHADevice[];
private _userSelectedName?: string;
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() private _showHelp: boolean = false;
@property() private _selectedDeviceIndex: number = -1;
@property() private _selectedDevice?: ZHADevice;
@property() private _nodes: ZHADevice[] = [];
constructor() {
super();
this._showHelp = false;
this._selectedNodeIndex = -1;
this._nodes = [];
}
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
_showHelp: {},
_selectedNodeIndex: {},
_selectedNode: {},
_entities: {},
_serviceData: {},
_nodes: {},
_userSelectedName: {},
};
}
public firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this._nodes.length === 0) {
public connectedCallback(): void {
super.connectedCallback();
this._fetchDevices();
}
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
}
protected serviceCalled(ev): void {
// Check if this is for us
if (ev.detail.success && ev.detail.service === "remove") {
this._selectedNodeIndex = -1;
this._fetchDevices();
}
}
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<span>Node Management</span>
<span>Device Management</span>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
@ -98,8 +61,8 @@ export class ZHANode extends LitElement {
></paper-icon-button>
</div>
<span slot="introduction">
Run ZHA commands that affect a single node. Pick a node to see a list
of available commands. <br /><br />Note: Sleepy (battery powered)
Run ZHA commands that affect a single device. Pick a device to see a
list of available commands. <br /><br />Note: Sleepy (battery powered)
devices need to be awake when executing commands against them. You can
generally wake a sleepy device by triggering it. <br /><br />Some
devices such as Xiaomi sensors have a wake up button that you can
@ -108,11 +71,15 @@ export class ZHANode extends LitElement {
</span>
<paper-card class="content">
<div class="node-picker">
<paper-dropdown-menu label="Nodes" class="flex">
<paper-dropdown-menu
label="Devices"
class="flex"
id="zha-device-selector"
>
<paper-listbox
slot="dropdown-content"
@iron-select="${this._selectedNodeChanged}"
.selected="${this._selectedNodeIndex}"
@iron-select="${this._selectedDeviceChanged}"
.selected="${this._selectedDeviceIndex}"
>
${this._nodes.map(
(entry) => html`
@ -128,95 +95,36 @@ export class ZHANode extends LitElement {
</div>
${this._showHelp
? html`
<div class="helpText">
Select node to view per-node options
<div class="help-text">
Select device to view per-device options
</div>
`
: ""}
${this._selectedNodeIndex !== -1
${this._selectedDeviceIndex !== -1
? html`
<zha-device-card
class="card"
.hass="${this.hass}"
.device="${this._selectedNode}"
.device="${this._selectedDevice}"
.narrow="${!this.isWide}"
.showHelp="${this._showHelp}"
.showActions="${true}"
@zha-device-removed="${this._onDeviceRemoved}"
.isJoinPage="${false}"
></zha-device-card>
`
: ""}
${this._selectedNodeIndex !== -1
? html`
<div class="input-text">
<paper-input
type="string"
.value="${this._userSelectedName}"
@value-changed="${this._onUserSelectedNameChanged}"
placeholder="User given name"
></paper-input>
</div>
`
: ""}
${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""}
${this._selectedNode ? this._renderClusters() : ""}
${this._selectedDevice ? this._renderClusters() : ""}
</paper-card>
</ha-config-section>
`;
}
private _renderNodeActions(): TemplateResult {
return html`
<div class="card-actions">
<mwc-button @click="${this._onReconfigureNodeClick}"
>Reconfigure Node</mwc-button
>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.services.reconfigure"
)}
</div>
`
: ""}
<ha-call-service-button
.hass="${this.hass}"
domain="zha"
service="remove"
.serviceData="${this._serviceData}"
>Remove Node</ha-call-service-button
>
${this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="remove"
/>
`
: ""}
<mwc-button
@click="${this._onUpdateDeviceNameClick}"
.disabled="${!this._userSelectedName ||
this._userSelectedName === ""}"
>Update Name</mwc-button
>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.services.updateDeviceName"
)}
</div>
`
: ""}
</div>
`;
}
private _renderClusters(): TemplateResult {
return html`
<zha-clusters
.hass="${this.hass}"
.selectedDevice="${this._selectedNode}"
.selectedDevice="${this._selectedDevice}"
.showHelp="${this._showHelp}"
></zha-clusters>
`;
@ -226,45 +134,10 @@ export class ZHANode extends LitElement {
this._showHelp = !this._showHelp;
}
private _selectedNodeChanged(event: ItemSelectedEvent): void {
this._selectedNodeIndex = event!.target!.selected;
this._selectedNode = this._nodes[this._selectedNodeIndex];
this._userSelectedName = "";
fireEvent(this, "zha-node-selected", { node: this._selectedNode });
this._serviceData = this._computeNodeServiceData();
}
private async _onReconfigureNodeClick(): Promise<void> {
if (this.hass) {
await reconfigureNode(this.hass, this._selectedNode!.ieee);
}
}
private _onUserSelectedNameChanged(value: ChangeEvent): void {
this._userSelectedName = value.detail!.value;
}
private async _onUpdateDeviceNameClick(): Promise<void> {
if (this.hass) {
const values: DeviceRegistryEntryMutableParams = {
name_by_user: this._userSelectedName,
};
await updateDeviceRegistryEntry(
this.hass,
this._selectedNode!.device_reg_id,
values
);
this._selectedNode!.user_given_name = this._userSelectedName!;
this._userSelectedName = "";
}
}
private _computeNodeServiceData(): NodeServiceData {
return {
ieee_address: this._selectedNode!.ieee,
};
private _selectedDeviceChanged(event: ItemSelectedEvent): void {
this._selectedDeviceIndex = event!.target!.selected;
this._selectedDevice = this._nodes[this._selectedDeviceIndex];
fireEvent(this, "zha-node-selected", { node: this._selectedDevice });
}
private async _fetchDevices() {
@ -273,6 +146,13 @@ export class ZHANode extends LitElement {
});
}
private _onDeviceRemoved(event: ZHADeviceRemovedEvent): void {
this._selectedDeviceIndex = -1;
this._nodes.splice(this._nodes.indexOf(event.detail!.device!), 1);
this._selectedDevice = undefined;
fireEvent(this, "zha-node-selected", { node: this._selectedDevice });
}
static get styles(): CSSResult[] {
return [
haStyle,
@ -298,13 +178,10 @@ export class ZHANode extends LitElement {
}
.help-text {
color: grey;
padding-left: 28px;
padding-right: 28px;
}
.helpText {
color: grey;
padding: 16px;
padding-bottom: 16px;
}
paper-card {
@ -355,12 +232,6 @@ export class ZHANode extends LitElement {
right: 0;
color: var(--primary-color);
}
.input-text {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
`,
];
}
@ -371,5 +242,3 @@ declare global {
"zha-node": ZHANode;
}
}
customElements.define("zha-node", ZHANode);

View File

@ -1,50 +1,69 @@
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { property, PropertyValues, UpdatingElement } from "lit-element";
import { loadCustomPanel } from "../../util/custom-panel/load-custom-panel";
import { createCustomPanelElement } from "../../util/custom-panel/create-custom-panel-element";
import { setCustomPanelProperties } from "../../util/custom-panel/set-custom-panel-properties";
import { HomeAssistant, Route, Panel } from "../../types";
import { CustomPanelConfig } from "../../data/panel_custom";
import EventsMixin from "../../mixins/events-mixin";
import NavigateMixin from "../../mixins/navigate-mixin";
import loadCustomPanel from "../../util/custom-panel/load-custom-panel";
import createCustomPanelElement from "../../util/custom-panel/create-custom-panel-element";
import setCustomPanelProperties from "../../util/custom-panel/set-custom-panel-properties";
/*
* Mixins are used by ifram to communicate with main frontend.
* @appliesMixin EventsMixin
* @appliesMixin NavigateMixin
*/
class HaPanelCustom extends NavigateMixin(EventsMixin(PolymerElement)) {
static get properties() {
return {
hass: Object,
narrow: Boolean,
route: Object,
panel: {
type: Object,
observer: "_panelChanged",
},
};
declare global {
interface Window {
customPanel: HaPanelCustom | undefined;
}
}
static get observers() {
return ["_dataChanged(hass, narrow, route)"];
export class HaPanelCustom extends UpdatingElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() public panel!: Panel;
private _setProperties?: (props: {}) => void | undefined;
public registerIframe(initialize, setProperties) {
initialize(this.panel, {
hass: this.hass,
narrow: this.narrow,
route: this.route,
});
this._setProperties = setProperties;
}
constructor() {
super();
this._setProperties = null;
public disconnectedCallback() {
super.disconnectedCallback();
this._cleanupPanel();
}
_panelChanged(panel) {
// Clean up
protected updated(changedProps: PropertyValues) {
if (changedProps.has("panel")) {
// Clean up old things if we had a panel
if (changedProps.get("panel")) {
this._cleanupPanel();
}
this._createPanel(this.panel);
return;
}
if (!this._setProperties) {
return;
}
const props = {};
for (const key of changedProps.keys()) {
props[key] = this[key];
}
this._setProperties(props);
}
private _cleanupPanel() {
delete window.customPanel;
this._setProperties = null;
this._setProperties = undefined;
while (this.lastChild) {
this.removeChild(this.lastChild);
}
}
const config = panel.config._panel_custom;
private _createPanel(panel: Panel) {
const config = panel.config!._panel_custom as CustomPanelConfig;
const tempA = document.createElement("a");
tempA.href = config.html_url || config.js_url || config.module_url;
tempA.href = config.html_url || config.js_url || config.module_url || "";
if (
!config.trust_external &&
@ -96,32 +115,13 @@ It will have access to all data in Home Assistant.
</style>
<iframe></iframe>
`.trim();
const iframeDoc = this.querySelector("iframe").contentWindow.document;
const iframeDoc = this.querySelector("iframe")!.contentWindow!.document;
iframeDoc.open();
iframeDoc.write(
`<!doctype html><script src='${window.customPanelJS}'></script>`
);
iframeDoc.close();
}
disconnectedCallback() {
super.disconnectedCallback();
delete window.customPanel;
}
_dataChanged(hass, narrow, route) {
if (!this._setProperties) return;
this._setProperties({ hass, narrow, route });
}
registerIframe(initialize, setProperties) {
initialize(this.panel, {
hass: this.hass,
narrow: this.narrow,
route: this.route,
});
this._setProperties = setProperties;
}
}
customElements.define("ha-panel-custom", HaPanelCustom);

View File

@ -10,17 +10,17 @@ import {
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../components/ha-card";
import "../../../components/ha-label-badge";
import "../components/hui-warning";
import { LovelaceCard } from "../types";
import { HomeAssistant } from "../../../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import {
callAlarmAction,
FORMAT_NUMBER,
} from "../../../data/alarm_control_panel";
import "../../../components/ha-card";
import "../../../components/ha-label-badge";
import "../components/hui-warning";
import { AlarmPanelCardConfig } from "./types";
const ICONS = {
armed_away: "hass:shield-lock",
@ -34,12 +34,6 @@ const ICONS = {
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
export interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
states?: string[];
}
@customElement("hui-alarm-panel-card")
class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
public static async getConfigElement() {
@ -53,7 +47,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: AlarmPanelCardConfig;
@property() private _code?: string;
@ -69,7 +63,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
: 8;
}
public setConfig(config: Config): void {
public setConfig(config: AlarmPanelCardConfig): void {
if (
!config ||
!config.entity ||
@ -305,7 +299,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
.actions mwc-button {
min-width: calc(var(--base-unit) * 9);
margin: 0 4px;
margin: 0 4px 4px;
}
mwc-button#disarm {

View File

@ -1,22 +1,16 @@
import { createCardElement } from "../common/create-card-element";
import { computeCardSize } from "../common/compute-card-size";
import {
Condition,
checkConditionsMet,
validateConditionalConfig,
} from "../../lovelace/common/validate-condition";
import { HomeAssistant } from "../../../types";
import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
interface Config extends LovelaceCardConfig {
card: LovelaceCardConfig;
conditions: Condition[];
}
import { ConditionalCardConfig } from "./types";
class HuiConditionalCard extends HTMLElement implements LovelaceCard {
private _hass?: HomeAssistant;
private _config?: Config;
private _config?: ConditionalCardConfig;
private _card?: LovelaceCard;
public setConfig(config) {

View File

@ -11,13 +11,8 @@ import {
import "@polymer/paper-card/paper-card";
import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
export interface Config extends LovelaceCardConfig {
content: string;
title?: string;
}
import { EmptyStateCardConfig } from "./types";
@customElement("hui-empty-state-card")
export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
@ -27,7 +22,7 @@ export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
return 2;
}
public setConfig(_config: Config): void {
public setConfig(_config: EmptyStateCardConfig): void {
// tslint:disable-next-line
}

View File

@ -15,30 +15,15 @@ import "../components/hui-entities-toggle";
import { fireEvent } from "../../../common/dom/fire_event";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { HomeAssistant } from "../../../types";
import { EntityConfig, EntityRow } from "../entity-rows/types";
import { EntityRow } from "../entity-rows/types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { processConfigEntities } from "../common/process-config-entities";
import { createRowElement } from "../common/create-row-element";
import { EntitiesCardConfig, EntitiesCardEntityConfig } from "./types";
import computeDomain from "../../../common/entity/compute_domain";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
export interface EntitiesCardEntityConfig extends EntityConfig {
type?: string;
secondary_info?: "entity-id" | "last-changed";
action_name?: string;
service?: string;
service_data?: object;
url?: string;
}
export interface EntitiesCardConfig extends LovelaceCardConfig {
show_header_toggle?: boolean;
title?: string;
entities: EntitiesCardEntityConfig[];
theme?: string;
}
@customElement("hui-entities-card")
class HuiEntitiesCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {

View File

@ -21,21 +21,13 @@ import computeStateDomain from "../../../common/entity/compute_state_domain";
import computeStateName from "../../../common/entity/compute_state_name";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import computeDomain from "../../../common/entity/compute_domain";
import { HomeAssistant, LightEntity } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { longPress } from "../common/directives/long-press-directive";
import { handleClick } from "../common/handle-click";
import { DOMAINS_TOGGLE } from "../../../common/const";
export interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
icon?: string;
theme?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
import { EntityButtonCardConfig } from "./types";
@customElement("hui-entity-button-card")
class HuiEntityButtonCard extends LitElement implements LovelaceCard {
@ -48,18 +40,20 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
return {
tap_action: { action: "toggle" },
hold_action: { action: "more-info" },
show_icon: true,
show_name: true,
};
}
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: EntityButtonCardConfig;
public getCardSize(): number {
return 2;
}
public setConfig(config: Config): void {
public setConfig(config: EntityButtonCardConfig): void {
if (!isValidEntityId(config.entity)) {
throw new Error("Invalid Entity");
}
@ -67,6 +61,8 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
this._config = {
theme: "default",
hold_action: { action: "more-info" },
show_icon: true,
show_name: true,
...config,
};
@ -126,6 +122,8 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
@ha-hold="${this._handleHold}"
.longPress="${longPress()}"
>
${this._config.show_icon
? html`
<ha-icon
data-domain="${computeStateDomain(stateObj)}"
data-state="${stateObj.state}"
@ -135,9 +133,15 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
color: this._computeColor(stateObj),
})}"
></ha-icon>
`
: ""}
${this._config.show_name
? html`
<span>
${this._config.name || computeStateName(stateObj)}
</span>
`
: ""}
<mwc-ripple></mwc-ripple>
</ha-card>
`;

View File

@ -4,14 +4,7 @@ import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { EntityConfig } from "../entity-rows/types";
import { HomeAssistant } from "../../../types";
export interface EntityFilterCardConfig extends LovelaceCardConfig {
type: "entity-filter";
entities: Array<EntityConfig | string>;
state_filter: string[];
card: Partial<LovelaceCardConfig>;
show_empty?: boolean;
}
import { EntityFilterCardConfig } from "./types";
class EntityFilterCard extends HTMLElement implements LovelaceCard {
public isPanel?: boolean;

View File

@ -11,11 +11,7 @@ import {
import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
interface Config extends LovelaceCardConfig {
error: string;
origConfig: LovelaceCardConfig;
}
import { ErrorCardConfig } from "./types";
export const createErrorCardElement = (config) => {
const el = document.createElement("hui-error-card");
@ -33,13 +29,13 @@ export const createErrorCardConfig = (error, origConfig) => ({
export class HuiErrorCard extends LitElement implements LovelaceCard {
public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ErrorCardConfig;
public getCardSize(): number {
return 4;
}
public setConfig(config: Config): void {
public setConfig(config: ErrorCardConfig): void {
this._config = config;
}

View File

@ -13,31 +13,15 @@ import { styleMap } from "lit-html/directives/style-map";
import "../../../components/ha-card";
import "../components/hui-warning";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import isValidEntityId from "../../../common/entity/valid_entity_id";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import computeStateName from "../../../common/entity/compute_state_name";
export interface SeverityConfig {
green?: number;
yellow?: number;
red?: number;
}
export interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
unit?: string;
min?: number;
max?: number;
severity?: SeverityConfig;
theme?: string;
}
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { GaugeCardConfig } from "./types";
export const severityMap = {
red: "var(--label-badge-red)",
@ -58,7 +42,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: GaugeCardConfig;
private _updated?: boolean;
@ -66,7 +50,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return 2;
}
public setConfig(config: Config): void {
public setConfig(config: GaugeCardConfig): void {
if (!config || !config.entity) {
throw new Error("Invalid card configuration");
}

View File

@ -10,14 +10,6 @@ import {
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { HomeAssistant } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { longPress } from "../common/directives/long-press-directive";
import { EntityConfig } from "../entity-rows/types";
import { processConfigEntities } from "../common/process-config-entities";
import { handleClick } from "../common/handle-click";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import computeStateName from "../../../common/entity/compute_state_name";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
@ -25,22 +17,14 @@ import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../components/hui-warning";
import "../components/hui-warning-element";
export interface ConfigEntity extends EntityConfig {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export interface GlanceCardConfig extends LovelaceCardConfig {
show_name?: boolean;
show_state?: boolean;
show_icon?: boolean;
title?: string;
theme?: string;
entities: ConfigEntity[];
columns?: number;
}
import { HomeAssistant } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { longPress } from "../common/directives/long-press-directive";
import { processConfigEntities } from "../common/process-config-entities";
import { handleClick } from "../common/handle-click";
import { GlanceCardConfig, ConfigEntity } from "./types";
@customElement("hui-glance-card")
export class HuiGlanceCard extends LitElement implements LovelaceCard {
@ -184,13 +168,13 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
if (!stateObj) {
return html`
<hui-warning
>${this.hass!.localize(
<hui-warning-element
label=${this.hass!.localize(
"ui.panel.lovelace.warning.entity_not_found",
"entity",
entityConf.entity
)}</hui-warning
>
)}
></hui-warning-element>
`;
}

View File

@ -11,14 +11,8 @@ import {
import "../../../components/ha-card";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { styleMap } from "lit-html/directives/style-map";
export interface Config extends LovelaceCardConfig {
aspect_ratio?: string;
title?: string;
url: string;
}
import { IframeCardConfig } from "./types";
@customElement("hui-iframe-card")
export class HuiIframeCard extends LitElement implements LovelaceCard {
@ -30,7 +24,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
return { url: "https://www.home-assistant.io", aspect_ratio: "50%" };
}
@property() protected _config?: Config;
@property() protected _config?: IframeCardConfig;
public getCardSize(): number {
if (!this._config) {
@ -42,7 +36,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
return 1 + aspectRatio / 25;
}
public setConfig(config: Config): void {
public setConfig(config: IframeCardConfig): void {
if (!config.url) {
throw new Error("URL required");
}

View File

@ -8,15 +8,6 @@ import {
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button";
import { fireEvent } from "../../../common/dom/fire_event";
import { styleMap } from "lit-html/directives/style-map";
import { HomeAssistant, LightEntity } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
import { toggleEntity } from "../common/entity/toggle-entity";
import stateIcon from "../../../common/entity/state_icon";
import computeStateName from "../../../common/entity/compute_state_name";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
@ -25,6 +16,15 @@ import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../components/hui-warning";
import { fireEvent } from "../../../common/dom/fire_event";
import { styleMap } from "lit-html/directives/style-map";
import { HomeAssistant, LightEntity } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
import { toggleEntity } from "../common/entity/toggle-entity";
import { LightCardConfig } from "./types";
const lightConfig = {
radius: 80,
step: 1,
@ -40,12 +40,6 @@ const lightConfig = {
animation: false,
};
export interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
theme?: string;
}
@customElement("hui-light-card")
export class HuiLightCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -58,7 +52,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: LightCardConfig;
@property() private _roundSliderStyle?: TemplateResult;
@ -70,7 +64,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
return 2;
}
public setConfig(config: Config): void {
public setConfig(config: LightCardConfig): void {
if (!config.entity || config.entity.split(".")[0] !== "light") {
throw new Error("Specify an entity from within the light domain.");
}

View File

@ -21,20 +21,13 @@ import computeStateDomain from "../../../common/entity/compute_state_domain";
import computeStateName from "../../../common/entity/compute_state_name";
import debounce from "../../../common/util/debounce";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import { HomeAssistant } from "../../../types";
import computeDomain from "../../../common/entity/compute_domain";
import { HomeAssistant } from "../../../types";
import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { EntityConfig } from "../entity-rows/types";
import { processConfigEntities } from "../common/process-config-entities";
export interface MapCardConfig extends LovelaceCardConfig {
title: string;
aspect_ratio: string;
default_zoom?: number;
entities?: Array<EntityConfig | string>;
geo_location_sources?: string[];
}
import { MapCardConfig } from "./types";
@customElement("hui-map-card")
class HuiMapCard extends LitElement implements LovelaceCard {

View File

@ -13,12 +13,7 @@ import "../../../components/ha-card";
import "../../../components/ha-markdown";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
export interface Config extends LovelaceCardConfig {
content: string;
title?: string;
}
import { MarkdownCardConfig } from "./types";
@customElement("hui-markdown-card")
export class HuiMarkdownCard extends LitElement implements LovelaceCard {
@ -31,13 +26,13 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
return { content: " " };
}
@property() private _config?: Config;
@property() private _config?: MarkdownCardConfig;
public getCardSize(): number {
return this._config!.content.split("\n").length;
}
public setConfig(config: Config): void {
public setConfig(config: MarkdownCardConfig): void {
if (!config.content) {
throw new Error("Invalid Configuration: Content Required");
}

View File

@ -2,11 +2,6 @@ import "../../../cards/ha-media_player-card";
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
// should be interface when converted to TS
export const Config = {
entity: "",
};
class HuiMediaControlCard extends LegacyWrapperCard {
static async getConfigElement() {
await import(/* webpackChunkName: "hui-media-control-card-editor" */ "../editor/config-elements/hui-media-control-card-editor");

View File

@ -11,17 +11,11 @@ import {
import "../../../components/ha-card";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { classMap } from "lit-html/directives/class-map";
import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive";
export interface Config extends LovelaceCardConfig {
image?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
import { PictureCardConfig } from "./types";
@customElement("hui-picture-card")
export class HuiPictureCard extends LitElement implements LovelaceCard {
@ -40,13 +34,13 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
public hass?: HomeAssistant;
@property() protected _config?: Config;
@property() protected _config?: PictureCardConfig;
public getCardSize(): number {
return 3;
}
public setConfig(config: Config): void {
public setConfig(config: PictureCardConfig): void {
if (!config || !config.image) {
throw new Error("Invalid Configuration: 'image' required");
}

View File

@ -9,25 +9,14 @@ import {
} from "lit-element";
import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element";
import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { LovelaceElementConfig, LovelaceElement } from "../elements/types";
interface Config extends LovelaceCardConfig {
title?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
entity?: string;
elements: LovelaceElementConfig[];
}
import { PictureElementsCardConfig } from "./types";
@customElement("hui-picture-elements-card")
class HuiPictureElementsCard extends LitElement implements LovelaceCard {
@property() private _config?: Config;
@property() private _config?: PictureElementsCardConfig;
private _hass?: HomeAssistant;
@ -43,7 +32,7 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
return 4;
}
public setConfig(config: Config): void {
public setConfig(config: PictureElementsCardConfig): void {
if (!config) {
throw new Error("Invalid Configuration");
} else if (

View File

@ -6,6 +6,7 @@ import {
property,
css,
CSSResult,
PropertyValues,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
@ -19,35 +20,23 @@ import computeStateName from "../../../common/entity/compute_state_name";
import { longPress } from "../common/directives/long-press-directive";
import { HomeAssistant } from "../../../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { LovelaceCard } from "../types";
import { handleClick } from "../common/handle-click";
import { UNAVAILABLE } from "../../../data/entity";
interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
show_name?: boolean;
show_state?: boolean;
}
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { PictureEntityCardConfig } from "./types";
@customElement("hui-picture-entity-card")
class HuiPictureEntityCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: PictureEntityCardConfig;
public getCardSize(): number {
return 3;
}
public setConfig(config: Config): void {
public setConfig(config: PictureEntityCardConfig): void {
if (!config || !config.entity) {
throw new Error("Invalid Configuration: 'entity' required");
}
@ -62,6 +51,10 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
this._config = { show_name: true, show_state: true, ...config };
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;

View File

@ -6,16 +6,10 @@ import {
property,
css,
CSSResult,
PropertyValues,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { DOMAINS_TOGGLE } from "../../../common/const";
import { LovelaceCard } from "../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { EntityConfig } from "../entity-rows/types";
import { HomeAssistant } from "../../../types";
import { longPress } from "../common/directives/long-press-directive";
import { processConfigEntities } from "../common/process-config-entities";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import computeStateName from "../../../common/entity/compute_state_name";
import computeDomain from "../../../common/entity/compute_domain";
@ -24,29 +18,26 @@ import stateIcon from "../../../common/entity/state_icon";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../components/hui-image";
import "../components/hui-warning-element";
import { DOMAINS_TOGGLE } from "../../../common/const";
import { LovelaceCard } from "../types";
import { EntityConfig } from "../entity-rows/types";
import { HomeAssistant } from "../../../types";
import { longPress } from "../common/directives/long-press-directive";
import { processConfigEntities } from "../common/process-config-entities";
import { handleClick } from "../common/handle-click";
import { fireEvent } from "../../../common/dom/fire_event";
import { toggleEntity } from "../common/entity/toggle-entity";
import { PictureGlanceCardConfig } from "./types";
const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]);
interface Config extends LovelaceCardConfig {
entities: EntityConfig[];
title?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
entity?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
@customElement("hui-picture-glance-card")
class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: PictureGlanceCardConfig;
private _entitiesDialog?: EntityConfig[];
@ -56,7 +47,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
return 3;
}
public setConfig(config: Config): void {
public setConfig(config: PictureGlanceCardConfig): void {
if (
!config ||
!config.entities ||
@ -85,6 +76,39 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_config")) {
return true;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass) {
return true;
}
if (this._entitiesDialog) {
for (const entity of this._entitiesDialog) {
if (
oldHass.states[entity.entity] !== this.hass!.states[entity.entity]
) {
return true;
}
}
}
if (this._entitiesToggle) {
for (const entity of this._entitiesToggle) {
if (
oldHass.states[entity.entity] !== this.hass!.states[entity.entity]
) {
return true;
}
}
}
return false;
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;
@ -138,7 +162,15 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
const stateObj = this.hass!.states[entityConf.entity];
if (!stateObj) {
return html``;
return html`
<hui-warning-element
label=${this.hass!.localize(
"ui.panel.lovelace.warning.entity_not_found",
"entity",
entityConf.entity
)}
></hui-warning-element>
`;
}
return html`

View File

@ -6,18 +6,20 @@ import {
CSSResult,
property,
customElement,
PropertyValues,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import { HassEntity } from "home-assistant-js-websocket";
import computeStateName from "../../../common/entity/compute_state_name";
import { LovelaceCardEditor, LovelaceCard } from "../types";
import { HomeAssistant } from "../../../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { fireEvent } from "../../../common/dom/fire_event";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { PlantStatusCardConfig, PlantAttributeTarget } from "./types";
const SENSORS = {
moisture: "hass:water",
@ -27,15 +29,6 @@ const SENSORS = {
battery: "hass:battery",
};
export interface PlantAttributeTarget extends EventTarget {
value?: string;
}
export interface PlantStatusConfig extends LovelaceCardConfig {
name?: string;
entity: string;
}
@customElement("hui-plant-status-card")
class HuiPlantStatusCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -49,13 +42,13 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: PlantStatusConfig;
@property() private _config?: PlantStatusCardConfig;
public getCardSize(): number {
return 3;
}
public setConfig(config: PlantStatusConfig): void {
public setConfig(config: PlantStatusCardConfig): void {
if (!config.entity || config.entity.split(".")[0] !== "plant") {
throw new Error("Specify an entity from within the plant domain.");
}
@ -63,6 +56,10 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this.hass || !this._config) {
return html``;
@ -91,7 +88,7 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
style="background-image:url(${stateObj.attributes.entity_picture})"
>
<div class="header">
${this._config.title || computeStateName(stateObj)}
${this._config.name || computeStateName(stateObj)}
</div>
</div>
<div class="content">

View File

@ -11,12 +11,6 @@ import {
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchRecent } from "../../../data/history";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import computeStateName from "../../../common/entity/compute_state_name";
import stateIcon from "../../../common/entity/state_icon";
@ -25,6 +19,13 @@ import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchRecent } from "../../../data/history";
import { SensorCardConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
const midPoint = (
_Ax: number,
_Ay: number,
@ -137,17 +138,6 @@ const coordinates = (
return calcPoints(history, hours, width, detail, min, max);
};
export interface Config extends LovelaceCardConfig {
entity: string;
name?: string;
icon?: string;
graph?: string;
unit?: string;
detail?: number;
theme?: string;
hours_to_show?: number;
}
@customElement("hui-sensor-card")
class HuiSensorCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -161,13 +151,13 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: SensorCardConfig;
@property() private _history?: any;
private _date?: Date;
public setConfig(config: Config): void {
public setConfig(config: SensorCardConfig): void {
if (!config.entity || config.entity.split(".")[0] !== "sensor") {
throw new Error("Specify an entity from within the sensor domain.");
}
@ -272,6 +262,14 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
this._date = new Date();
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_history")) {
return true;
}
return hasConfigOrEntityChanged(this, changedProps);
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this._config || this._config.graph !== "line" || !this.hass) {

View File

@ -16,7 +16,6 @@ import "../../../components/ha-icon";
import { HomeAssistant } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import {
fetchItems,
updateItem,
@ -24,10 +23,7 @@ import {
clearItems,
addItem,
} from "../../../data/shopping-list";
export interface Config extends LovelaceCardConfig {
title?: string;
}
import { ShoppingListCardConfig } from "./types";
@customElement("hui-shopping-list-card")
class HuiShoppingListCard extends LitElement implements LovelaceCard {
@ -42,7 +38,7 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ShoppingListCardConfig;
@property() private _uncheckedItems?: ShoppingListItem[];
@ -54,7 +50,7 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
return (this._config ? (this._config.title ? 1 : 0) : 0) + 3;
}
public setConfig(config: Config): void {
public setConfig(config: ShoppingListCardConfig): void {
this._config = config;
this._uncheckedItems = [];
this._checkedItems = [];

View File

@ -1,14 +1,10 @@
import { html, LitElement, TemplateResult } from "lit-element";
import { createCardElement } from "../common/create-card-element";
import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
interface Config extends LovelaceCardConfig {
cards: LovelaceCardConfig[];
}
import { StackCardConfig } from "./types";
export abstract class HuiStackCard extends LitElement implements LovelaceCard {
static get properties() {
@ -29,12 +25,12 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
}
}
protected _cards?: LovelaceCard[];
private _config?: Config;
private _config?: StackCardConfig;
private _hass?: HomeAssistant;
public abstract getCardSize(): number;
public setConfig(config: Config): void {
public setConfig(config: StackCardConfig): void {
if (!config || !config.cards || !Array.isArray(config.cards)) {
throw new Error("Card config incorrect");
}

View File

@ -19,10 +19,10 @@ import computeStateName from "../../../common/entity/compute_state_name";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { HomeAssistant, ClimateEntity } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
import { UNIT_F } from "../../../common/const";
import { fireEvent } from "../../../common/dom/fire_event";
import { ThermostatCardConfig } from "./types";
const thermostatConfig = {
radius: 150,
@ -47,12 +47,6 @@ const modeIcons = {
idle: "hass:power-sleep",
};
export interface Config extends LovelaceCardConfig {
entity: string;
theme?: string;
name?: string;
}
@customElement("hui-thermostat-card")
export class HuiThermostatCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -66,7 +60,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ThermostatCardConfig;
@property() private _roundSliderStyle?: TemplateResult;
@ -82,7 +76,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
return 4;
}
public setConfig(config: Config): void {
public setConfig(config: ThermostatCardConfig): void {
if (!config.entity || config.entity.split(".")[0] !== "climate") {
throw new Error("Specify an entity from within the climate domain.");
}

View File

@ -2,12 +2,6 @@ import "../../../cards/ha-weather-card";
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
// should be interface when converted to TS
export const Config = {
entity: "",
name: "",
};
class HuiWeatherForecastCard extends LegacyWrapperCard {
static async getConfigElement() {
await import(/* webpackChunkName: "hui-weather-forecast-card-editor" */ "../editor/config-elements/hui-weather-forecast-card-editor");

View File

@ -0,0 +1,200 @@
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { Condition } from "../common/validate-condition";
import { EntityConfig } from "../entity-rows/types";
import { LovelaceElementConfig } from "../elements/types";
export interface AlarmPanelCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
states?: string[];
}
export interface ConditionalCardConfig extends LovelaceCardConfig {
card: LovelaceCardConfig;
conditions: Condition[];
}
export interface EmptyStateCardConfig extends LovelaceCardConfig {
content: string;
title?: string;
}
export interface EntitiesCardEntityConfig extends EntityConfig {
type?: string;
secondary_info?: "entity-id" | "last-changed";
action_name?: string;
service?: string;
service_data?: object;
url?: string;
}
export interface EntitiesCardConfig extends LovelaceCardConfig {
show_header_toggle?: boolean;
title?: string;
entities: EntitiesCardEntityConfig[];
theme?: string;
}
export interface EntityButtonCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
show_name?: boolean;
icon?: string;
show_icon?: boolean;
theme?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export interface EntityFilterCardConfig extends LovelaceCardConfig {
type: "entity-filter";
entities: Array<EntityConfig | string>;
state_filter: string[];
card: Partial<LovelaceCardConfig>;
show_empty?: boolean;
}
export interface ErrorCardConfig extends LovelaceCardConfig {
error: string;
origConfig: LovelaceCardConfig;
}
export interface SeverityConfig {
green?: number;
yellow?: number;
red?: number;
}
export interface GaugeCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
unit?: string;
min?: number;
max?: number;
severity?: SeverityConfig;
theme?: string;
}
export interface ConfigEntity extends EntityConfig {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export interface GlanceCardConfig extends LovelaceCardConfig {
show_name?: boolean;
show_state?: boolean;
show_icon?: boolean;
title?: string;
theme?: string;
entities: ConfigEntity[];
columns?: number;
}
export interface IframeCardConfig extends LovelaceCardConfig {
aspect_ratio?: string;
title?: string;
url: string;
}
export interface LightCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
theme?: string;
}
export interface MapCardConfig extends LovelaceCardConfig {
title: string;
aspect_ratio: string;
default_zoom?: number;
entities?: Array<EntityConfig | string>;
geo_location_sources?: string[];
}
export interface MarkdownCardConfig extends LovelaceCardConfig {
content: string;
title?: string;
}
export interface MediaControlCardConfig extends LovelaceCardConfig {
entity: string;
}
export interface PictureCardConfig extends LovelaceCardConfig {
image?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export interface PictureElementsCardConfig extends LovelaceCardConfig {
title?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
entity?: string;
elements: LovelaceElementConfig[];
}
export interface PictureEntityCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
show_name?: boolean;
show_state?: boolean;
}
export interface PictureGlanceCardConfig extends LovelaceCardConfig {
entities: EntityConfig[];
title?: string;
image?: string;
camera_image?: string;
state_image?: {};
aspect_ratio?: string;
entity?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export interface PlantAttributeTarget extends EventTarget {
value?: string;
}
export interface PlantStatusCardConfig extends LovelaceCardConfig {
name?: string;
entity: string;
}
export interface SensorCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
icon?: string;
graph?: string;
unit?: string;
detail?: number;
theme?: string;
hours_to_show?: number;
}
export interface ShoppingListCardConfig extends LovelaceCardConfig {
title?: string;
}
export interface StackCardConfig extends LovelaceCardConfig {
cards: LovelaceCardConfig[];
}
export interface ThermostatCardConfig extends LovelaceCardConfig {
entity: string;
theme?: string;
name?: string;
}
export interface WeatherForecastCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
}

View File

@ -5,6 +5,7 @@ import {
LovelaceViewConfig,
} from "../../../data/lovelace";
import { HassEntity, HassEntities } from "home-assistant-js-websocket";
import extractViews from "../../../common/entity/extract_views";
import getViewEntities from "../../../common/entity/get_view_entities";
import computeStateName from "../../../common/entity/compute_state_name";
@ -12,9 +13,10 @@ import splitByGroups from "../../../common/entity/split_by_groups";
import computeObjectId from "../../../common/entity/compute_object_id";
import computeStateDomain from "../../../common/entity/compute_state_domain";
import computeDomain from "../../../common/entity/compute_domain";
import { EntityRowConfig, WeblinkConfig } from "../entity-rows/types";
import { EntitiesCardConfig } from "../cards/hui-entities-card";
import { LocalizeFunc } from "../../../common/translations/localize";
import { EntitiesCardConfig } from "../cards/types";
const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
const DOMAINS_BADGES = [

View File

@ -11,13 +11,11 @@ export function checkConditionsMet(
hass: HomeAssistant
): boolean {
return conditions.every((c) => {
if (!(c.entity in hass.states)) {
return false;
}
if (c.state) {
return hass.states[c.entity].state === c.state;
}
return hass!.states[c.entity].state !== c.state_not;
const state = hass.states[c.entity]
? hass!.states[c.entity].state
: "unavailable";
return c.state ? state === c.state : state !== c.state_not;
});
}

View File

@ -0,0 +1,26 @@
import "@polymer/paper-dialog/paper-dialog";
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
import { HaIronFocusablesHelper } from "./ha-iron-focusables-helper.js";
const paperDialogClass = customElements.get("paper-dialog");
// behavior that will override existing iron-overlay-behavior and call the fixed implementation
const haTabFixBehaviorImpl = {
get _focusableNodes() {
return HaIronFocusablesHelper.getTabbableNodes(this);
},
};
// paper-dialog that uses the haTabFixBehaviorImpl behvaior
// export class HaPaperDialog extends paperDialogClass {}
export class HaPaperDialog extends mixinBehaviors(
[haTabFixBehaviorImpl],
paperDialogClass
) {}
declare global {
interface HTMLElementTagNameMap {
"ha-paper-dialog": HaPaperDialog;
}
}
customElements.define("ha-paper-dialog", HaPaperDialog);

View File

@ -0,0 +1,90 @@
/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
part of the polymer project is also subject to an additional IP rights grant
found at http://polymer.github.io/PATENTS.txt
*/
/*
Fixes issue with not using shadow dom properly in iron-overlay-behavior/icon-focusables-helper.js
*/
import { dom } from "@polymer/polymer/lib/legacy/polymer.dom.js";
import { IronFocusablesHelper } from "@polymer/iron-overlay-behavior/iron-focusables-helper.js";
export const HaIronFocusablesHelper = {
/**
* Returns a sorted array of tabbable nodes, including the root node.
* It searches the tabbable nodes in the light and shadow dom of the chidren,
* sorting the result by tabindex.
* @param {!Node} node
* @return {!Array<!HTMLElement>}
*/
getTabbableNodes: function(node) {
var result = [];
// If there is at least one element with tabindex > 0, we need to sort
// the final array by tabindex.
var needsSortByTabIndex = this._collectTabbableNodes(node, result);
if (needsSortByTabIndex) {
return IronFocusablesHelper._sortByTabIndex(result);
}
return result;
},
/**
* Searches for nodes that are tabbable and adds them to the `result` array.
* Returns if the `result` array needs to be sorted by tabindex.
* @param {!Node} node The starting point for the search; added to `result`
* if tabbable.
* @param {!Array<!HTMLElement>} result
* @return {boolean}
* @private
*/
_collectTabbableNodes: function(node, result) {
// If not an element or not visible, no need to explore children.
if (
node.nodeType !== Node.ELEMENT_NODE ||
!IronFocusablesHelper._isVisible(node)
) {
return false;
}
var element = /** @type {!HTMLElement} */ (node);
var tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
var needsSort = tabIndex > 0;
if (tabIndex >= 0) {
result.push(element);
}
// In ShadowDOM v1, tab order is affected by the order of distrubution.
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B];
// in ShadowDOM v0 tab order is not affected by the distrubution order,
// in fact getTabbableNodes(#root) returns [#B, #A].
// <div id="root">
// <!-- shadow -->
// <slot name="a">
// <slot name="b">
// <!-- /shadow -->
// <input id="A" slot="a">
// <input id="B" slot="b" tabindex="1">
// </div>
// TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
var children;
if (element.localName === "content" || element.localName === "slot") {
children = dom(element).getDistributedNodes();
} else {
// /////////////////////////
// Use shadow root if possible, will check for distributed nodes.
// THIS IS THE CHANGED LINE
children = dom(element.shadowRoot || element.root || element).children;
// /////////////////////////
}
for (var i = 0; i < children.length; i++) {
// Ensure method is always invoked to collect tabbable children.
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
}
return needsSort;
},
};

View File

@ -9,15 +9,15 @@ import {
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/hui-entities-card";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/entity/state-badge";
import "../../../components/ha-relative-time";
import "../../../components/ha-icon";
import "../components/hui-warning";
import { HomeAssistant } from "../../../types";
import { computeRTL } from "../../../common/util/compute_rtl";
import { EntitiesCardEntityConfig } from "../cards/types";
class HuiGenericEntityRow extends LitElement {
@property() public hass?: HomeAssistant;

View File

@ -0,0 +1,36 @@
import {
html,
LitElement,
TemplateResult,
CSSResult,
css,
customElement,
property,
} from "lit-element";
import "../../../components/ha-icon";
@customElement("hui-warning-element")
export class HuiWarningElement extends LitElement {
@property() public label?: string;
protected render(): TemplateResult | void {
return html`
<ha-icon icon="hass:alert" .title="${this.label}"></ha-icon>
`;
}
static get styles(): CSSResult {
return css`
ha-icon {
color: #fce588;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-warning-element": HuiWarningElement;
}
}

View File

@ -96,6 +96,10 @@ export class HuiYamlEditor extends HTMLElement {
tabSize: 2,
autofocus: true,
viewportMargin: Infinity,
extraKeys: {
Tab: "indentMore",
"Shift-Tab": "indentLess",
},
gutters:
this._hass && computeRTL(this._hass!)
? ["rtl-gutter", "CodeMirror-linenumbers"]

View File

@ -1,60 +0,0 @@
import "@material/mwc-button";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "./hui-notification-item-template";
import EventsMixin from "../../../../mixins/events-mixin";
import LocalizeMixin from "../../../../mixins/localize-mixin";
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
export class HuiConfiguratorNotificationItem extends EventsMixin(
LocalizeMixin(PolymerElement)
) {
static get template() {
return html`
<hui-notification-item-template>
<span slot="header">[[localize('domain.configurator')]]</span>
<div>[[_getMessage(notification)]]</div>
<mwc-button slot="actions" on-click="_handleClick"
>[[_localizeState(notification.state)]]</mwc-button
>
</hui-notification-item-template>
`;
}
static get properties() {
return {
hass: Object,
notification: Object,
};
}
_handleClick() {
this.fire("hass-more-info", { entityId: this.notification.entity_id });
}
_localizeState(state) {
return this.localize(`state.configurator.${state}`);
}
_getMessage(notification) {
const friendlyName = notification.attributes.friendly_name;
return this.localize(
"ui.notification_drawer.click_to_configure",
"entity",
friendlyName
);
}
}
customElements.define(
"hui-configurator-notification-item",
HuiConfiguratorNotificationItem
);

View File

@ -0,0 +1,59 @@
import {
html,
LitElement,
TemplateResult,
property,
customElement,
} from "lit-element";
import "@material/mwc-button";
import "./hui-notification-item-template";
import { HomeAssistant } from "../../../../types";
import { HassEntity } from "home-assistant-js-websocket";
import { fireEvent } from "../../../../common/dom/fire_event";
@customElement("hui-configurator-notification-item")
export class HuiConfiguratorNotificationItem extends LitElement {
@property() public hass?: HomeAssistant;
@property() public notification?: HassEntity;
protected render(): TemplateResult | void {
if (!this.hass || !this.notification) {
return html``;
}
return html`
<hui-notification-item-template>
<span slot="header">${this.hass.localize("domain.configurator")}</span>
<div>
${this.hass.localize(
"ui.notification_drawer.click_to_configure",
"entity",
this.notification.attributes.friendly_name
)}
</div>
<mwc-button slot="actions" @click="${this._handleClick}"
>${this.hass.localize(
`state.configurator.${this.notification.state}`
)}</mwc-button
>
</hui-notification-item-template>
`;
}
private _handleClick(): void {
fireEvent(this, "hass-more-info", {
entityId: this.notification!.entity_id,
});
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-configurator-notification-item": HuiConfiguratorNotificationItem;
}
}

View File

@ -1,35 +0,0 @@
import { PolymerElement } from "@polymer/polymer/polymer-element";
import computeDomain from "../../../../common/entity/compute_domain";
import "./hui-configurator-notification-item";
import "./hui-persistent-notification-item";
export class HuiNotificationItem extends PolymerElement {
static get properties() {
return {
hass: Object,
notification: {
type: Object,
observer: "_stateChanged",
},
};
}
_stateChanged(notification) {
if (this.lastChild) {
this.removeChild(this.lastChild);
}
if (!notification) return;
const domain = notification.entity_id
? computeDomain(notification.entity_id)
: "persistent_notification";
const tag = `hui-${domain}-notification-item`;
const el = document.createElement(tag);
el.hass = this.hass;
el.notification = notification;
this.appendChild(el);
}
}
customElements.define("hui-notification-item", HuiNotificationItem);

View File

@ -0,0 +1,55 @@
import {
LitElement,
property,
customElement,
PropertyValues,
TemplateResult,
html,
} from "lit-element";
import "./hui-configurator-notification-item";
import "./hui-persistent-notification-item";
import { HassEntity } from "home-assistant-js-websocket";
import { HomeAssistant } from "../../../../types";
@customElement("hui-notification-item")
export class HuiNotificationItem extends LitElement {
@property() public hass?: HomeAssistant;
@property() public notification?: HassEntity;
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.hass || !this.notification || changedProps.has("notification")) {
return true;
}
return false;
}
protected render(): TemplateResult | void {
if (!this.hass || !this.notification) {
return html``;
}
return this.notification.entity_id
? html`
<hui-configurator-notification-item
.hass="${this.hass}"
.notification="${this.notification}"
></hui-configurator-notification-item>
`
: html`
<hui-persistent-notification-item
.hass="${this.hass}"
.notification="${this.notification}"
></hui-persistent-notification-item>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-notification-item": HuiNotificationItem;
}
}

View File

@ -87,6 +87,6 @@ export class HuiPersistentNotificationItem extends LocalizeMixin(
}
}
customElements.define(
"hui-persistent_notification-notification-item",
"hui-persistent-notification-item",
HuiPersistentNotificationItem
);

View File

@ -12,6 +12,7 @@ import { LovelaceCardConfig } from "../../../../data/lovelace";
import "./hui-edit-card";
import "./hui-dialog-pick-card";
import { EditCardDialogParams } from "./show-edit-card-dialog";
import { addCard, replaceCard } from "../config-util";
declare global {
// for fire event
@ -32,19 +33,22 @@ export class HuiDialogEditCard extends LitElement {
@property() private _cardConfig?: LovelaceCardConfig;
@property() private _newCard?: boolean;
constructor() {
super();
this._cardPicked = this._cardPicked.bind(this);
this._cancel = this._cancel.bind(this);
this._save = this._save.bind(this);
}
public async showDialog(params: EditCardDialogParams): Promise<void> {
this._params = params;
const [view, card] = params.path;
this._newCard = card !== undefined ? false : true;
this._cardConfig =
params.path.length === 2
? (this._cardConfig = params.lovelace.config.views[
params.path[0]
].cards![params.path[1]])
card !== undefined
? params.lovelace.config.views[view].cards![card]
: undefined;
}
@ -66,9 +70,10 @@ export class HuiDialogEditCard extends LitElement {
<hui-edit-card
.hass="${this.hass}"
.lovelace="${this._params.lovelace}"
.path="${this._params.path}"
.cardConfig="${this._cardConfig}"
.closeDialog="${this._cancel}"
.saveCard="${this._save}"
.newCard="${this._newCard}"
>
</hui-edit-card>
`;
@ -82,6 +87,19 @@ export class HuiDialogEditCard extends LitElement {
this._params = undefined;
this._cardConfig = undefined;
}
private async _save(cardConf: LovelaceCardConfig): Promise<void> {
const lovelace = this._params!.lovelace;
await lovelace.saveConfig(
this._params!.path.length === 1
? addCard(lovelace.config, this._params!.path as [number], cardConf)
: replaceCard(
lovelace.config,
this._params!.path as [number, number],
cardConf
)
);
}
}
declare global {

View File

@ -15,9 +15,10 @@ import { haStyleDialog } from "../../../../resources/styles";
import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-dialog/paper-dialog";
import "../../components/dialog/ha-dialog";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
import { HaPaperDialog } from "../../components/dialog/ha-dialog";
import "@material/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import { HomeAssistant } from "../../../../types";
@ -33,10 +34,9 @@ import "./hui-card-preview";
// tslint:disable-next-line
import { HuiCardPreview } from "./hui-card-preview";
import { LovelaceCardEditor, Lovelace } from "../../types";
import { ConfigValue, ConfigError } from "../types";
import { ConfigError } from "../types";
import { EntityConfig } from "../../entity-rows/types";
import { getCardElementTag } from "../../common/get-card-element-tag";
import { addCard, replaceCard } from "../config-util";
import { afterNextRender } from "../../../../common/util/render-status";
declare global {
@ -58,15 +58,17 @@ export class HuiEditCard extends LitElement {
public lovelace?: Lovelace;
public path?: [number] | [number, number];
public closeDialog?: () => void;
public saveCard?: (cardConf: LovelaceCardConfig) => Promise<void>;
public newCard?: boolean;
@property() private _configElement?: LovelaceCardEditor | null;
@property() private _uiEditor?: boolean;
@property() private _configValue?: ConfigValue;
@property() private _cardConfig?: LovelaceCardConfig;
@property() private _configState?: string;
@ -76,16 +78,28 @@ export class HuiEditCard extends LitElement {
@property() private _errorMsg?: TemplateResult;
private _cardType?: string;
private get _dialog(): PaperDialogElement {
return this.shadowRoot!.querySelector("paper-dialog")!;
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private get _previewEl(): HuiCardPreview {
return this.shadowRoot!.querySelector("hui-card-preview")!;
}
// tslint:disable-next-line
private __cardYaml: string | undefined;
private get _cardYaml(): string | undefined {
if (!this.__cardYaml) {
this.__cardYaml = yaml.safeDump(this._cardConfig);
}
return this.__cardYaml;
}
private set _cardYaml(yml: string | undefined) {
this.__cardYaml = yml;
}
public constructor() {
super();
this._saving = false;
@ -98,7 +112,8 @@ export class HuiEditCard extends LitElement {
return;
}
this._configValue = { format: "yaml", value: undefined };
this._cardConfig = undefined;
this._cardYaml = undefined;
this._configState = "OK";
this._uiEditor = true;
this._errorMsg = undefined;
@ -119,7 +134,7 @@ export class HuiEditCard extends LitElement {
: html`
<hui-yaml-editor
.hass="${this.hass}"
.value="${this._configValue!.value}"
.value="${this._cardYaml}"
@yaml-changed="${this._handleYamlChanged}"
@yaml-save="${this._save}"
></hui-yaml-editor>
@ -133,7 +148,7 @@ export class HuiEditCard extends LitElement {
}
return html`
<paper-dialog
<ha-paper-dialog
with-backdrop
opened
modal
@ -162,7 +177,6 @@ export class HuiEditCard extends LitElement {
<div class="paper-dialog-buttons">
<mwc-button
class="toggle-button"
?hidden="${!this._configValue || !this._configValue.value}"
?disabled="${this._configElement === null ||
this._configState !== "OK"}"
@click="${this._toggleEditor}"
@ -174,7 +188,6 @@ export class HuiEditCard extends LitElement {
>${this.hass!.localize("ui.common.cancel")}</mwc-button
>
<mwc-button
?hidden="${!this._configValue || !this._configValue.value}"
?disabled="${this._saving || this._configState !== "OK"}"
@click="${this._save}"
>
@ -187,7 +200,7 @@ export class HuiEditCard extends LitElement {
</div>
`
: ""}
</paper-dialog>
</ha-paper-dialog>
`;
}
@ -206,7 +219,7 @@ export class HuiEditCard extends LitElement {
private async _resizeDialog(): Promise<void> {
await this.updateComplete;
fireEvent(this._dialog, "iron-resize");
fireEvent(this._dialog as HTMLElement, "iron-resize");
}
private async _save(): Promise<void> {
@ -222,22 +235,9 @@ export class HuiEditCard extends LitElement {
this._saving = true;
const cardConf: LovelaceCardConfig =
this._configValue!.format === "yaml"
? yaml.safeLoad(this._configValue!.value!)
: this._configValue!.value!;
try {
const lovelace = this.lovelace!;
await lovelace.saveConfig(
this._creatingCard
? addCard(lovelace.config, this.path as [number], cardConf)
: replaceCard(
lovelace.config,
this.path as [number, number],
cardConf
)
);
await this.saveCard!(this._cardConfig!);
this._cardYaml = undefined;
this.closeDialog!();
} catch (err) {
alert(`Saving failed: ${err.message}`);
@ -247,12 +247,9 @@ export class HuiEditCard extends LitElement {
}
private _handleYamlChanged(ev: CustomEvent): void {
this._configValue = { format: "yaml", value: ev.detail.value };
this._cardConfig = yaml.safeLoad(ev.detail.value);
try {
const config = yaml.safeLoad(
this._configValue.value
) as LovelaceCardConfig;
this._updatePreview(config);
this._updatePreview(this._cardConfig!);
this._configState = "OK";
} catch (err) {
this._configState = "YAML_ERROR";
@ -264,7 +261,7 @@ export class HuiEditCard extends LitElement {
}
private _handleUIConfigChanged(value: LovelaceCardConfig): void {
this._configValue = { format: "json", value };
this._cardConfig = value;
this._updatePreview(value);
}
@ -294,35 +291,23 @@ export class HuiEditCard extends LitElement {
}
private async _toggleEditor(): Promise<void> {
if (this._uiEditor && this._configValue!.format === "json") {
this._configValue = {
format: "yaml",
value: yaml.safeDump(this._configValue!.value),
};
this._uiEditor = !this._uiEditor;
} else if (this._configElement && this._configValue!.format === "yaml") {
const yamlConfig = this._configValue!.value;
const cardConfig = yaml.safeLoad(yamlConfig) as LovelaceCardConfig;
this._uiEditor = !this._uiEditor;
if (cardConfig.type !== this._cardType) {
const succes = await this._loadConfigElement(cardConfig);
if (!succes) {
this._cardYaml = undefined;
if (this._uiEditor) {
this._uiEditor = false;
} else if (this._configElement) {
const success = await this._loadConfigElement(this._cardConfig!);
if (!success) {
this._loadedDialog();
}
this._cardType = cardConfig.type;
} else {
this._configValue = {
format: "json",
value: cardConfig,
};
this._configElement.setConfig(cardConfig);
this._uiEditor = true;
this._configElement.setConfig(this._cardConfig!);
}
}
this._resizeDialog();
}
private _isConfigValid(): boolean {
if (!this._configValue || !this._configValue.value) {
if (!this._cardConfig) {
return false;
}
if (this._configState === "OK") {
@ -333,14 +318,10 @@ export class HuiEditCard extends LitElement {
}
private _isConfigChanged(): boolean {
if (this._creatingCard) {
if (this.newCard) {
return true;
}
const configValue =
this._configValue!.format === "yaml"
? yaml.safeLoad(this._configValue!.value)
: this._configValue!.value;
return JSON.stringify(configValue) !== JSON.stringify(this.cardConfig);
return JSON.stringify(this._cardConfig) !== JSON.stringify(this.cardConfig);
}
private async _loadConfigElement(conf: LovelaceCardConfig): Promise<boolean> {
@ -357,10 +338,11 @@ export class HuiEditCard extends LitElement {
const elClass = customElements.get(tag);
let configElement;
this._cardConfig = conf;
if (elClass && elClass.getConfigElement) {
configElement = await elClass.getConfigElement();
} else {
this._configValue = { format: "yaml", value: yaml.safeDump(conf) };
this._updatePreview(conf);
this._uiEditor = false;
this._configElement = null;
@ -374,10 +356,6 @@ export class HuiEditCard extends LitElement {
Your config is not supported by the UI editor:<br /><b>${err.message}</b
><br />Falling back to YAML editor.
`;
this._configValue = {
format: "yaml",
value: yaml.safeDump(conf),
};
this._updatePreview(conf);
this._uiEditor = false;
this._configElement = null;
@ -388,17 +366,12 @@ export class HuiEditCard extends LitElement {
configElement.addEventListener("config-changed", (ev) =>
this._handleUIConfigChanged(ev.detail.config)
);
this._configValue = { format: "json", value: conf };
this._configElement = configElement;
await this.updateComplete;
this._updatePreview(conf);
return true;
}
private get _creatingCard(): boolean {
return this.path!.length === 1;
}
private _openedChanged(ev): void {
if (!ev.detail.value) {
this.closeDialog!();

View File

@ -16,11 +16,11 @@ import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-alarm-panel-card";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon";
import { AlarmPanelCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -34,9 +34,9 @@ export class HuiAlarmPanelCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: AlarmPanelCardConfig;
public setConfig(config: Config): void {
public setConfig(config: AlarmPanelCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -10,23 +10,23 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "../../../../components/entity/state-badge";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import { processEditorEntities } from "../process-editor-entities";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { configElementStyle } from "./config-elements-style";
import {
EntitiesCardConfig,
EntitiesCardEntityConfig,
} from "../../cards/hui-entities-card";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/state-badge";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
} from "../../cards/types";
const entitiesConfigStruct = struct.union([
{

View File

@ -7,6 +7,10 @@ import {
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../components/hui-action-editor";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import { struct } from "../../common/structs/struct";
import {
EntitiesEditorEvent,
@ -16,19 +20,17 @@ import {
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-entity-button-card";
import { configElementStyle } from "./config-elements-style";
import { ActionConfig } from "../../../../data/lovelace";
import "../../components/hui-action-editor";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import { EntityButtonCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
entity: "string?",
name: "string?",
show_name: "boolean?",
icon: "string?",
show_icon: "boolean?",
tap_action: struct.optional(actionConfigStruct),
hold_action: struct.optional(actionConfigStruct),
theme: "string?",
@ -39,9 +41,9 @@ export class HuiEntityButtonCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: EntityButtonCardConfig;
public setConfig(config: Config): void {
public setConfig(config: EntityButtonCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}
@ -54,10 +56,18 @@ export class HuiEntityButtonCardEditor extends LitElement
return this._config!.name || "";
}
get _show_name(): boolean {
return this._config!.show_name || true;
}
get _icon(): string {
return this._config!.icon || "";
}
get _show_icon(): boolean {
return this._config!.show_icon || true;
}
get _tap_action(): ActionConfig {
return this._config!.tap_action || { action: "more-info" };
}
@ -101,6 +111,20 @@ export class HuiEntityButtonCardEditor extends LitElement
@value-changed="${this._valueChanged}"
></paper-input>
</div>
<div class="side-by-side">
<paper-toggle-button
?checked="${this._config!.show_name !== false}"
.configValue="${"show_name"}"
@change="${this._valueChanged}"
>Show Name?</paper-toggle-button
>
<paper-toggle-button
?checked="${this._config!.show_icon !== false}"
.configValue="${"show_icon"}"
@change="${this._valueChanged}"
>Show Icon?</paper-toggle-button
>
</div>
<hui-theme-select-editor
.hass="${this.hass}"
.value="${this._theme}"
@ -147,7 +171,12 @@ export class HuiEntityButtonCardEditor extends LitElement
} else {
this._config = {
...this._config,
[target.configValue!]: target.value ? target.value : target.config,
[target.configValue!]:
target.checked !== undefined
? target.checked
: target.value
? target.value
: target.config,
};
}
}

View File

@ -10,16 +10,16 @@ import {
import "@polymer/paper-input/paper-input";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config, SeverityConfig } from "../../cards/hui-gauge-card";
import { configElementStyle } from "./config-elements-style";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import { GaugeCardConfig, SeverityConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -37,11 +37,11 @@ export class HuiGaugeCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: GaugeCardConfig;
private _useSeverity?: boolean;
public setConfig(config: Config): void {
public setConfig(config: GaugeCardConfig): void {
config = cardConfigStruct(config);
this._useSeverity = !!config.severity;
this._config = config;

View File

@ -10,20 +10,20 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "../../../../components/entity/state-badge";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import { struct } from "../../common/structs/struct";
import { processEditorEntities } from "../process-editor-entities";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { GlanceCardConfig, ConfigEntity } from "../../cards/hui-glance-card";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/state-badge";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import { GlanceCardConfig, ConfigEntity } from "../../cards/types";
const entitiesConfigStruct = struct.union([
{

View File

@ -12,8 +12,8 @@ import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-iframe-card";
import { configElementStyle } from "./config-elements-style";
import { IframeCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -27,9 +27,9 @@ export class HuiIframeCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: IframeCardConfig;
public setConfig(config: Config): void {
public setConfig(config: IframeCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -7,16 +7,16 @@ import {
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-light-card";
import { configElementStyle } from "./config-elements-style";
import "../../components/hui-theme-select-editor";
import "../../components/hui-entity-editor";
import { LightCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -30,9 +30,9 @@ export class HuiLightCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: LightCardConfig;
public setConfig(config: Config): void {
public setConfig(config: LightCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -9,19 +9,19 @@ import {
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../components/hui-entity-editor";
import "../../components/hui-input-list-editor";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { MapCardConfig } from "../../cards/hui-map-card";
import { configElementStyle } from "./config-elements-style";
import { processEditorEntities } from "../process-editor-entities";
import { EntityConfig } from "../../entity-rows/types";
import { PolymerChangedEvent } from "../../../../polymer-types";
import "../../components/hui-entity-editor";
import "../../components/hui-input-list-editor";
import { MapCardConfig } from "../../cards/types";
const entitiesConfigStruct = struct.union([
{

View File

@ -13,8 +13,8 @@ import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-markdown-card";
import { configElementStyle } from "./config-elements-style";
import { MarkdownCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -27,9 +27,9 @@ export class HuiMarkdownCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: MarkdownCardConfig;
public setConfig(config: Config): void {
public setConfig(config: MarkdownCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -11,7 +11,7 @@ import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-media-control-card";
import { MediaControlCardConfig } from "../../cards/hui-media-control-card";
import "../../../../components/entity/ha-entity-picker";
@ -25,9 +25,9 @@ export class HuiMediaControlCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: MediaControlCardConfig;
public setConfig(config: Config): void {
public setConfig(config: MediaControlCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -7,6 +7,8 @@ import {
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../components/hui-action-editor";
import { struct } from "../../common/structs/struct";
import {
EntitiesEditorEvent,
@ -16,11 +18,9 @@ import {
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-picture-card";
import { configElementStyle } from "./config-elements-style";
import { ActionConfig } from "../../../../data/lovelace";
import "../../components/hui-action-editor";
import { PictureCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -34,9 +34,9 @@ export class HuiPictureCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: PictureCardConfig;
public setConfig(config: Config): void {
public setConfig(config: PictureCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -7,16 +7,16 @@ import {
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-alarm-panel-card";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon";
import { PlantStatusCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -29,9 +29,9 @@ export class HuiPlantStatusCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: PlantStatusCardConfig;
public setConfig(config: Config): void {
public setConfig(config: PlantStatusCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -10,16 +10,16 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "../../components/hui-theme-select-editor";
import "../../../../components/entity/ha-entity-picker";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-sensor-card";
import { configElementStyle } from "./config-elements-style";
import "../../components/hui-theme-select-editor";
import "../../../../components/entity/ha-entity-picker";
import { SensorCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -38,9 +38,9 @@ export class HuiSensorCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: SensorCardConfig;
public setConfig(config: Config): void {
public setConfig(config: SensorCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -12,7 +12,7 @@ import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-shopping-list-card";
import { ShoppingListCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -24,9 +24,9 @@ export class HuiShoppingListEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ShoppingListCardConfig;
public setConfig(config: Config): void {
public setConfig(config: ShoppingListCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -7,16 +7,16 @@ import {
} from "lit-element";
import "@polymer/paper-input/paper-input";
import "../../components/hui-theme-select-editor";
import "../../../../components/entity/ha-entity-picker";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-thermostat-card";
import { configElementStyle } from "./config-elements-style";
import "../../components/hui-theme-select-editor";
import "../../../../components/entity/ha-entity-picker";
import { ThermostatCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -30,9 +30,9 @@ export class HuiThermostatCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ThermostatCardConfig;
public setConfig(config: Config): void {
public setConfig(config: ThermostatCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -6,15 +6,15 @@ import {
property,
} from "lit-element";
import "../../../../components/entity/ha-entity-picker";
import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Config } from "../../cards/hui-weather-forecast-card";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/ha-entity-picker";
import { WeatherForecastCardConfig } from "../../cards/types";
const cardConfigStruct = struct({
type: "string",
@ -27,9 +27,9 @@ export class HuiWeatherForecastCardEditor extends LitElement
implements LovelaceCardEditor {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: WeatherForecastCardConfig;
public setConfig(config: Config): void {
public setConfig(config: WeatherForecastCardConfig): void {
config = cardConfigStruct(config);
this._config = config;
}

View File

@ -1,24 +1,21 @@
import {
Condition,
checkConditionsMet,
validateConditionalConfig,
} from "../../lovelace/common/validate-condition";
import { createStyledHuiElement } from "../cards/picture-elements/create-styled-hui-element";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import {
LovelaceElement,
LovelaceElementConfig,
ConditionalElementConfig,
} from "./types";
import { HomeAssistant } from "../../../types";
interface Config extends LovelaceElementConfig {
conditions: Condition[];
elements: LovelaceElementConfig[];
}
class HuiConditionalElement extends HTMLElement implements LovelaceElement {
public _hass?: HomeAssistant;
private _config?: Config;
private _config?: ConditionalElementConfig;
private _elements: LovelaceElement[] = [];
public setConfig(config: Config): void {
public setConfig(config: ConditionalElementConfig): void {
if (
!config.conditions ||
!Array.isArray(config.conditions) ||

View File

@ -13,24 +13,15 @@ import "../../../components/ha-icon";
import { computeTooltip } from "../common/compute-tooltip";
import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { LovelaceElement, IconElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { ActionConfig } from "../../../data/lovelace";
export interface Config extends LovelaceElementConfig {
entity?: string;
name?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
icon: string;
}
@customElement("hui-icon-element")
export class HuiIconElement extends LitElement implements LovelaceElement {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
public hass?: HomeAssistant;
@property() private _config?: IconElementConfig;
public setConfig(config: Config): void {
public setConfig(config: IconElementConfig): void {
if (!config.icon) {
throw Error("Invalid Configuration: 'icon' required");
}

View File

@ -13,28 +13,15 @@ import "../components/hui-image";
import { computeTooltip } from "../common/compute-tooltip";
import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { LovelaceElement, ImageElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { ActionConfig } from "../../../data/lovelace";
export interface Config extends LovelaceElementConfig {
entity?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
image?: string;
state_image?: string;
camera_image?: string;
filter?: string;
state_filter?: string;
aspect_ratio?: string;
}
@customElement("hui-image-element")
export class HuiImageElement extends LitElement implements LovelaceElement {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ImageElementConfig;
public setConfig(config: Config): void {
public setConfig(config: ImageElementConfig): void {
if (!config) {
throw Error("Error in element configuration");
}

View File

@ -10,20 +10,14 @@ import {
import "../../../components/buttons/ha-call-service-button";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { LovelaceElement, ServiceButtonElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
export interface Config extends LovelaceElementConfig {
title?: string;
service?: string;
service_data?: object;
}
@customElement("hui-service-button-element")
export class HuiServiceButtonElement extends LitElement
implements LovelaceElement {
public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: ServiceButtonElementConfig;
private _domain?: string;
private _service?: string;
@ -31,7 +25,7 @@ export class HuiServiceButtonElement extends LitElement
return { _config: {} };
}
public setConfig(config: Config): void {
public setConfig(config: ServiceButtonElementConfig): void {
if (!config || !config.service) {
throw Error("Invalid Configuration: 'service' required");
}

View File

@ -4,26 +4,24 @@ import {
TemplateResult,
customElement,
property,
PropertyValues,
} from "lit-element";
import "../../../components/entity/ha-state-label-badge";
import "../components/hui-warning";
import "../components/hui-warning-element";
import computeStateName from "../../../common/entity/compute_state_name";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { LovelaceElement, StateBadgeElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
export interface Config extends LovelaceElementConfig {
entity: string;
}
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-state-badge-element")
export class HuiStateBadgeElement extends LitElement
implements LovelaceElement {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: StateBadgeElementConfig;
public setConfig(config: Config): void {
public setConfig(config: StateBadgeElementConfig): void {
if (!config.entity) {
throw Error("Invalid Configuration: 'entity' required");
}
@ -31,6 +29,10 @@ export class HuiStateBadgeElement extends LitElement
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;
@ -40,13 +42,13 @@ export class HuiStateBadgeElement extends LitElement
if (!stateObj) {
return html`
<hui-warning
>${this.hass.localize(
<hui-warning-element
label="${this.hass.localize(
"ui.panel.lovelace.warning.entity_not_found",
"entity",
this._config.entity
)}</hui-warning
>
)}"
></hui-warning-element>
`;
}

View File

@ -6,30 +6,25 @@ import {
property,
css,
CSSResult,
PropertyValues,
} from "lit-element";
import "../../../components/entity/state-badge";
import "../components/hui-warning";
import "../components/hui-warning-element";
import { computeTooltip } from "../common/compute-tooltip";
import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { LovelaceElement, StateIconElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { ActionConfig } from "../../../data/lovelace";
export interface Config extends LovelaceElementConfig {
entity: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-state-icon-element")
export class HuiStateIconElement extends LitElement implements LovelaceElement {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: StateIconElementConfig;
public setConfig(config: Config): void {
public setConfig(config: StateIconElementConfig): void {
if (!config.entity) {
throw Error("Invalid Configuration: 'entity' required");
}
@ -37,6 +32,10 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;
@ -46,13 +45,13 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
if (!stateObj) {
return html`
<hui-warning
>${this.hass.localize(
<hui-warning-element
label=${this.hass.localize(
"ui.panel.lovelace.warning.entity_not_found",
"entity",
this._config.entity
)}</hui-warning
>
)}
></hui-warning-element>
`;
}

View File

@ -6,33 +6,26 @@ import {
property,
css,
CSSResult,
PropertyValues,
} from "lit-element";
import "../../../components/entity/ha-state-label-badge";
import "../components/hui-warning";
import "../components/hui-warning-element";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import { computeTooltip } from "../common/compute-tooltip";
import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { LovelaceElement, StateLabelElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { ActionConfig } from "../../../data/lovelace";
interface Config extends LovelaceElementConfig {
entity: string;
prefix?: string;
suffix?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-state-label-element")
class HuiStateLabelElement extends LitElement implements LovelaceElement {
@property() public hass?: HomeAssistant;
@property() private _config?: Config;
@property() private _config?: StateLabelElementConfig;
public setConfig(config: Config): void {
public setConfig(config: StateLabelElementConfig): void {
if (!config.entity) {
throw Error("Invalid Configuration: 'entity' required");
}
@ -40,6 +33,10 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;
@ -49,13 +46,13 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
if (!stateObj) {
return html`
<hui-warning
>${this.hass.localize(
<hui-warning-element
label=${this.hass.localize(
"ui.panel.lovelace.warning.entity_not_found",
"entity",
this._config.entity
)}</hui-warning
>
)}
></hui-warning-element>
`;
}

View File

@ -1,4 +1,6 @@
import { HomeAssistant } from "../../../types";
import { Condition } from "../common/validate-condition";
import { ActionConfig } from "../../../data/lovelace";
export interface LovelaceElementConfig {
type: string;
@ -9,3 +11,52 @@ export interface LovelaceElement extends HTMLElement {
hass?: HomeAssistant;
setConfig(config: LovelaceElementConfig): void;
}
export interface ConditionalElementConfig extends LovelaceElementConfig {
conditions: Condition[];
elements: LovelaceElementConfig[];
}
export interface IconElementConfig extends LovelaceElementConfig {
entity?: string;
name?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
icon: string;
}
export interface ImageElementConfig extends LovelaceElementConfig {
entity?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
image?: string;
state_image?: string;
camera_image?: string;
filter?: string;
state_filter?: string;
aspect_ratio?: string;
}
export interface ServiceButtonElementConfig extends LovelaceElementConfig {
title?: string;
service?: string;
service_data?: object;
}
export interface StateBadgeElementConfig extends LovelaceElementConfig {
entity: string;
}
export interface StateIconElementConfig extends LovelaceElementConfig {
entity: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}
export interface StateLabelElementConfig extends LovelaceElementConfig {
entity: string;
prefix?: string;
suffix?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
}

View File

@ -6,6 +6,7 @@ import {
css,
CSSResult,
customElement,
PropertyValues,
} from "lit-element";
import "../../../components/ha-climate-state";
@ -14,6 +15,7 @@ import "../components/hui-warning";
import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-climate-entity-row")
class HuiClimateEntityRow extends LitElement implements EntityRow {
@ -29,6 +31,10 @@ class HuiClimateEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this.hass || !this._config) {
return html``;

View File

@ -6,6 +6,7 @@ import {
css,
CSSResult,
customElement,
PropertyValues,
} from "lit-element";
import "../components/hui-generic-entity-row";
@ -16,6 +17,7 @@ import "../components/hui-warning";
import { isTiltOnly } from "../../../util/cover-model";
import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-cover-entity-row")
class HuiCoverEntityRow extends LitElement implements EntityRow {
@ -30,6 +32,10 @@ class HuiCoverEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;

View File

@ -4,6 +4,7 @@ import {
TemplateResult,
property,
customElement,
PropertyValues,
} from "lit-element";
import "../components/hui-generic-entity-row";
@ -11,9 +12,11 @@ import "../../../components/entity/ha-entity-toggle";
import "../components/hui-warning";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import { DOMAINS_TOGGLE } from "../../../common/const";
import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-group-entity-row")
class HuiGroupEntityRow extends LitElement implements EntityRow {
@ -28,6 +31,10 @@ class HuiGroupEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;

View File

@ -6,6 +6,7 @@ import {
customElement,
css,
CSSResult,
PropertyValues,
} from "lit-element";
import "../components/hui-generic-entity-row";
@ -16,6 +17,7 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { EntityRow, EntityConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { setValue } from "../../../data/input_text";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-input-number-entity-row")
class HuiInputNumberEntityRow extends LitElement implements EntityRow {
@ -48,6 +50,10 @@ class HuiInputNumberEntityRow extends LitElement implements EntityRow {
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;

View File

@ -6,6 +6,7 @@ import {
css,
CSSResult,
customElement,
PropertyValues,
} from "lit-element";
import { repeat } from "lit-html/directives/repeat";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
@ -16,9 +17,11 @@ import "../../../components/entity/state-badge";
import "../components/hui-warning";
import computeStateName from "../../../common/entity/compute_state_name";
import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { setOption } from "../../../data/input-select";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-input-select-entity-row")
class HuiInputSelectEntityRow extends LitElement implements EntityRow {
@ -34,6 +37,10 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this.hass || !this._config) {
return html``;

View File

@ -4,6 +4,7 @@ import {
TemplateResult,
property,
customElement,
PropertyValues,
} from "lit-element";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
@ -13,6 +14,7 @@ import "../components/hui-warning";
import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { setValue } from "../../../data/input_text";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-input-text-entity-row")
class HuiInputTextEntityRow extends LitElement implements EntityRow {
@ -27,6 +29,10 @@ class HuiInputTextEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;

View File

@ -6,6 +6,7 @@ import {
css,
CSSResult,
customElement,
PropertyValues,
} from "lit-element";
import "../components/hui-generic-entity-row";
@ -13,6 +14,7 @@ import "../components/hui-warning";
import { HomeAssistant } from "../../../types";
import { EntityRow, EntityConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-lock-entity-row")
class HuiLockEntityRow extends LitElement implements EntityRow {
@ -27,6 +29,10 @@ class HuiLockEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
return html``;

View File

@ -6,6 +6,7 @@ import {
CSSResult,
property,
customElement,
PropertyValues,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button";
@ -22,6 +23,7 @@ import {
OFF_STATES,
SUPPORT_PAUSE,
} from "../../../data/media-player";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@customElement("hui-media-player-entity-row")
class HuiMediaPlayerEntityRow extends LitElement implements EntityRow {
@ -37,6 +39,10 @@ class HuiMediaPlayerEntityRow extends LitElement implements EntityRow {
this._config = config;
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
protected render(): TemplateResult | void {
if (!this.hass || !this._config) {
return html``;

Some files were not shown because too many files have changed in this diff Show More