Merge pull request #2493 from home-assistant/dev

20190116.0
This commit is contained in:
Paulus Schoutsen 2019-01-16 16:13:01 -08:00 committed by GitHub
commit 00ad91af9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
165 changed files with 3039 additions and 744 deletions

View File

@ -1,12 +1,11 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive"; import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive";
export class DemoUtilLongPress extends LitElement { export class DemoUtilLongPress extends LitElement {
public render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
${ ${

View File

@ -17,7 +17,6 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@material/mwc-ripple": "^0.3.1",
"@mdi/svg": "^3.0.39", "@mdi/svg": "^3.0.39",
"@polymer/app-layout": "^3.0.1", "@polymer/app-layout": "^3.0.1",
"@polymer/app-localize-behavior": "^3.0.1", "@polymer/app-localize-behavior": "^3.0.1",
@ -35,7 +34,6 @@
"@polymer/iron-media-query": "^3.0.1", "@polymer/iron-media-query": "^3.0.1",
"@polymer/iron-pages": "^3.0.1", "@polymer/iron-pages": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1", "@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/lit-element": "0.6.2",
"@polymer/neon-animation": "^3.0.1", "@polymer/neon-animation": "^3.0.1",
"@polymer/paper-button": "^3.0.1", "@polymer/paper-button": "^3.0.1",
"@polymer/paper-card": "^3.0.1", "@polymer/paper-card": "^3.0.1",
@ -78,7 +76,8 @@
"jquery": "^3.3.1", "jquery": "^3.3.1",
"js-yaml": "^3.12.0", "js-yaml": "^3.12.0",
"leaflet": "^1.3.4", "leaflet": "^1.3.4",
"lit-html": "0.12.0", "lit-element": "2.0.0-rc.2",
"lit-html": "1.0.0-rc.2",
"marked": "^0.5.0", "marked": "^0.5.0",
"mdn-polyfills": "^5.12.0", "mdn-polyfills": "^5.12.0",
"moment": "^2.22.2", "moment": "^2.22.2",
@ -158,9 +157,7 @@
"@webcomponents/shadycss": "^1.6.0", "@webcomponents/shadycss": "^1.6.0",
"@vaadin/vaadin-overlay": "3.2.2", "@vaadin/vaadin-overlay": "3.2.2",
"@vaadin/vaadin-lumo-styles": "1.3.0", "@vaadin/vaadin-lumo-styles": "1.3.0",
"fecha": "https://github.com/taylorhakes/fecha/archive/5e8fe08d982647fdb19fb403459838b02647813c.tar.gz", "fecha": "https://github.com/taylorhakes/fecha/archive/5e8fe08d982647fdb19fb403459838b02647813c.tar.gz"
"lit-html": "0.12.0",
"@polymer/lit-element": "0.6.2"
}, },
"main": "src/home-assistant.js", "main": "src/home-assistant.js",
"husky": { "husky": {

View File

@ -10,9 +10,10 @@ function patch(version) {
function today() { function today() {
const now = new Date(); const now = new Date();
return `${now.getFullYear()}${now.getMonth() + 1}${String( return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(
now.getDate() 2,
).padStart(2, "0")}.0`; "0"
)}${String(now.getDate()).padStart(2, "0")}.0`;
} }
const methods = { const methods = {
@ -51,4 +52,4 @@ async function main(args) {
console.log(stdout); console.log(stdout);
} }
main(process.argv.slice(2)); main(process.argv.slice(2)).catch(console.error);

View File

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

View File

@ -1,5 +1,5 @@
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin"; import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { LitElement, html, PropertyDeclarations } from "@polymer/lit-element"; import { LitElement, html, PropertyDeclarations } from "lit-element";
import "./ha-auth-flow"; import "./ha-auth-flow";
import { AuthProvider } from "../data/auth"; import { AuthProvider } from "../data/auth";
@ -50,7 +50,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
}; };
} }
public render() { protected render() {
if (!this._authProviders) { if (!this._authProviders) {
return html` return html`
<p>[[localize('ui.panel.page-authorize.initializing')]]</p> <p>[[localize('ui.panel.page-authorize.initializing')]]</p>

View File

@ -8,7 +8,7 @@
export const DEFAULT_DOMAIN_ICON = "hass:bookmark"; export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
/** Panel to show when no panel is picked. */ /** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "states"; export const DEFAULT_PANEL = "lovelace";
/** Domains that have a state card. */ /** Domains that have a state card. */
export const DOMAINS_WITH_CARD = [ export const DOMAINS_WITH_CARD = [

View File

@ -1,4 +1,4 @@
import { LitElement, html } from "@polymer/lit-element"; import { LitElement, html } from "lit-element";
import "./ha-progress-button"; import "./ha-progress-button";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";

View File

@ -3,10 +3,10 @@ import {
html, html,
PropertyValues, PropertyValues,
PropertyDeclarations, PropertyDeclarations,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { classMap } from "lit-html/directives/classMap"; import { classMap } from "lit-html/directives/class-map";
import computeStateDomain from "../../common/entity/compute_state_domain"; import computeStateDomain from "../../common/entity/compute_state_domain";
import computeStateName from "../../common/entity/compute_state_name"; import computeStateName from "../../common/entity/compute_state_name";
@ -41,7 +41,7 @@ export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
this.clearInterval(); this.clearInterval();
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
const state = this.state; const state = this.state;
if (!state) { if (!state) {

View File

@ -1,10 +1,13 @@
import { import {
html,
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult, html } from "lit-html"; CSSResult,
import { classMap } from "lit-html/directives/classMap"; css,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "./ha-icon"; import "./ha-icon";
class HaLabelBadge extends LitElement { class HaLabelBadge extends LitElement {
@ -24,9 +27,8 @@ class HaLabelBadge extends LitElement {
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()}
<div class="badge-container"> <div class="badge-container">
<div class="label-badge" id="badge"> <div class="label-badge" id="badge">
<div <div
@ -77,9 +79,9 @@ class HaLabelBadge extends LitElement {
`; `;
} }
protected renderStyle(): TemplateResult { static get styles(): CSSResult[] {
return html` return [
<style> css`
.badge-container { .badge-container {
display: inline-block; display: inline-block;
text-align: center; text-align: center;
@ -148,8 +150,8 @@ class HaLabelBadge extends LitElement {
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: normal; line-height: normal;
} }
</style> `,
`; ];
} }
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {

View File

@ -20,6 +20,7 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
<paper-toggle-button <paper-toggle-button
disabled="[[_compDisabled(disabled, loading)]]" disabled="[[_compDisabled(disabled, loading)]]"
checked="{{pushChecked}}" checked="{{pushChecked}}"
on-change="handlePushChange"
></paper-toggle-button> ></paper-toggle-button>
`; `;
} }
@ -35,7 +36,6 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
type: Boolean, type: Boolean,
value: value:
"Notification" in window && Notification.permission === "granted", "Notification" in window && Notification.permission === "granted",
observer: "handlePushChange",
}, },
loading: { loading: {
type: Boolean, type: Boolean,
@ -63,12 +63,12 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
} }
} }
handlePushChange(pushChecked) { handlePushChange(event) {
// Somehow this is triggered on Safari on page load causing // Somehow this is triggered on Safari on page load causing
// it to get into a loop and crash the page. // it to get into a loop and crash the page.
if (!pushSupported) return; if (!pushSupported) return;
if (pushChecked) { if (event.target.checked) {
this.subscribePushNotifications(); this.subscribePushNotifications();
} else { } else {
this.unsubscribePushNotifications(); this.unsubscribePushNotifications();
@ -77,10 +77,9 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
async subscribePushNotifications() { async subscribePushNotifications() {
const reg = await navigator.serviceWorker.ready; const reg = await navigator.serviceWorker.ready;
let sub;
try { try {
const sub = await reg.pushManager.subscribe({ userVisibleOnly: true });
let browserName; let browserName;
if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1) { if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1) {
browserName = "firefox"; browserName = "firefox";
@ -88,12 +87,24 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
browserName = "chrome"; browserName = "chrome";
} }
const name = prompt("What should this device be called ?");
if (name == null) {
this.pushChecked = false;
return;
}
sub = await reg.pushManager.subscribe({ userVisibleOnly: true });
await this.hass.callApi("POST", "notify.html5", { await this.hass.callApi("POST", "notify.html5", {
subscription: sub, subscription: sub,
browser: browserName, browser: browserName,
name,
}); });
} catch (err) { } catch (err) {
const message = err.message || "Notification registration failed."; const message = err.message || "Notification registration failed.";
if (sub) {
await sub.unsubscribe();
}
// eslint-disable-next-line // eslint-disable-next-line
console.error(err); console.error(err);

View File

@ -1,8 +1,28 @@
import "@polymer/paper-slider"; import "@polymer/paper-slider";
const PaperSliderClass = customElements.get("paper-slider"); const PaperSliderClass = customElements.get("paper-slider");
let subTemplate;
class HaSlider extends PaperSliderClass { class HaSlider extends PaperSliderClass {
static get template() {
if (!subTemplate) {
subTemplate = PaperSliderClass.template.cloneNode(true);
const superStyle = subTemplate.content.querySelector("style");
// append style to add mirroring of pin in RTL
superStyle.appendChild(
document.createTextNode(`
:host([dir="rtl"]) #sliderContainer.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;
}
`)
);
}
return subTemplate;
}
_calcStep(value) { _calcStep(value) {
if (!this.step) { if (!this.step) {
return parseFloat(value); return parseFloat(value);

View File

@ -20,6 +20,10 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
:host([rendered]) { :host([rendered]) {
opacity: 1; opacity: 1;
} }
ha-chart-base {
direction: ltr;
}
</style> </style>
<ha-chart-base <ha-chart-base
data="[[chartData]]" data="[[chartData]]"
@ -185,6 +189,11 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
afterSetDimensions: (yaxe) => { afterSetDimensions: (yaxe) => {
yaxe.maxWidth = yaxe.chart.width * 0.18; yaxe.maxWidth = yaxe.chart.width * 0.18;
}, },
position: this.hass.translationMetadata.translations[
this.hass.selectedLanguage || this.hass.language
].isRTL
? "right"
: "left",
}, },
], ],
}, },

View File

@ -1,5 +1,8 @@
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
export const FORMAT_TEXT = "text";
export const FORMAT_NUMBER = "number";
export const callAlarmAction = ( export const callAlarmAction = (
hass: HomeAssistant, hass: HomeAssistant,
entity: string, entity: string,

104
src/data/zha.ts Normal file
View File

@ -0,0 +1,104 @@
import { HassEntity } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
export interface ZHADeviceEntity extends HassEntity {
device_info?: {
identifiers: any[];
};
}
export interface ZHAEntities {
[key: string]: HassEntity[];
}
export interface Attribute {
name: string;
id: number;
}
export interface Cluster {
name: string;
id: number;
type: string;
}
export interface Command {
name: string;
id: number;
type: string;
}
export interface ReadAttributeServiceData {
entity_id: string;
cluster_id: number;
cluster_type: string;
attribute: number;
manufacturer: number;
}
export const reconfigureNode = (
hass: HomeAssistant,
ieeeAddress: string
): Promise<void> =>
hass.callWS({
type: "zha/nodes/reconfigure",
ieee: ieeeAddress,
});
export const fetchAttributesForCluster = (
hass: HomeAssistant,
entityId: string,
ieeeAddress: string,
clusterId: number,
clusterType: string
): Promise<Attribute[]> =>
hass.callWS({
type: "zha/entities/clusters/attributes",
entity_id: entityId,
ieee: ieeeAddress,
cluster_id: clusterId,
cluster_type: clusterType,
});
export const readAttributeValue = (
hass: HomeAssistant,
data: ReadAttributeServiceData
): Promise<string> => {
return hass.callWS({
...data,
type: "zha/entities/clusters/attributes/value",
});
};
export const fetchCommandsForCluster = (
hass: HomeAssistant,
entityId: string,
ieeeAddress: string,
clusterId: number,
clusterType: string
): Promise<Command[]> =>
hass.callWS({
type: "zha/entities/clusters/commands",
entity_id: entityId,
ieee: ieeeAddress,
cluster_id: clusterId,
cluster_type: clusterType,
});
export const fetchClustersForZhaNode = (
hass: HomeAssistant,
entityId: string,
ieeeAddress: string
): Promise<Cluster[]> =>
hass.callWS({
type: "zha/entities/clusters",
entity_id: entityId,
ieee: ieeeAddress,
});
export const fetchEntitiesForZhaNode = (
hass: HomeAssistant
): Promise<ZHAEntities> =>
hass.callWS({
type: "zha/entities",
});

View File

@ -72,6 +72,18 @@ function initPushNotifications() {
var data; var data;
if (event.data) { if (event.data) {
data = event.data.json(); data = event.data.json();
if (data.dismiss) {
event.waitUntil(
self.registration
.getNotifications({ tag: data.tag })
.then(function(notifications) {
for (const n of notifications) {
n.close();
}
})
);
return;
}
event.waitUntil( event.waitUntil(
self.registration self.registration
.showNotification(data.title, data) .showNotification(data.title, data)
@ -96,7 +108,11 @@ function initPushNotifications() {
event.notification.close(); event.notification.close();
if (!event.notification.data || !event.notification.data.url) { if (
event.action ||
!event.notification.data ||
!event.notification.data.url
) {
return; return;
} }

View File

@ -1,5 +1,5 @@
import { PolymerElement } from "@polymer/polymer"; import { PolymerElement } from "@polymer/polymer";
import { Constructor } from "@polymer/lit-element"; import { Constructor } from "lit-element";
import { HASSDomEvent, ValidHassDomEvent } from "../../common/dom/fire_event"; import { HASSDomEvent, ValidHassDomEvent } from "../../common/dom/fire_event";
interface RegisterDialogParams { interface RegisterDialogParams {

View File

@ -4,7 +4,7 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { afterNextRender } from "@polymer/polymer/lib/utils/render-status"; import { afterNextRender } from "@polymer/polymer/lib/utils/render-status";
import { html as litHtml, LitElement } from "@polymer/lit-element"; import { html as litHtml, LitElement } from "lit-element";
import "../home-assistant-main"; import "../home-assistant-main";
import "../ha-init-page"; import "../ha-init-page";
@ -95,7 +95,11 @@ class HomeAssistant extends ext(PolymerElement, [
} }
computePanelUrl(routeData) { computePanelUrl(routeData) {
return (routeData && routeData.panel) || DEFAULT_PANEL; return (
(routeData && routeData.panel) ||
localStorage.defaultPage ||
DEFAULT_PANEL
);
} }
panelUrlChanged(newPanelUrl) { panelUrlChanged(newPanelUrl) {

View File

@ -3,7 +3,7 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; } from "lit-element";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { getActiveTranslation } from "../util/hass-translation"; import { getActiveTranslation } from "../util/hass-translation";
import { LocalizeFunc, LocalizeMixin } from "./localize-base-mixin"; import { LocalizeFunc, LocalizeMixin } from "./localize-base-mixin";

View File

@ -3,7 +3,7 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; } from "lit-element";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { import {
localizeBaseMixin, localizeBaseMixin,

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card"; import "@polymer/paper-card/paper-card";
import "@polymer/paper-toggle-button/paper-toggle-button"; import "@polymer/paper-toggle-button/paper-toggle-button";
@ -23,7 +27,7 @@ export class CloudAlexaPref extends LitElement {
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.cloudStatus) { if (!this.cloudStatus) {
return html``; return html``;
} }

View File

@ -3,8 +3,8 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { repeat } from "lit-html/directives/repeat"; import { repeat } from "lit-html/directives/repeat";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import { HassEntityBase } from "home-assistant-js-websocket"; import { HassEntityBase } from "home-assistant-js-websocket";
@ -34,7 +34,7 @@ export class CloudExposedEntities extends LitElement {
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._filterFunc) { if (!this._filterFunc) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card"; import "@polymer/paper-card/paper-card";
import "@polymer/paper-toggle-button/paper-toggle-button"; import "@polymer/paper-toggle-button/paper-toggle-button";
@ -24,7 +28,7 @@ export class CloudGooglePref extends LitElement {
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.cloudStatus) { if (!this.cloudStatus) {
return html``; return html``;
} }

View File

@ -1,4 +1,10 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
html,
LitElement,
PropertyDeclarations,
css,
CSSResult,
} from "lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
@ -10,10 +16,9 @@ import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
// tslint:disable-next-line // tslint:disable-next-line
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { buttonLink } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { WebhookDialogParams } from "./types"; import { WebhookDialogParams } from "./types";
import { haStyle } from "../../../resources/ha-style";
const inputLabel = "Public URL Click to copy to clipboard"; const inputLabel = "Public URL Click to copy to clipboard";
@ -44,7 +49,6 @@ export class CloudWebhookManageDialog extends LitElement {
? "https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger" ? "https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger"
: `https://www.home-assistant.io/components/${webhook.domain}/`; : `https://www.home-assistant.io/components/${webhook.domain}/`;
return html` return html`
${this._renderStyle()}
<paper-dialog with-backdrop> <paper-dialog with-backdrop>
<h2>Webhook for ${webhook.name}</h2> <h2>Webhook for ${webhook.name}</h2>
<div> <div>
@ -112,24 +116,25 @@ export class CloudWebhookManageDialog extends LitElement {
this._paperInput.label = inputLabel; this._paperInput.label = inputLabel;
} }
private _renderStyle() { static get styles(): CSSResult[] {
return html` return [
<style> haStyle,
css`
paper-dialog { paper-dialog {
width: 650px; width: 650px;
} }
paper-input { paper-input {
margin-top: -8px; margin-top: -8px;
} }
${buttonLink} button.link { button.link {
color: var(--primary-color); color: var(--primary-color);
} }
paper-button { paper-button {
color: var(--primary-color); color: var(--primary-color);
font-weight: 500; font-weight: 500;
} }
</style> `,
`; ];
} }
} }

View File

@ -3,7 +3,7 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; } from "lit-element";
import "@polymer/paper-toggle-button/paper-toggle-button"; import "@polymer/paper-toggle-button/paper-toggle-button";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";

View File

@ -0,0 +1,52 @@
import { h, Component } from "preact";
import "@polymer/paper-input/paper-input";
import { onChangeEvent } from "../../../../common/preact/event";
export default class TimePatternTrigger extends Component {
constructor() {
super();
this.onChange = onChangeEvent.bind(this, "trigger");
}
/* eslint-disable camelcase */
render({ trigger, localize }) {
const { hours, minutes, seconds } = trigger;
return (
<div>
<paper-input
label={localize(
"ui.panel.config.automation.editor.triggers.type.time_pattern.hours"
)}
name="hours"
value={hours}
onvalue-changed={this.onChange}
/>
<paper-input
label={localize(
"ui.panel.config.automation.editor.triggers.type.time_pattern.minutes"
)}
name="minutes"
value={minutes}
onvalue-changed={this.onChange}
/>
<paper-input
label={localize(
"ui.panel.config.automation.editor.triggers.type.time_pattern.seconds"
)}
name="seconds"
value={seconds}
onvalue-changed={this.onChange}
/>
</div>
);
}
}
TimePatternTrigger.defaultConfig = {
hours: "",
minutes: "",
seconds: "",
};

View File

@ -8,6 +8,7 @@ import EventTrigger from "./event";
import HassTrigger from "./homeassistant"; import HassTrigger from "./homeassistant";
import MQTTTrigger from "./mqtt"; import MQTTTrigger from "./mqtt";
import NumericStateTrigger from "./numeric_state"; import NumericStateTrigger from "./numeric_state";
import TimePatternTrigger from "./time_pattern";
import StateTrigger from "./state"; import StateTrigger from "./state";
import SunTrigger from "./sun"; import SunTrigger from "./sun";
import TemplateTrigger from "./template"; import TemplateTrigger from "./template";
@ -24,6 +25,7 @@ const TYPES = {
sun: SunTrigger, sun: SunTrigger,
template: TemplateTrigger, template: TemplateTrigger,
time: TimeTrigger, time: TimeTrigger,
time_pattern: TimePatternTrigger,
webhook: WebhookTrigger, webhook: WebhookTrigger,
zone: ZoneTrigger, zone: ZoneTrigger,
}; };

View File

@ -1,51 +1,119 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { HomeAssistant } from "../../../types"; import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button";
import { HassEntity } from "home-assistant-js-websocket";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { Cluster } from "../../../data/zha";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
import "../../../resources/ha-style"; import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import {
ZHAClusterSelectedParams,
ZHAEntitySelectedParams,
ZHANodeSelectedParams,
} from "./types";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-network"; import "./zha-network";
import "./zha-node";
export class HaConfigZha extends LitElement { export class HaConfigZha extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public isWide?: boolean; public isWide?: boolean;
private _haStyle?: DocumentFragment; private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment; private _ironFlex?: DocumentFragment;
private _selectedNode?: HassEntity;
private _selectedCluster?: Cluster;
private _selectedEntity?: HassEntity;
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
hass: {}, hass: {},
isWide: {}, isWide: {},
_selectedCluster: {},
_selectedEntity: {},
_selectedNode: {},
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
<ha-app-layout has-scrolling-region=""> <ha-app-layout>
<app-header slot="header" fixed=""> <app-header slot="header">
<app-toolbar> <app-toolbar>
<paper-icon-button <paper-icon-button
icon="hass:arrow-left" icon="hass:arrow-left"
@click="${this._onBackTapped}" @click="${this._onBackTapped}"
></paper-icon-button> ></paper-icon-button>
<div main-title>Zigbee Home Automation</div>
</app-toolbar> </app-toolbar>
</app-header> </app-header>
<zha-network <zha-network
id="zha-network" .isWide="${this.isWide}"
.is-wide="${this.isWide}"
.hass="${this.hass}" .hass="${this.hass}"
></zha-network> ></zha-network>
<zha-node
.isWide="${this.isWide}"
.hass="${this.hass}"
@zha-cluster-selected="${this._onClusterSelected}"
@zha-node-selected="${this._onNodeSelected}"
@zha-entity-selected="${this._onEntitySelected}"
></zha-node>
${
this._selectedCluster
? html`
<zha-cluster-attributes
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
.selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}"
></zha-cluster-attributes>
<zha-cluster-commands
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
.selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}"
></zha-cluster-commands>
`
: ""
}
</ha-app-layout> </ha-app-layout>
`; `;
} }
private _onClusterSelected(
selectedClusterEvent: HASSDomEvent<ZHAClusterSelectedParams>
): void {
this._selectedCluster = selectedClusterEvent.detail.cluster;
}
private _onNodeSelected(
selectedNodeEvent: HASSDomEvent<ZHANodeSelectedParams>
): void {
this._selectedNode = selectedNodeEvent.detail.node;
this._selectedCluster = undefined;
this._selectedEntity = undefined;
}
private _onEntitySelected(
selectedEntityEvent: HASSDomEvent<ZHAEntitySelectedParams>
): void {
this._selectedEntity = selectedEntityEvent.detail.entity;
}
private renderStyle(): TemplateResult { private renderStyle(): TemplateResult {
if (!this._haStyle) { if (!this._haStyle) {
this._haStyle = document.importNode( this._haStyle = document.importNode(

View File

@ -0,0 +1,50 @@
import { HassEntity } from "home-assistant-js-websocket";
import { ZHADeviceEntity, Cluster } from "../../../data/zha";
export interface PickerTarget extends EventTarget {
selected: number;
}
export interface ItemSelectedEvent {
target?: PickerTarget;
}
export interface ChangeEvent {
detail?: {
value?: any;
};
target?: EventTarget;
}
export interface SetAttributeServiceData {
entity_id: string;
cluster_id: number;
cluster_type: string;
attribute: number;
value: any;
manufacturer: number;
}
export interface IssueCommandServiceData {
entity_id: string;
cluster_id: number;
cluster_type: string;
command: number;
command_type: string;
}
export interface ZHAEntitySelectedParams {
entity: HassEntity;
}
export interface ZHANodeSelectedParams {
node: ZHADeviceEntity;
}
export interface ZHAClusterSelectedParams {
cluster: Cluster;
}
export interface NodeServiceData {
ieee_address: string;
}

View File

@ -0,0 +1,329 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Attribute,
Cluster,
fetchAttributesForCluster,
ReadAttributeServiceData,
readAttributeValue,
ZHADeviceEntity,
} from "../../../data/zha";
import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import {
ChangeEvent,
ItemSelectedEvent,
SetAttributeServiceData,
} from "./types";
export class ZHAClusterAttributes extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showHelp: boolean;
public selectedNode?: HassEntity;
public selectedEntity?: ZHADeviceEntity;
public selectedCluster?: Cluster;
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
private _attributes: Attribute[];
private _selectedAttributeIndex: number;
private _attributeValue?: any;
private _manufacturerCodeOverride?: string | number;
private _setAttributeServiceData?: SetAttributeServiceData;
constructor() {
super();
this.showHelp = false;
this._selectedAttributeIndex = -1;
this._attributes = [];
this._attributeValue = "";
}
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
showHelp: {},
selectedNode: {},
selectedEntity: {},
selectedCluster: {},
_attributes: {},
_selectedAttributeIndex: {},
_attributeValue: {},
_manufacturerCodeOverride: {},
_setAttributeServiceData: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedCluster")) {
this._attributes = [];
this._selectedAttributeIndex = -1;
this._attributeValue = "";
this._fetchAttributesForCluster();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
${this.renderStyle()}
<ha-config-section .isWide="${this.isWide}">
<div style="position: relative" slot="header">
<span>Cluster Attributes</span>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
>
</paper-icon-button>
</div>
<span slot="introduction">View and edit cluster attributes.</span>
<paper-card class="content">
<div class="attribute-picker">
<paper-dropdown-menu
label="Attributes of the selected cluster"
class="flex"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedAttributeIndex}"
@iron-select="${this._selectedAttributeChanged}"
>
${
this._attributes.map(
(entry) => html`
<paper-item
>${entry.name + " (id: " + entry.id + ")"}</paper-item
>
`
)
}
</paper-listbox>
</paper-dropdown-menu>
</div>
${
this.showHelp
? html`
<div style="color: grey; padding: 16px">
Select an attribute to view or set its value
</div>
`
: ""
}
${
this._selectedAttributeIndex !== -1
? this._renderAttributeInteractions()
: ""
}
</paper-card>
</ha-config-section>
`;
}
private _renderAttributeInteractions(): TemplateResult {
return html`
<div class="input-text">
<paper-input
label="Value"
type="string"
.value="${this._attributeValue}"
@value-changed="${this._onAttributeValueChanged}"
placeholder="Value"
></paper-input>
</div>
<div class="input-text">
<paper-input
label="Manufacturer code override"
type="number"
.value="${this._manufacturerCodeOverride}"
@value-changed="${this._onManufacturerCodeOverrideChanged}"
placeholder="Value"
></paper-input>
</div>
<div class="card-actions">
<paper-button @click="${this._onGetZigbeeAttributeClick}"
>Get Zigbee Attribute</paper-button
>
<ha-call-service-button
.hass="${this.hass}"
domain="zha"
service="set_zigbee_cluster_attribute"
.serviceData="${this._setAttributeServiceData}"
>Set Zigbee Attribute</ha-call-service-button
>
${
this.showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="set_zigbee_cluster_attribute"
></ha-service-description>
`
: ""
}
</div>
`;
}
private async _fetchAttributesForCluster(): Promise<void> {
if (this.selectedEntity && this.selectedCluster && this.hass) {
this._attributes = await fetchAttributesForCluster(
this.hass,
this.selectedEntity!.entity_id,
this.selectedEntity!.device_info!.identifiers[0][1],
this.selectedCluster!.id,
this.selectedCluster!.type
);
}
}
private _computeReadAttributeServiceData():
| ReadAttributeServiceData
| undefined {
if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) {
return;
}
return {
entity_id: this.selectedEntity!.entity_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.attributes.manufacturer_code,
};
}
private _computeSetAttributeServiceData():
| SetAttributeServiceData
| undefined {
if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) {
return;
}
return {
entity_id: this.selectedEntity!.entity_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id,
value: this._attributeValue,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.attributes.manufacturer_code,
};
}
private _onAttributeValueChanged(value: ChangeEvent): void {
this._attributeValue = value.detail!.value;
this._setAttributeServiceData = this._computeSetAttributeServiceData();
}
private _onManufacturerCodeOverrideChanged(value: ChangeEvent): void {
this._manufacturerCodeOverride = value.detail!.value;
this._setAttributeServiceData = this._computeSetAttributeServiceData();
}
private async _onGetZigbeeAttributeClick(): Promise<void> {
const data = this._computeReadAttributeServiceData();
if (data && this.hass) {
this._attributeValue = await readAttributeValue(this.hass, data);
}
}
private _onHelpTap(): void {
this.showHelp = !this.showHelp;
}
private _selectedAttributeChanged(event: ItemSelectedEvent): void {
this._selectedAttributeIndex = event.target!.selected;
this._attributeValue = "";
}
private renderStyle(): TemplateResult {
if (!this._haStyle) {
this._haStyle = document.importNode(
(document.getElementById("ha-style")!
.children[0] as HTMLTemplateElement).content,
true
);
}
if (!this._ironFlex) {
this._ironFlex = document.importNode(
(document.getElementById("iron-flex")!
.children[0] as HTMLTemplateElement).content,
true
);
}
return html`
${this._ironFlex} ${this._haStyle}
<style>
.content {
margin-top: 24px;
}
paper-card {
display: block;
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--google-red-500);
}
.attribute-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.input-text {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
</style>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-cluster-attributes": ZHAClusterAttributes;
}
}
customElements.define("zha-cluster-attributes", ZHAClusterAttributes);

View File

@ -0,0 +1,282 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Cluster,
Command,
fetchCommandsForCluster,
ZHADeviceEntity,
} from "../../../data/zha";
import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import {
ChangeEvent,
IssueCommandServiceData,
ItemSelectedEvent,
} from "./types";
export class ZHAClusterCommands extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public selectedNode?: HassEntity;
public selectedEntity?: ZHADeviceEntity;
public selectedCluster?: Cluster;
private _showHelp: boolean;
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
private _commands: Command[];
private _selectedCommandIndex: number;
private _manufacturerCodeOverride?: number;
private _issueClusterCommandServiceData?: IssueCommandServiceData;
constructor() {
super();
this._showHelp = false;
this._selectedCommandIndex = -1;
this._commands = [];
}
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
selectedNode: {},
selectedEntity: {},
selectedCluster: {},
_showHelp: {},
_commands: {},
_selectedCommandIndex: {},
_manufacturerCodeOverride: {},
_issueClusterCommandServiceData: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedCluster")) {
this._commands = [];
this._selectedCommandIndex = -1;
this._fetchCommandsForCluster();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
${this.renderStyle()}
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<span>Cluster Commands</span>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
>
</paper-icon-button>
</div>
<span slot="introduction">View and issue cluster commands.</span>
<paper-card class="content">
<div class="command-picker">
<paper-dropdown-menu
label="Commands of the selected cluster"
class="flex"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedCommandIndex}"
@iron-select="${this._selectedCommandChanged}"
>
${
this._commands.map(
(entry) => html`
<paper-item
>${entry.name + " (id: " + entry.id + ")"}</paper-item
>
`
)
}
</paper-listbox>
</paper-dropdown-menu>
</div>
${
this._showHelp
? html`
<div class="helpText">Select a command to interact with</div>
`
: ""
}
${
this._selectedCommandIndex !== -1
? html`
<div class="input-text">
<paper-input
label="Manufacturer code override"
type="number"
.value="${this._manufacturerCodeOverride}"
@value-changed="${
this._onManufacturerCodeOverrideChanged
}"
placeholder="Value"
></paper-input>
</div>
<div class="card-actions">
<ha-call-service-button
.hass="${this.hass}"
domain="zha"
service="issue_zigbee_cluster_command"
.serviceData="${this._issueClusterCommandServiceData}"
>Issue Zigbee Command</ha-call-service-button
>
${
this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="issue_zigbee_cluster_command"
></ha-service-description>
`
: ""
}
</div>
`
: ""
}
</paper-card>
</ha-config-section>
`;
}
private async _fetchCommandsForCluster(): Promise<void> {
if (this.selectedEntity && this.selectedCluster && this.hass) {
this._commands = await fetchCommandsForCluster(
this.hass,
this.selectedEntity!.entity_id,
this.selectedEntity!.device_info!.identifiers[0][1],
this.selectedCluster!.id,
this.selectedCluster!.type
);
}
}
private _computeIssueClusterCommandServiceData():
| IssueCommandServiceData
| undefined {
if (!this.selectedEntity || !this.selectedCluster) {
return;
}
return {
entity_id: this.selectedEntity!.entity_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
command: this._commands[this._selectedCommandIndex].id,
command_type: this._commands[this._selectedCommandIndex].type,
};
}
private _onManufacturerCodeOverrideChanged(value: ChangeEvent): void {
this._manufacturerCodeOverride = value.detail!.value;
this._issueClusterCommandServiceData = this._computeIssueClusterCommandServiceData();
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _selectedCommandChanged(event: ItemSelectedEvent): void {
this._selectedCommandIndex = event.target!.selected;
this._issueClusterCommandServiceData = this._computeIssueClusterCommandServiceData();
}
private renderStyle(): TemplateResult {
if (!this._haStyle) {
this._haStyle = document.importNode(
(document.getElementById("ha-style")!
.children[0] as HTMLTemplateElement).content,
true
);
}
if (!this._ironFlex) {
this._ironFlex = document.importNode(
(document.getElementById("iron-flex")!
.children[0] as HTMLTemplateElement).content,
true
);
}
return html`
${this._ironFlex} ${this._haStyle}
<style>
.content {
margin-top: 24px;
}
paper-card {
display: block;
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--google-red-500);
}
.command-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.input-text {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.sectionHeader {
position: relative;
}
.helpText {
color: grey;
padding: 16px;
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
</style>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-cluster-commands": ZHAClusterCommands;
}
}
customElements.define("zha-cluster-commands", ZHAClusterCommands);

View File

@ -0,0 +1,165 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
} 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,
ZHADeviceEntity,
} from "../../../data/zha";
import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ItemSelectedEvent } from "./types";
declare global {
// for fire event
interface HASSDomEvents {
"zha-cluster-selected": {
cluster?: Cluster;
};
}
}
const computeClusterKey = (cluster: Cluster): string => {
return `${cluster.name} (id: ${cluster.id}, type: ${cluster.type})`;
};
export class ZHAClusters extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showHelp: boolean;
public selectedEntity?: ZHADeviceEntity;
private _selectedClusterIndex: number;
private _clusters: Cluster[];
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
constructor() {
super();
this.showHelp = false;
this._selectedClusterIndex = -1;
this._clusters = [];
}
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
showHelp: {},
selectedEntity: {},
_selectedClusterIndex: {},
_clusters: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedEntity")) {
this._clusters = [];
this._selectedClusterIndex = -1;
fireEvent(this, "zha-cluster-selected", {
cluster: undefined,
});
this._fetchClustersForZhaNode();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
${this._renderStyle()}
<div class="node-picker">
<paper-dropdown-menu label="Clusters" class="flex">
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedClusterIndex}"
@iron-select="${this._selectedClusterChanged}"
>
${
this._clusters.map(
(entry) => html`
<paper-item>${computeClusterKey(entry)}</paper-item>
`
)
}
</paper-listbox>
</paper-dropdown-menu>
</div>
${
this.showHelp
? html`
<div class="helpText">
Select cluster to view attributes and commands
</div>
`
: ""
}
`;
}
private async _fetchClustersForZhaNode(): Promise<void> {
if (this.hass) {
this._clusters = await fetchClustersForZhaNode(
this.hass,
this.selectedEntity!.entity_id,
this.selectedEntity!.device_info!.identifiers[0][1]
);
}
}
private _selectedClusterChanged(event: ItemSelectedEvent): void {
this._selectedClusterIndex = event.target!.selected;
fireEvent(this, "zha-cluster-selected", {
cluster: this._clusters[this._selectedClusterIndex],
});
}
private _renderStyle(): TemplateResult {
if (!this._haStyle) {
this._haStyle = document.importNode(
(document.getElementById("ha-style")!
.children[0] as HTMLTemplateElement).content,
true
);
}
if (!this._ironFlex) {
this._ironFlex = document.importNode(
(document.getElementById("iron-flex")!
.children[0] as HTMLTemplateElement).content,
true
);
}
return html`
${this._ironFlex} ${this._haStyle}
<style>
.node-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.helpText {
color: grey;
padding: 16px;
}
</style>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-cluster": ZHAClusters;
}
}
customElements.define("zha-clusters", ZHAClusters);

View File

@ -0,0 +1,177 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { HassEntity } from "home-assistant-js-websocket";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchEntitiesForZhaNode } from "../../../data/zha";
import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import { ItemSelectedEvent } from "./types";
declare global {
// for fire event
interface HASSDomEvents {
"zha-entity-selected": {
entity?: HassEntity;
};
}
}
export class ZHAEntities extends LitElement {
public hass?: HomeAssistant;
public showHelp?: boolean;
public selectedNode?: HassEntity;
private _selectedEntityIndex: number;
private _entities: HassEntity[];
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
constructor() {
super();
this._entities = [];
this._selectedEntityIndex = -1;
}
static get properties(): PropertyDeclarations {
return {
hass: {},
showHelp: {},
selectedNode: {},
_selectedEntityIndex: {},
_entities: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedNode")) {
this._entities = [];
this._selectedEntityIndex = -1;
fireEvent(this, "zha-entity-selected", {
entity: undefined,
});
this._fetchEntitiesForZhaNode();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
${this._renderStyle()}
<div class="node-picker">
<paper-dropdown-menu label="Entities" class="flex">
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedEntityIndex}"
@iron-select="${this._selectedEntityChanged}"
>
${
this._entities.map(
(entry) => html`
<paper-item>${entry.entity_id}</paper-item>
`
)
}
</paper-listbox>
</paper-dropdown-menu>
</div>
${
this.showHelp
? html`
<div class="helpText">
Select entity to view per-entity options
</div>
`
: ""
}
${
this._selectedEntityIndex !== -1
? html`
<div class="actions">
<paper-button @click="${this._showEntityInformation}"
>Entity Information</paper-button
>
</div>
`
: ""
}
`;
}
private async _fetchEntitiesForZhaNode(): Promise<void> {
if (this.hass) {
const fetchedEntities = await fetchEntitiesForZhaNode(this.hass);
this._entities = fetchedEntities[this.selectedNode!.attributes.ieee];
}
}
private _selectedEntityChanged(event: ItemSelectedEvent): void {
this._selectedEntityIndex = event.target!.selected;
fireEvent(this, "zha-entity-selected", {
entity: this._entities[this._selectedEntityIndex],
});
}
private _showEntityInformation(): void {
fireEvent(this, "hass-more-info", {
entityId: this._entities[this._selectedEntityIndex].entity_id,
});
}
private _renderStyle(): TemplateResult {
if (!this._haStyle) {
this._haStyle = document.importNode(
(document.getElementById("ha-style")!
.children[0] as HTMLTemplateElement).content,
true
);
}
if (!this._ironFlex) {
this._ironFlex = document.importNode(
(document.getElementById("iron-flex")!
.children[0] as HTMLTemplateElement).content,
true
);
}
return html`
${this._ironFlex} ${this._haStyle}
<style>
.node-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.actions {
border-top: 1px solid #e8e8e8;
padding: 5px 16px;
position: relative;
}
.actions paper-button:not([disabled]) {
color: var(--primary-color);
font-weight: 500;
}
.helpText {
color: grey;
padding: 16px;
}
</style>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-entities": ZHAEntities;
}
}
customElements.define("zha-entities", ZHAEntities);

View File

@ -1,42 +1,45 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-card/paper-card";
import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description"; import "../../../components/ha-service-description";
import "../ha-config-section";
import { HomeAssistant } from "../../../types";
import "../../../resources/ha-style"; import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
export class ZHANetwork extends LitElement { export class ZHANetwork extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public isWide?: boolean; public isWide?: boolean;
public showDescription: boolean; private _showHelp: boolean;
private _haStyle?: DocumentFragment; private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment; private _ironFlex?: DocumentFragment;
constructor() { constructor() {
super(); super();
this.showDescription = false; this._showHelp = false;
} }
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
hass: {}, hass: {},
isWide: {}, isWide: {},
showDescription: {}, _showHelp: {},
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
<ha-config-section .is-wide="${this.isWide}"> <ha-config-section .isWide="${this.isWide}">
<div style="position: relative" slot="header"> <div style="position: relative" slot="header">
<span>Zigbee Home Automation network management</span> <span>Network Management</span>
<paper-icon-button class="toggle-help-icon" @click="${ <paper-icon-button class="toggle-help-icon" @click="${
this._onHelpTap this._onHelpTap
}" icon="hass:help-circle"></paper-icon-button> }" icon="hass:help-circle"></paper-icon-button>
@ -49,7 +52,7 @@ export class ZHANetwork extends LitElement {
this.hass this.hass
}" domain="zha" service="permit">Permit</ha-call-service-button> }" domain="zha" service="permit">Permit</ha-call-service-button>
${ ${
this.showDescription this._showHelp
? html` ? html`
<ha-service-description <ha-service-description
.hass="${this.hass}" .hass="${this.hass}"
@ -65,7 +68,7 @@ export class ZHANetwork extends LitElement {
} }
private _onHelpTap(): void { private _onHelpTap(): void {
this.showDescription = !this.showDescription; this._showHelp = !this._showHelp;
} }
private renderStyle(): TemplateResult { private renderStyle(): TemplateResult {

View File

@ -0,0 +1,325 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { HassEntity } from "home-assistant-js-websocket";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
import computeStateName from "../../../common/entity/compute_state_name";
import sortByName from "../../../common/entity/states_sort_by_name";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import {
ItemSelectedEvent,
NodeServiceData,
ZHAEntitySelectedParams,
} from "./types";
import "./zha-clusters";
import "./zha-entities";
import { reconfigureNode } from "../../../data/zha";
declare global {
// for fire event
interface HASSDomEvents {
"zha-node-selected": {
node?: HassEntity;
};
}
}
export class ZHANode extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
private _showHelp: boolean;
private _selectedNodeIndex: number;
private _selectedNode?: HassEntity;
private _selectedEntity?: HassEntity;
private _serviceData?: {};
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
private _nodes: HassEntity[];
constructor() {
super();
this._showHelp = false;
this._selectedNodeIndex = -1;
this._nodes = [];
}
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
_showHelp: {},
_selectedNodeIndex: {},
_selectedNode: {},
_serviceData: {},
_selectedEntity: {},
};
}
protected render(): TemplateResult | void {
this._nodes = this._computeNodes(this.hass);
return html`
${this.renderStyle()}
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<span>Node Management</span>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
></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)
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
press at ~5 second intervals that keep devices awake while you
interact with them.
</span>
<paper-card class="content">
<div class="node-picker">
<paper-dropdown-menu label="Nodes" class="flex">
<paper-listbox
slot="dropdown-content"
@iron-select="${this._selectedNodeChanged}"
>
${
this._nodes.map(
(entry) => html`
<paper-item
>${this._computeSelectCaption(entry)}</paper-item
>
`
)
}
</paper-listbox>
</paper-dropdown-menu>
</div>
${
this._showHelp
? html`
<div class="helpText">
Select node to view per-node options
</div>
`
: ""
}
${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""}
${this._selectedNodeIndex !== -1 ? this._renderEntities() : ""}
${this._selectedEntity ? this._renderClusters() : ""}
</paper-card>
</ha-config-section>
`;
}
private _renderNodeActions(): TemplateResult {
return html`
<div class="card-actions">
<paper-button @click="${this._showNodeInformation}"
>Node Information</paper-button
>
<paper-button @click="${this._onReconfigureNodeClick}"
>Reconfigure Node</paper-button
>
${
this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
domain="zha"
service="reconfigure_device"
/>
`
: ""
}
<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"
/>
`
: ""
}
</div>
`;
}
private _renderEntities(): TemplateResult {
return html`
<zha-entities
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
.showHelp="${this._showHelp}"
@zha-entity-selected="${this._onEntitySelected}"
></zha-entities>
`;
}
private _renderClusters(): TemplateResult {
return html`
<zha-clusters
.hass="${this.hass}"
.selectedEntity="${this._selectedEntity}"
.showHelp="${this._showHelp}"
></zha-clusters>
`;
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _selectedNodeChanged(event: ItemSelectedEvent): void {
this._selectedNodeIndex = event!.target!.selected;
this._selectedNode = this._nodes[this._selectedNodeIndex];
this._selectedEntity = undefined;
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!.attributes.ieee);
}
}
private _showNodeInformation(): void {
fireEvent(this, "hass-more-info", {
entityId: this._selectedNode!.entity_id,
});
}
private _computeNodeServiceData(): NodeServiceData {
return {
ieee_address: this._selectedNode!.attributes.ieee,
};
}
private _computeSelectCaption(stateObj: HassEntity): string {
return (
computeStateName(stateObj) + " (Node:" + stateObj.attributes.ieee + ")"
);
}
private _computeNodes(hass?: HomeAssistant): HassEntity[] {
if (hass) {
return Object.keys(hass.states)
.map((key) => hass.states[key])
.filter((ent) => ent.entity_id.match("zha[.]"))
.sort(sortByName);
} else {
return [];
}
}
private _onEntitySelected(
entitySelectedEvent: HASSDomEvent<ZHAEntitySelectedParams>
): void {
this._selectedEntity = entitySelectedEvent.detail.entity;
}
private renderStyle(): TemplateResult {
if (!this._haStyle) {
this._haStyle = document.importNode(
(document.getElementById("ha-style")!
.children[0] as HTMLTemplateElement).content,
true
);
}
if (!this._ironFlex) {
this._ironFlex = document.importNode(
(document.getElementById("iron-flex")!
.children[0] as HTMLTemplateElement).content,
true
);
}
return html`
${this._ironFlex} ${this._haStyle}
<style>
.content {
margin-top: 24px;
}
.node-info {
margin-left: 16px;
}
.sectionHeader {
position: relative;
}
.help-text {
padding-left: 28px;
padding-right: 28px;
}
.helpText {
color: grey;
padding: 16px;
}
paper-card {
display: block;
margin: 0 auto;
max-width: 600px;
}
.node-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
.toggle-help-icon {
position: absolute;
top: 6px;
right: 0;
color: var(--primary-color);
}
</style>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-node": ZHANode;
}
}
customElements.define("zha-node", ZHANode);

View File

@ -20,7 +20,7 @@ import formatTime from "../../common/datetime/format_time";
import EventsMixin from "../../mixins/events-mixin"; import EventsMixin from "../../mixins/events-mixin";
import LocalizeMixin from "../../mixins/localize-mixin"; import LocalizeMixin from "../../mixins/localize-mixin";
const OPT_IN_PANEL = "lovelace"; const OPT_IN_PANEL = "states";
let registeredDialog = false; let registeredDialog = false;
class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) { class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
@ -167,7 +167,7 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
</template> </template>
</p> </p>
<p> <p>
<a href='/lovelace'>Try out the new Lovelace UI</a> <a href="[[_nonDefaultLink()]]">[[_nonDefaultLinkText()]]</a>
<div id="love" style="cursor:pointer;" on-click="_toggleDefaultPage">[[_defaultPageText()]]</div <div id="love" style="cursor:pointer;" on-click="_toggleDefaultPage">[[_defaultPageText()]]</div
</p> </p>
</div> </div>
@ -366,6 +366,26 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
}); });
} }
_nonDefaultLink() {
if (
localStorage.defaultPage === OPT_IN_PANEL &&
OPT_IN_PANEL === "states"
) {
return "/lovelace";
}
return "/states";
}
_nonDefaultLinkText() {
if (
localStorage.defaultPage === OPT_IN_PANEL &&
OPT_IN_PANEL === "states"
) {
return "Go to the Lovelace UI";
}
return "Go to the states UI";
}
_defaultPageText() { _defaultPageText() {
return `>> ${ return `>> ${
localStorage.defaultPage === OPT_IN_PANEL ? "Remove" : "Set" localStorage.defaultPage === OPT_IN_PANEL ? "Remove" : "Set"

View File

@ -3,14 +3,17 @@ import {
LitElement, LitElement,
PropertyValues, PropertyValues,
PropertyDeclarations, PropertyDeclarations,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { classMap } from "lit-html/directives/classMap"; import { classMap } from "lit-html/directives/class-map";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import { callAlarmAction } from "../../../data/alarm_control_panel"; import {
callAlarmAction,
FORMAT_NUMBER,
} from "../../../data/alarm_control_panel";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import "../../../components/ha-card"; import "../../../components/ha-card";
@ -97,7 +100,7 @@ class HuiAlarmPanelCard extends hassLocalizeLitMixin(LitElement)
return true; return true;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }
@ -150,7 +153,7 @@ class HuiAlarmPanelCard extends hassLocalizeLitMixin(LitElement)
` `
} }
${ ${
stateObj.attributes.code_format !== "Number" stateObj.attributes.code_format !== FORMAT_NUMBER
? html`` ? html``
: html` : html`
<div id="keypad"> <div id="keypad">
@ -206,7 +209,7 @@ class HuiAlarmPanelCard extends hassLocalizeLitMixin(LitElement)
private _handleActionClick(e: MouseEvent): void { private _handleActionClick(e: MouseEvent): void {
callAlarmAction( callAlarmAction(
this.hass!, this.hass!,
this._config!.entity_id, this._config!.entity,
(e.currentTarget! as any).action, (e.currentTarget! as any).action,
this._code! this._code!
); );

View File

@ -3,8 +3,8 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../components/hui-entities-toggle"; import "../components/hui-entities-toggle";
@ -21,7 +21,7 @@ import { createRowElement } from "../common/create-row-element";
import computeDomain from "../../../common/entity/compute_domain"; import computeDomain from "../../../common/entity/compute_domain";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
export interface ConfigEntity extends EntityConfig { export interface EntitiesCardEntityConfig extends EntityConfig {
type?: string; type?: string;
secondary_info?: "entity-id" | "last-changed"; secondary_info?: "entity-id" | "last-changed";
action_name?: string; action_name?: string;
@ -30,10 +30,10 @@ export interface ConfigEntity extends EntityConfig {
url?: string; url?: string;
} }
export interface Config extends LovelaceCardConfig { export interface EntitiesCardConfig extends LovelaceCardConfig {
show_header_toggle?: boolean; show_header_toggle?: boolean;
title?: string; title?: string;
entities: ConfigEntity[]; entities: EntitiesCardEntityConfig[];
theme?: string; theme?: string;
} }
@ -49,8 +49,8 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
} }
protected _hass?: HomeAssistant; protected _hass?: HomeAssistant;
protected _config?: Config; protected _config?: EntitiesCardConfig;
protected _configEntities?: ConfigEntity[]; protected _configEntities?: EntitiesCardEntityConfig[];
set hass(hass: HomeAssistant) { set hass(hass: HomeAssistant) {
this._hass = hass; this._hass = hass;
@ -81,7 +81,7 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
return (this._config.title ? 1 : 0) + this._config.entities.length; return (this._config.title ? 1 : 0) + this._config.entities.length;
} }
public setConfig(config: Config): void { public setConfig(config: EntitiesCardConfig): void {
const entities = processConfigEntities(config.entities); const entities = processConfigEntities(config.entities);
this._config = { theme: "default", ...config }; this._config = { theme: "default", ...config };
@ -95,7 +95,7 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
} }
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this._hass) { if (!this._config || !this._hass) {
return html``; return html``;
} }
@ -171,7 +171,7 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
`; `;
} }
private renderEntity(entityConf: ConfigEntity): TemplateResult { private renderEntity(entityConf: EntitiesCardEntityConfig): TemplateResult {
const element = createRowElement(entityConf); const element = createRowElement(entityConf);
if (this._hass) { if (this._hass) {
element.hass = this._hass; element.hass = this._hass;
@ -189,7 +189,7 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
`; `;
} }
private _handleClick(entityConf: ConfigEntity): void { private _handleClick(entityConf: EntitiesCardEntityConfig): void {
const entityId = entityConf.entity; const entityId = entityConf.entity;
fireEvent(this, "hass-more-info", { entityId }); fireEvent(this, "hass-more-info", { entityId });
} }

View File

@ -3,10 +3,10 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { TemplateResult } from "lit-html"; import { styleMap } from "lit-html/directives/style-map";
import { styleMap } from "lit-html/directives/styleMap";
import "../../../components/ha-card"; import "../../../components/ha-card";
@ -82,7 +82,7 @@ class HuiEntityButtonCard extends hassLocalizeLitMixin(LitElement)
return true; return true;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -1,8 +1,7 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
interface Config extends LovelaceCardConfig { interface Config extends LovelaceCardConfig {
@ -40,7 +39,7 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }

View File

@ -3,9 +3,9 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { styleMap } from "lit-html/directives/styleMap"; import { styleMap } from "lit-html/directives/style-map";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
@ -56,6 +56,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
public hass?: HomeAssistant; public hass?: HomeAssistant;
private _config?: Config; private _config?: Config;
private _updated?: boolean;
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
@ -78,7 +79,12 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
this._config = { min: 0, max: 100, theme: "default", ...config }; this._config = { min: 0, max: 100, theme: "default", ...config };
} }
protected render(): TemplateResult { public connectedCallback(): void {
super.connectedCallback();
this._setBaseUnit();
}
protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }
@ -148,12 +154,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
(this.shadowRoot!.querySelector( this._updated = true;
"ha-card" this._setBaseUnit();
)! as HTMLElement).style.setProperty(
"--base-unit",
this._computeBaseUnit()
);
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -169,6 +171,19 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
} }
private _setBaseUnit(): void {
if (!this.isConnected || !this._updated) {
return;
}
const baseUnit = this._computeBaseUnit();
if (baseUnit === "0px") {
return;
}
(this.shadowRoot!.querySelector(
"ha-card"
)! as HTMLElement).style.setProperty("--base-unit", baseUnit);
}
private _computeSeverity(numberValue: number): string { private _computeSeverity(numberValue: number): string {
const sections = this._config!.severity; const sections = this._config!.severity;

View File

@ -3,9 +3,9 @@ import {
LitElement, LitElement,
PropertyValues, PropertyValues,
PropertyDeclarations, PropertyDeclarations,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { classMap } from "lit-html/directives/classMap"; import { classMap } from "lit-html/directives/class-map";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -114,7 +114,7 @@ export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement)
return true; return true;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,4 @@
import { html } from "@polymer/lit-element"; import { html, TemplateResult } from "lit-element";
import { TemplateResult } from "lit-html";
import { computeCardSize } from "../common/compute-card-size"; import { computeCardSize } from "../common/compute-card-size";
import { HuiStackCard } from "./hui-stack-card"; import { HuiStackCard } from "./hui-stack-card";

View File

@ -1,11 +1,15 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import { TemplateResult } from "lit-html"; import { styleMap } from "lit-html/directives/style-map";
import { styleMap } from "lit-html/directives/styleMap";
export interface Config extends LovelaceCardConfig { export interface Config extends LovelaceCardConfig {
aspect_ratio?: string; aspect_ratio?: string;
@ -31,7 +35,13 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
} }
public getCardSize(): number { public getCardSize(): number {
return 1 + this.offsetHeight / 50; if (!this._config) {
return 3;
}
const aspectRatio = this._config.aspect_ratio
? Number(this._config.aspect_ratio.replace("%", ""))
: 50;
return 1 + aspectRatio / 25;
} }
public setConfig(config: Config): void { public setConfig(config: Config): void {
@ -42,7 +52,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }
@ -75,7 +85,6 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
#root { #root {
width: 100%; width: 100%;
position: relative; position: relative;
padding-top: ${this._config!.aspect_ratio || "50%"};
} }
iframe { iframe {
position: absolute; position: absolute;

View File

@ -3,11 +3,11 @@ import {
LitElement, LitElement,
PropertyValues, PropertyValues,
PropertyDeclarations, PropertyDeclarations,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { styleMap } from "lit-html/directives/styleMap"; import { styleMap } from "lit-html/directives/style-map";
import { HomeAssistant, LightEntity } from "../../../types"; import { HomeAssistant, LightEntity } from "../../../types";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
@ -82,7 +82,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
this._config = { theme: "default", ...config }; this._config = { theme: "default", ...config };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return html``; return html``;
} }

View File

@ -247,7 +247,10 @@ class HuiMapCard extends PolymerElement {
const stateObj = this.hass.states[entityId]; const stateObj = this.hass.states[entityId];
if ( if (
computeStateDomain(stateObj) === "geo_location" && computeStateDomain(stateObj) === "geo_location" &&
this._configGeoLocationSources.includes(stateObj.attributes.source) (this._configGeoLocationSources.includes(
stateObj.attributes.source
) ||
this._configGeoLocationSources.includes("all"))
) { ) {
allEntities.push(entityId); allEntities.push(entityId);
} }

View File

@ -1,12 +1,16 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { classMap } from "lit-html/directives/classMap"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-markdown"; import "../../../components/ha-markdown";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import { TemplateResult } from "lit-html";
export interface Config extends LovelaceCardConfig { export interface Config extends LovelaceCardConfig {
content: string; content: string;
@ -42,7 +46,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }

View File

@ -1,12 +1,16 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace"; import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html"; import { classMap } from "lit-html/directives/class-map";
import { classMap } from "lit-html/directives/classMap";
import { handleClick } from "../common/handle-click"; import { handleClick } from "../common/handle-click";
import { longPress } from "../common/directives/long-press-directive"; import { longPress } from "../common/directives/long-press-directive";
@ -49,7 +53,7 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { TemplateResult } from "lit-html";
import { createHuiElement } from "../common/create-hui-element"; import { createHuiElement } from "../common/create-hui-element";
@ -55,7 +54,7 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }

View File

@ -1,6 +1,10 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html/lib/shady-render"; html,
import { classMap } from "lit-html/directives/classMap"; LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../components/hui-image"; import "../components/hui-image";
@ -65,7 +69,7 @@ class HuiPictureEntityCard extends hassLocalizeLitMixin(LitElement)
this._config = { show_name: true, show_state: true, ...config }; this._config = { show_name: true, show_state: true, ...config };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -1,6 +1,10 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { classMap } from "lit-html/directives/classMap"; html,
import { TemplateResult } from "lit-html"; LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { DOMAINS_TOGGLE } from "../../../common/const"; import { DOMAINS_TOGGLE } from "../../../common/const";
@ -83,7 +87,7 @@ class HuiPictureGlanceCard extends hassLocalizeLitMixin(LitElement)
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -4,8 +4,8 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import "@polymer/paper-spinner/paper-spinner"; import "@polymer/paper-spinner/paper-spinner";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
@ -192,7 +192,7 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
return 3; return 3;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -1,6 +1,5 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { repeat } from "lit-html/directives/repeat"; import { repeat } from "lit-html/directives/repeat";
import { TemplateResult } from "lit-html";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-checkbox/paper-checkbox";
@ -79,7 +78,7 @@ class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement)
} }
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { TemplateResult } from "lit-html";
import { createCardElement } from "../common/create-card-element"; import { createCardElement } from "../common/create-card-element";
@ -12,10 +11,6 @@ interface Config extends LovelaceCardConfig {
} }
export abstract class HuiStackCard extends LitElement implements LovelaceCard { export abstract class HuiStackCard extends LitElement implements LovelaceCard {
protected _cards?: LovelaceCard[];
private _config?: Config;
private _hass?: HomeAssistant;
static get properties() { static get properties() {
return { return {
_config: {}, _config: {},
@ -33,6 +28,9 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
element.hass = this._hass; element.hass = this._hass;
} }
} }
protected _cards?: LovelaceCard[];
private _config?: Config;
private _hass?: HomeAssistant;
public abstract getCardSize(): number; public abstract getCardSize(): number;
@ -42,15 +40,12 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
} }
this._config = config; this._config = config;
this._cards = config.cards.map((card) => { this._cards = config.cards.map((card) => {
const element = createCardElement(card) as LovelaceCard; const element = this._createCardElement(card) as LovelaceCard;
if (this._hass) {
element.hass = this._hass;
}
return element; return element;
}); });
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }
@ -62,4 +57,31 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
} }
protected abstract renderStyle(): TemplateResult; protected abstract renderStyle(): TemplateResult;
private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = createCardElement(cardConfig) as LovelaceCard;
if (this._hass) {
element.hass = this._hass;
}
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element;
}
private _rebuildCard(
cardElToReplace: LovelaceCard,
config: LovelaceCardConfig
): void {
const newCardEl = this._createCardElement(config);
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
this._cards = this._cards!.map((curCardEl) =>
curCardEl === cardElToReplace ? newCardEl : curCardEl
);
}
} }

View File

@ -3,9 +3,9 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { classMap } from "lit-html/directives/classMap"; } from "lit-element";
import { TemplateResult } from "lit-html"; import { classMap } from "lit-html/directives/class-map";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
@ -51,18 +51,6 @@ export interface Config extends LovelaceCardConfig {
name?: string; name?: string;
} }
function formatTemp(temps: string[]): string {
return temps.filter(Boolean).join("-");
}
function computeTemperatureStepSize(hass: HomeAssistant, config: Config) {
const stateObj = hass.states[config.entity];
if (stateObj.attributes.target_temp_step) {
return stateObj.attributes.target_temp_step;
}
return hass.config.unit_system.temperature === UNIT_F ? 1 : 0.5;
}
export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement) export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
implements LovelaceCard { implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> { public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -79,6 +67,8 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
private _roundSliderStyle?: TemplateResult; private _roundSliderStyle?: TemplateResult;
private _jQuery?: any; private _jQuery?: any;
private _broadCard?: boolean; private _broadCard?: boolean;
private _loaded?: boolean;
private _updated?: boolean;
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
@ -101,7 +91,14 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
this._config = { theme: "default", ...config }; this._config = { theme: "default", ...config };
} }
protected render(): TemplateResult { public connectedCallback(): void {
super.connectedCallback();
if (this._updated && !this._loaded) {
this._initialLoad();
}
}
protected render(): TemplateResult | void {
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return html``; return html``;
} }
@ -157,7 +154,10 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._initialLoad(); this._updated = true;
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -189,13 +189,23 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
} }
} }
private get _stepSize(): number {
const stateObj = this.hass!.states[this._config!.entity];
if (stateObj.attributes.target_temp_step) {
return stateObj.attributes.target_temp_step;
}
return this.hass!.config.unit_system.temperature === UNIT_F ? 1 : 0.5;
}
private async _initialLoad(): Promise<void> { private async _initialLoad(): Promise<void> {
const radius = this.clientWidth / 3; this._loaded = true;
const radius = this.clientWidth / 3.2;
this._broadCard = this.clientWidth > 390; this._broadCard = this.clientWidth > 390;
(this.shadowRoot!.querySelector( (this.shadowRoot!.querySelector(
"#thermostat" "#thermostat"
)! as HTMLElement).style.minHeight = radius * 2 + "px"; )! as HTMLElement).style.height = radius * 2 + "px";
const loaded = await loadRoundslider(); const loaded = await loadRoundslider();
await new Promise((resolve) => afterNextRender(resolve)); await new Promise((resolve) => afterNextRender(resolve));
@ -212,7 +222,6 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
: "min-range"; : "min-range";
const [sliderValue, uiValue] = this._genSliderValue(stateObj); const [sliderValue, uiValue] = this._genSliderValue(stateObj);
const step = computeTemperatureStepSize(this.hass!, this._config!);
this._jQuery("#thermostat", this.shadowRoot).roundSlider({ this._jQuery("#thermostat", this.shadowRoot).roundSlider({
...thermostatConfig, ...thermostatConfig,
@ -220,11 +229,10 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
min: stateObj.attributes.min_temp, min: stateObj.attributes.min_temp,
max: stateObj.attributes.max_temp, max: stateObj.attributes.max_temp,
sliderType: _sliderType, sliderType: _sliderType,
create: () => this._loaded(),
change: (value) => this._setTemperature(value), change: (value) => this._setTemperature(value),
drag: (value) => this._dragEvent(value), drag: (value) => this._dragEvent(value),
value: sliderValue, value: sliderValue,
step, step: this._stepSize,
}); });
this._updateSetTemp(uiValue); this._updateSetTemp(uiValue);
} }
@ -240,10 +248,13 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
sliderValue = `${stateObj.attributes.target_temp_low}, ${ sliderValue = `${stateObj.attributes.target_temp_low}, ${
stateObj.attributes.target_temp_high stateObj.attributes.target_temp_high
}`; }`;
uiValue = formatTemp([ uiValue = this.formatTemp(
String(stateObj.attributes.target_temp_low), [
String(stateObj.attributes.target_temp_high), String(stateObj.attributes.target_temp_low),
]); String(stateObj.attributes.target_temp_high),
],
false
);
} else { } else {
sliderValue = stateObj.attributes.temperature; sliderValue = stateObj.attributes.temperature;
uiValue = "" + stateObj.attributes.temperature; uiValue = "" + stateObj.attributes.temperature;
@ -252,18 +263,12 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
return [sliderValue, uiValue]; return [sliderValue, uiValue];
} }
private _loaded(): void {
(this.shadowRoot!.querySelector(
"#thermostat"
)! as HTMLElement).style.minHeight = null;
}
private _updateSetTemp(value: string): void { private _updateSetTemp(value: string): void {
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = value; this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = value;
} }
private _dragEvent(e): void { private _dragEvent(e): void {
this._updateSetTemp(formatTemp(String(e.value).split(","))); this._updateSetTemp(this.formatTemp(String(e.value).split(","), true));
} }
private _setTemperature(e): void { private _setTemperature(e): void {
@ -314,6 +319,21 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
}); });
} }
private formatTemp(temps: string[], spaceStepSize: boolean): string {
temps = temps.filter(Boolean);
// If we are sliding the slider, append 0 to the temperatures if we're
// having a 0.5 step size, so that the text doesn't jump while sliding
if (spaceStepSize) {
const stepSize = this._stepSize;
temps = temps.map((val) =>
val.includes(".") || stepSize === 1 ? val : `${val}.0`
);
}
return temps.join("-");
}
private renderStyle(): TemplateResult { private renderStyle(): TemplateResult {
return html` return html`
${this._roundSliderStyle} ${this._roundSliderStyle}
@ -370,39 +390,37 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
--mode-color: var(--unknown-color); --mode-color: var(--unknown-color);
} }
.no-title { .no-title {
--title-margin-top: 33% !important; --title-position-top: 33% !important;
} }
.large { .large {
--thermostat-padding-top: 25px; --thermostat-padding-top: 25px;
--thermostat-margin-bottom: 25px; --thermostat-margin-bottom: 25px;
--title-font-size: 28px; --title-font-size: 28px;
--title-margin-top: 20%; --title-position-top: 27%;
--climate-info-margin-top: 17%; --climate-info-position-top: 81%;
--modes-margin-top: 2%;
--set-temperature-font-size: 25px; --set-temperature-font-size: 25px;
--current-temperature-font-size: 71px; --current-temperature-font-size: 71px;
--current-temperature-margin-top: 10%; --current-temperature-position-top: 10%;
--current-temperature-text-padding-left: 15px; --current-temperature-text-padding-left: 15px;
--uom-font-size: 20px; --uom-font-size: 20px;
--uom-margin-left: -18px; --uom-margin-left: -18px;
--current-mode-font-size: 18px; --current-mode-font-size: 18px;
--set-temperature-padding-bottom: 5px; --set-temperature-margin-bottom: -5px;
} }
.small { .small {
--thermostat-padding-top: 15px; --thermostat-padding-top: 15px;
--thermostat-margin-bottom: 15px; --thermostat-margin-bottom: 15px;
--title-font-size: 18px; --title-font-size: 18px;
--title-margin-top: 20%; --title-position-top: 28%;
--climate-info-margin-top: 7.5%; --climate-info-position-top: 79%;
--modes-margin-top: 1%;
--set-temperature-font-size: 16px; --set-temperature-font-size: 16px;
--current-temperature-font-size: 25px; --current-temperature-font-size: 25px;
--current-temperature-margin-top: 5%; --current-temperature-position-top: 5%;
--current-temperature-text-padding-left: 7px; --current-temperature-text-padding-left: 7px;
--uom-font-size: 12px; --uom-font-size: 12px;
--uom-margin-left: -5px; --uom-margin-left: -5px;
--current-mode-font-size: 14px; --current-mode-font-size: 14px;
--set-temperature-padding-bottom: 0px; --set-temperature-margin-bottom: 0px;
} }
#thermostat { #thermostat {
margin: 0 auto var(--thermostat-margin-bottom); margin: 0 auto var(--thermostat-margin-bottom);
@ -449,21 +467,28 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
} }
#set-temperature { #set-temperature {
font-size: var(--set-temperature-font-size); font-size: var(--set-temperature-font-size);
padding-bottom: var(--set-temperature-padding-bottom); margin-bottom: var(--set-temperature-margin-bottom);
} }
.title { .title {
font-size: var(--title-font-size); font-size: var(--title-font-size);
margin-top: var(--title-margin-top); position: absolute;
top: var(--title-position-top);
left: 50%;
transform: translate(-50%, -50%);
} }
.climate-info { .climate-info {
margin-top: var(--climate-info-margin-top); position: absolute;
top: var(--climate-info-position-top);
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
} }
.current-mode { .current-mode {
font-size: var(--current-mode-font-size); font-size: var(--current-mode-font-size);
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.modes { .modes {
margin-top: var(--modes-margin-top); margin-top: 16px;
} }
.modes ha-icon { .modes ha-icon {
color: var(--disabled-text-color); color: var(--disabled-text-color);
@ -475,7 +500,10 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
color: var(--mode-color); color: var(--mode-color);
} }
.current-temperature { .current-temperature {
margin-top: var(--current-temperature-margin-top); position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: var(--current-temperature-font-size); font-size: var(--current-temperature-font-size);
} }
.current-temperature-text { .current-temperature-text {

View File

@ -1,8 +1,7 @@
import { html } from "@polymer/lit-element"; import { html, TemplateResult } from "lit-element";
import { computeCardSize } from "../common/compute-card-size"; import { computeCardSize } from "../common/compute-card-size";
import { HuiStackCard } from "./hui-stack-card"; import { HuiStackCard } from "./hui-stack-card";
import { TemplateResult } from "lit-html";
class HuiVerticalStackCard extends HuiStackCard { class HuiVerticalStackCard extends HuiStackCard {
public getCardSize() { public getCardSize() {

View File

@ -106,7 +106,7 @@ export const createCardElement = (
customElements.whenDefined(tag).then(() => { customElements.whenDefined(tag).then(() => {
clearTimeout(timer); clearTimeout(timer);
fireEvent(element, "rebuild-view"); fireEvent(element, "ll-rebuild");
}); });
return element; return element;

View File

@ -72,7 +72,7 @@ export const createHuiElement = (
customElements.whenDefined(tag).then(() => { customElements.whenDefined(tag).then(() => {
clearTimeout(timer); clearTimeout(timer);
fireEvent(element, "rebuild-view"); fireEvent(element, "ll-rebuild");
}); });
return element; return element;

View File

@ -52,6 +52,9 @@ const DOMAIN_TO_ELEMENT_TYPE = {
timer: "timer", timer: "timer",
switch: "toggle", switch: "toggle",
vacuum: "toggle", vacuum: "toggle",
// Temporary. Once climate is rewritten,
// water heater should get it's own row.
water_heater: "climate",
}; };
const TIMEOUT = 2000; const TIMEOUT = 2000;
@ -115,7 +118,7 @@ export const createRowElement = (
customElements.whenDefined(tag).then(() => { customElements.whenDefined(tag).then(() => {
clearTimeout(timer); clearTimeout(timer);
fireEvent(element, "rebuild-view"); fireEvent(element, "ll-rebuild");
}); });
return element; return element;

View File

@ -1,5 +1,8 @@
import { directive, PropertyPart } from "lit-html"; import { directive, PropertyPart } from "lit-html";
import "@material/mwc-ripple"; // See https://github.com/home-assistant/home-assistant-polymer/pull/2457
// on how to undo mwc -> paper migration
// import "@material/mwc-ripple";
import "@polymer/paper-ripple";
const isTouch = const isTouch =
"ontouchstart" in window || "ontouchstart" in window ||
@ -25,7 +28,7 @@ class LongPress extends HTMLElement implements LongPress {
constructor() { constructor() {
super(); super();
this.holdTime = 500; this.holdTime = 500;
this.ripple = document.createElement("mwc-ripple"); this.ripple = document.createElement("paper-ripple");
this.timer = undefined; this.timer = undefined;
this.held = false; this.held = false;
this.cooldownStart = false; this.cooldownStart = false;
@ -34,6 +37,7 @@ class LongPress extends HTMLElement implements LongPress {
public connectedCallback() { public connectedCallback() {
Object.assign(this.style, { Object.assign(this.style, {
borderRadius: "50%", // paper-ripple
position: "absolute", position: "absolute",
width: isTouch ? "100px" : "50px", width: isTouch ? "100px" : "50px",
height: isTouch ? "100px" : "50px", height: isTouch ? "100px" : "50px",
@ -42,7 +46,9 @@ class LongPress extends HTMLElement implements LongPress {
}); });
this.appendChild(this.ripple); this.appendChild(this.ripple);
this.ripple.primary = true; this.ripple.style.color = "#03a9f4"; // paper-ripple
this.ripple.style.color = "var(--primary-color)"; // paper-ripple
// this.ripple.primary = true;
[ [
"touchcancel", "touchcancel",
@ -140,14 +146,17 @@ class LongPress extends HTMLElement implements LongPress {
top: `${y}px`, top: `${y}px`,
display: null, display: null,
}); });
this.ripple.disabled = false; this.ripple.holdDown = true; // paper-ripple
this.ripple.active = true; this.ripple.simulatedRipple(); // paper-ripple
this.ripple.unbounded = true; // this.ripple.disabled = false;
// this.ripple.active = true;
// this.ripple.unbounded = true;
} }
private stopAnimation() { private stopAnimation() {
this.ripple.active = false; this.ripple.holdDown = false; // paper-ripple
this.ripple.disabled = true; // this.ripple.active = false;
// this.ripple.disabled = true;
this.style.display = "none"; this.style.display = "none";
} }
} }
@ -174,7 +183,6 @@ export const longPressBind = (element: LongPressElement) => {
longpress.bind(element); longpress.bind(element);
}; };
export const longPress = () => export const longPress = directive(() => (part: PropertyPart) => {
directive((part: PropertyPart) => { longPressBind(part.committer.element);
longPressBind(part.committer.element); });
});

View File

@ -14,6 +14,7 @@ import computeStateDomain from "../../../common/entity/compute_state_domain";
import { LocalizeFunc } from "../../../mixins/localize-base-mixin"; import { LocalizeFunc } from "../../../mixins/localize-base-mixin";
import computeDomain from "../../../common/entity/compute_domain"; import computeDomain from "../../../common/entity/compute_domain";
import { EntityRowConfig, WeblinkConfig } from "../entity-rows/types"; import { EntityRowConfig, WeblinkConfig } from "../entity-rows/types";
import { EntitiesCardConfig } from "../cards/hui-entities-card";
const DEFAULT_VIEW_ENTITY_ID = "group.default_view"; const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
const DOMAINS_BADGES = [ const DOMAINS_BADGES = [
@ -24,11 +25,15 @@ const DOMAINS_BADGES = [
"sun", "sun",
"timer", "timer",
]; ];
const HIDE_DOMAIN = new Set(["persistent_notification", "configurator"]); const HIDE_DOMAIN = new Set([
"persistent_notification",
"configurator",
"geo_location",
]);
const computeCards = ( const computeCards = (
title: string, states: Array<[string, HassEntity]>,
states: Array<[string, HassEntity]> entityCardOptions: Partial<EntitiesCardConfig>
): LovelaceCardConfig[] => { ): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = []; const cards: LovelaceCardConfig[] = [];
@ -68,7 +73,7 @@ const computeCards = (
type: "weather-forecast", type: "weather-forecast",
entity: entityId, entity: entityId,
}); });
} else if (domain === "weblink") { } else if (domain === "weblink" && stateObj) {
const conf: WeblinkConfig = { const conf: WeblinkConfig = {
type: "weblink", type: "weblink",
url: stateObj.state, url: stateObj.state,
@ -85,9 +90,9 @@ const computeCards = (
if (entities.length > 0) { if (entities.length > 0) {
cards.unshift({ cards.unshift({
title,
type: "entities", type: "entities",
entities, entities,
...entityCardOptions,
}); });
} }
@ -152,10 +157,13 @@ const generateViewConfig = (
splitted.groups.forEach((groupEntity) => { splitted.groups.forEach((groupEntity) => {
cards = cards.concat( cards = cards.concat(
computeCards( computeCards(
computeStateName(groupEntity),
groupEntity.attributes.entity_id.map( groupEntity.attributes.entity_id.map(
(entityId): [string, HassEntity] => [entityId, entities[entityId]] (entityId): [string, HassEntity] => [entityId, entities[entityId]]
) ),
{
title: computeStateName(groupEntity),
show_header_toggle: groupEntity.attributes.control !== "hidden",
}
) )
); );
}); });
@ -165,10 +173,12 @@ const generateViewConfig = (
.forEach((domain) => { .forEach((domain) => {
cards = cards.concat( cards = cards.concat(
computeCards( computeCards(
localize(`domain.${domain}`),
ungroupedEntitites[domain].map( ungroupedEntitites[domain].map(
(entityId): [string, HassEntity] => [entityId, entities[entityId]] (entityId): [string, HassEntity] => [entityId, entities[entityId]]
) ),
{
title: localize(`domain.${domain}`),
}
) )
); );
}); });
@ -237,6 +247,16 @@ export const generateLovelaceConfig = (
) )
); );
// Add map of geo locations to default view if loaded
if (hass.config.components.includes("geo_location")) {
if (views[0] && views[0].cards) {
views[0].cards.push({
type: "map",
geo_location_sources: ["all"],
});
}
}
// Make sure we don't have Home as title and first tab. // Make sure we don't have Home as title and first tab.
if (views.length > 1 && title === "Home") { if (views.length > 1 && title === "Home") {
title = "Home Assistant"; title = "Home Assistant";

View File

@ -1,5 +1,5 @@
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { PropertyValues } from "@polymer/lit-element"; import { PropertyValues } from "lit-element";
// Check if config or Entity changed // Check if config or Entity changed
export function hasConfigOrEntityChanged( export function hasConfigOrEntityChanged(

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-input/paper-textarea";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
@ -51,7 +55,7 @@ export class HuiActionEditor extends LitElement {
return config.service || ""; return config.service || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass || !this.actions) { if (!this.hass || !this.actions) {
return html``; return html``;
} }

View File

@ -1,4 +1,4 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { html, LitElement, PropertyDeclarations } from "lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import "@polymer/paper-menu-button/paper-menu-button"; import "@polymer/paper-menu-button/paper-menu-button";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
@ -46,10 +46,6 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) {
paper-button { paper-button {
color: var(--primary-color); color: var(--primary-color);
font-weight: 500; font-weight: 500;
letter-spacing: 0.05em;
font-size: 16px;
padding: 0;
margin: 0;
} }
paper-icon-button { paper-icon-button {
color: var(--primary-text-color); color: var(--primary-text-color);

View File

@ -3,8 +3,8 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button"; import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
import { DOMAINS_TOGGLE } from "../../../common/const"; import { DOMAINS_TOGGLE } from "../../../common/const";
@ -35,7 +35,7 @@ class HuiEntitiesToggle extends LitElement {
} }
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._toggleEntities) { if (!this._toggleEntities) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
@ -19,7 +23,7 @@ export class HuiEntityEditor extends LitElement {
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.entities) { if (!this.entities) {
return html``; return html``;
} }

View File

@ -1,6 +1,10 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
@ -28,7 +32,7 @@ export class HuiThemeSelectionEditor extends hassLocalizeLitMixin(LitElement) {
}; };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
const themes = ["Backend-selected", "default"].concat( const themes = ["Backend-selected", "default"].concat(
Object.keys(this.hass!.themes.themes).sort() Object.keys(this.hass!.themes.themes).sort()
); );

View File

@ -3,8 +3,8 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { TemplateResult } from "lit-html"; } from "lit-element";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import format_date from "../../../common/datetime/format_date"; import format_date from "../../../common/datetime/format_date";
@ -49,7 +49,7 @@ class HuiTimestampDisplay extends hassLocalizeLitMixin(LitElement) {
this._clearInterval(); this._clearInterval();
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.ts || !this.hass) { if (!this.ts || !this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -39,7 +38,7 @@ export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public cardPicked?: (cardConf: LovelaceCardConfig) => void; public cardPicked?: (cardConf: LovelaceCardConfig) => void;
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
<h3>${this.localize("ui.panel.lovelace.editor.edit_card.pick_card")}</h3> <h3>${this.localize("ui.panel.lovelace.editor.edit_card.pick_card")}</h3>

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { HASSDomEvent } from "../../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../../common/dom/fire_event";
@ -48,7 +52,7 @@ export class HuiDialogEditCard extends LitElement {
: undefined; : undefined;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._params) { if (!this._params) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
// tslint:disable-next-line:no-duplicate-imports // tslint:disable-next-line:no-duplicate-imports
@ -23,7 +27,7 @@ export class HuiDialogMoveCardView extends hassLocalizeLitMixin(LitElement) {
await this.updateComplete; await this.updateComplete;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._params) { if (!this._params) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
@ -17,7 +21,7 @@ export class HuiDialogPickCard extends hassLocalizeLitMixin(LitElement) {
return {}; return {};
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
<paper-dialog <paper-dialog
with-backdrop with-backdrop

View File

@ -3,9 +3,9 @@ import {
LitElement, LitElement,
PropertyDeclarations, PropertyDeclarations,
PropertyValues, PropertyValues,
} from "@polymer/lit-element"; TemplateResult,
import { classMap } from "lit-html/directives/classMap"; } from "lit-element";
import { TemplateResult } from "lit-html"; import { classMap } from "lit-html/directives/class-map";
import yaml from "js-yaml"; import yaml from "js-yaml";
import "@polymer/paper-spinner/paper-spinner"; import "@polymer/paper-spinner/paper-spinner";
@ -107,7 +107,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
this._loadConfigElement(this.cardConfig!); this._loadConfigElement(this.cardConfig!);
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
let content; let content;
let preview; let preview;
if (this._configElement !== undefined) { if (this._configElement !== undefined) {

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-input/paper-textarea";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -21,7 +25,7 @@ export class HuiYAMLEditor extends LitElement {
} }
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
<paper-textarea <paper-textarea

View File

@ -1,4 +1,4 @@
import { html } from "@polymer/lit-element"; import { html } from "lit-element";
export const configElementStyle = html` export const configElementStyle = html`
<style> <style>

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
@ -49,7 +53,7 @@ export class HuiAlarmPanelCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.states || []; return this._config!.states || [];
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
@ -12,7 +16,10 @@ import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { Config, ConfigEntity } from "../../cards/hui-entities-card"; import {
EntitiesCardConfig,
EntitiesCardEntityConfig,
} from "../../cards/hui-entities-card";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/state-badge"; import "../../../../components/entity/state-badge";
@ -53,16 +60,16 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
} }
public hass?: HomeAssistant; public hass?: HomeAssistant;
private _config?: Config; private _config?: EntitiesCardConfig;
private _configEntities?: ConfigEntity[]; private _configEntities?: EntitiesCardEntityConfig[];
public setConfig(config: Config): void { public setConfig(config: EntitiesCardConfig): void {
config = cardConfigStruct(config); config = cardConfigStruct(config);
this._config = config; this._config = config;
this._configEntities = processEditorEntities(config.entities); this._configEntities = processEditorEntities(config.entities);
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -68,7 +72,7 @@ export class HuiEntityButtonCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.theme || "default"; return this._config!.theme || "default";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-toggle-button/paper-toggle-button"; import "@polymer/paper-toggle-button/paper-toggle-button";
@ -70,7 +74,7 @@ export class HuiGaugeCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.severity || undefined; return this._config!.severity || undefined;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
@ -68,7 +72,7 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.columns || NaN; return this._config!.columns || NaN;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -44,7 +48,7 @@ export class HuiIframeCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.aspect_ratio || ""; return this._config!.aspect_ratio || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -47,7 +51,7 @@ export class HuiLightCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.entity || ""; return this._config!.entity || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -64,7 +68,7 @@ export class HuiMapCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.entities || []; return this._config!.entities || [];
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-input/paper-textarea";
@ -40,7 +44,7 @@ export class HuiMarkdownCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.content || ""; return this._config!.content || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types"; import { EntitiesEditorEvent, EditorTarget } from "../types";
@ -34,7 +38,7 @@ export class HuiMediaControlCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.entity || ""; return this._config!.entity || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -51,7 +55,7 @@ export class HuiPictureCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.hold_action || { action: "none" }; return this._config!.hold_action || { action: "none" };
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -42,7 +46,7 @@ export class HuiPlantStatusCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.name || ""; return this._config!.name || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
@ -75,7 +79,7 @@ export class HuiSensorCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.hours_to_show || "24"; return this._config!.hours_to_show || "24";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -33,7 +37,7 @@ export class HuiShoppingListEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.title || ""; return this._config!.title || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
@ -47,7 +51,7 @@ export class HuiThermostatCardEditor extends hassLocalizeLitMixin(LitElement)
return this._config!.theme || "default"; return this._config!.theme || "default";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { struct } from "../../common/structs/struct"; import { struct } from "../../common/structs/struct";
import { EntitiesEditorEvent, EditorTarget } from "../types"; import { EntitiesEditorEvent, EditorTarget } from "../types";
@ -41,7 +45,7 @@ export class HuiWeatherForecastCardEditor
return this._config!.name || ""; return this._config!.name || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner"; import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-dialog/paper-dialog";
@ -41,7 +45,7 @@ export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) {
return this.shadowRoot!.querySelector("paper-dialog")!; return this.shadowRoot!.querySelector("paper-dialog")!;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
<paper-dialog with-backdrop> <paper-dialog with-backdrop>
@ -108,6 +112,7 @@ export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) {
try { try {
const lovelace = this._params!.lovelace; const lovelace = this._params!.lovelace;
await lovelace.saveConfig(lovelace.config); await lovelace.saveConfig(lovelace.config);
lovelace.setEditMode(true);
this._saving = false; this._saving = false;
this._closeDialog(); this._closeDialog();
} catch (err) { } catch (err) {

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner"; import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-dialog/paper-dialog";
// This is not a duplicate import, one is for types, one is for element. // This is not a duplicate import, one is for types, one is for element.
@ -47,7 +51,7 @@ export class HuiDialogEditLovelace extends hassLocalizeLitMixin(LitElement) {
return this.shadowRoot!.querySelector("paper-dialog")!; return this.shadowRoot!.querySelector("paper-dialog")!;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${this.renderStyle()} ${this.renderStyle()}
<paper-dialog with-backdrop> <paper-dialog with-backdrop>

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { EditorTarget } from "../types"; import { EditorTarget } from "../types";
@ -33,7 +37,7 @@ export class HuiLovelaceEditor extends hassLocalizeLitMixin(LitElement) {
return this.config.title || ""; return this.config.title || "";
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
return html` return html`
${configElementStyle} ${configElementStyle}
<div class="card-config"> <div class="card-config">

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { HASSDomEvent } from "../../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../../common/dom/fire_event";
@ -34,7 +38,7 @@ export class HuiDialogEditView extends LitElement {
(this.shadowRoot!.children[0] as any).showDialog(); (this.shadowRoot!.children[0] as any).showDialog();
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._params) { if (!this._params) {
return html``; return html``;
} }

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner"; import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tab";
@ -83,7 +87,7 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
return this.shadowRoot!.querySelector("paper-dialog")!; return this.shadowRoot!.querySelector("paper-dialog")!;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
let content; let content;
switch (this._curTab) { switch (this._curTab) {
case "tab-settings": case "tab-settings":

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import {
import { TemplateResult } from "lit-html"; html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { EditorTarget } from "../types"; import { EditorTarget } from "../types";
@ -59,7 +63,7 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
} }

View File

@ -1,4 +1,4 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
@ -8,7 +8,6 @@ import { longPress } from "../common/directives/long-press-directive";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceElement, LovelaceElementConfig } from "./types"; import { LovelaceElement, LovelaceElementConfig } from "./types";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
interface Config extends LovelaceElementConfig { interface Config extends LovelaceElementConfig {
icon: string; icon: string;
@ -31,7 +30,7 @@ export class HuiIconElement extends hassLocalizeLitMixin(LitElement)
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }

View File

@ -1,4 +1,4 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import "../components/hui-image"; import "../components/hui-image";
@ -8,7 +8,6 @@ import { longPress } from "../common/directives/long-press-directive";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceElement, LovelaceElementConfig } from "./types"; import { LovelaceElement, LovelaceElementConfig } from "./types";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
interface Config extends LovelaceElementConfig { interface Config extends LovelaceElementConfig {
image?: string; image?: string;
@ -40,7 +39,7 @@ export class HuiImageElement extends hassLocalizeLitMixin(LitElement)
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import { TemplateResult } from "lit-html";
import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-call-service-button";
@ -37,7 +36,7 @@ export class HuiServiceButtonElement extends LitElement
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if (!this._config) { if (!this._config) {
return html``; return html``;
} }

View File

@ -1,11 +1,10 @@
import { html, LitElement } from "@polymer/lit-element"; import { html, LitElement, TemplateResult } from "lit-element";
import "../../../components/entity/ha-state-label-badge"; import "../../../components/entity/ha-state-label-badge";
import computeStateName from "../../../common/entity/compute_state_name"; import computeStateName from "../../../common/entity/compute_state_name";
import { LovelaceElement, LovelaceElementConfig } from "./types"; import { LovelaceElement, LovelaceElementConfig } from "./types";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
export class HuiStateBadgeElement extends LitElement export class HuiStateBadgeElement extends LitElement
implements LovelaceElement { implements LovelaceElement {
@ -24,7 +23,7 @@ export class HuiStateBadgeElement extends LitElement
this._config = config; this._config = config;
} }
protected render(): TemplateResult { protected render(): TemplateResult | void {
if ( if (
!this._config || !this._config ||
!this.hass || !this.hass ||

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