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

View File

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

View File

@ -10,9 +10,10 @@ function patch(version) {
function today() {
const now = new Date();
return `${now.getFullYear()}${now.getMonth() + 1}${String(
now.getDate()
).padStart(2, "0")}.0`;
return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(
2,
"0"
)}${String(now.getDate()).padStart(2, "0")}.0`;
}
const methods = {
@ -51,4 +52,4 @@ async function main(args) {
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(
name="home-assistant-frontend",
version="20190109.1",
version="20190116.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@ -1,5 +1,5 @@
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 { AuthProvider } from "../data/auth";
@ -50,7 +50,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
};
}
public render() {
protected render() {
if (!this._authProviders) {
return html`
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>

View File

@ -8,7 +8,7 @@
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
/** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "states";
export const DEFAULT_PANEL = "lovelace";
/** Domains that have a state 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 { fireEvent } from "../../common/dom/fire_event";

View File

@ -3,10 +3,10 @@ import {
html,
PropertyValues,
PropertyDeclarations,
} from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
TemplateResult,
} from "lit-element";
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 computeStateName from "../../common/entity/compute_state_name";
@ -41,7 +41,7 @@ export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
this.clearInterval();
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
const state = this.state;
if (!state) {

View File

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

View File

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

View File

@ -1,8 +1,28 @@
import "@polymer/paper-slider";
const PaperSliderClass = customElements.get("paper-slider");
let subTemplate;
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) {
if (!this.step) {
return parseFloat(value);

View File

@ -20,6 +20,10 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
:host([rendered]) {
opacity: 1;
}
ha-chart-base {
direction: ltr;
}
</style>
<ha-chart-base
data="[[chartData]]"
@ -185,6 +189,11 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
afterSetDimensions: (yaxe) => {
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";
export const FORMAT_TEXT = "text";
export const FORMAT_NUMBER = "number";
export const callAlarmAction = (
hass: HomeAssistant,
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;
if (event.data) {
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(
self.registration
.showNotification(data.title, data)
@ -96,7 +108,11 @@ function initPushNotifications() {
event.notification.close();
if (!event.notification.data || !event.notification.data.url) {
if (
event.action ||
!event.notification.data ||
!event.notification.data.url
) {
return;
}

View File

@ -1,5 +1,5 @@
import { PolymerElement } from "@polymer/polymer";
import { Constructor } from "@polymer/lit-element";
import { Constructor } from "lit-element";
import { HASSDomEvent, ValidHassDomEvent } from "../../common/dom/fire_event";
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 { PolymerElement } from "@polymer/polymer/polymer-element";
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 "../ha-init-page";
@ -95,7 +95,11 @@ class HomeAssistant extends ext(PolymerElement, [
}
computePanelUrl(routeData) {
return (routeData && routeData.panel) || DEFAULT_PANEL;
return (
(routeData && routeData.panel) ||
localStorage.defaultPage ||
DEFAULT_PANEL
);
}
panelUrlChanged(newPanelUrl) {

View File

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

View File

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

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
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) {
return html``;
}

View File

@ -3,8 +3,8 @@ import {
LitElement,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
TemplateResult,
} from "lit-element";
import { repeat } from "lit-html/directives/repeat";
import "@polymer/paper-tooltip/paper-tooltip";
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) {
return html``;
}

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
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) {
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-input/paper-input";
@ -10,10 +16,9 @@ import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
// tslint:disable-next-line
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { buttonLink } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import { WebhookDialogParams } from "./types";
import { haStyle } from "../../../resources/ha-style";
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/components/${webhook.domain}/`;
return html`
${this._renderStyle()}
<paper-dialog with-backdrop>
<h2>Webhook for ${webhook.name}</h2>
<div>
@ -112,24 +116,25 @@ export class CloudWebhookManageDialog extends LitElement {
this._paperInput.label = inputLabel;
}
private _renderStyle() {
return html`
<style>
static get styles(): CSSResult[] {
return [
haStyle,
css`
paper-dialog {
width: 650px;
}
paper-input {
margin-top: -8px;
}
${buttonLink} button.link {
button.link {
color: var(--primary-color);
}
paper-button {
color: var(--primary-color);
font-weight: 500;
}
</style>
`;
`,
];
}
}

View File

@ -3,7 +3,7 @@ import {
LitElement,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
} from "lit-element";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "@polymer/paper-item/paper-item";
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 MQTTTrigger from "./mqtt";
import NumericStateTrigger from "./numeric_state";
import TimePatternTrigger from "./time_pattern";
import StateTrigger from "./state";
import SunTrigger from "./sun";
import TemplateTrigger from "./template";
@ -24,6 +25,7 @@ const TYPES = {
sun: SunTrigger,
template: TemplateTrigger,
time: TimeTrigger,
time_pattern: TimePatternTrigger,
webhook: WebhookTrigger,
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-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
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 "../../../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-node";
export class HaConfigZha extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
private _selectedNode?: HassEntity;
private _selectedCluster?: Cluster;
private _selectedEntity?: HassEntity;
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
_selectedCluster: {},
_selectedEntity: {},
_selectedNode: {},
};
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
return html`
${this.renderStyle()}
<ha-app-layout has-scrolling-region="">
<app-header slot="header" fixed="">
<ha-app-layout>
<app-header slot="header">
<app-toolbar>
<paper-icon-button
icon="hass:arrow-left"
@click="${this._onBackTapped}"
></paper-icon-button>
<div main-title>Zigbee Home Automation</div>
</app-toolbar>
</app-header>
<zha-network
id="zha-network"
.is-wide="${this.isWide}"
.isWide="${this.isWide}"
.hass="${this.hass}"
></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>
`;
}
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 {
if (!this._haStyle) {
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 {
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/ha-service-description";
import "../ha-config-section";
import { HomeAssistant } from "../../../types";
import "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
export class ZHANetwork extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showDescription: boolean;
private _showHelp: boolean;
private _haStyle?: DocumentFragment;
private _ironFlex?: DocumentFragment;
constructor() {
super();
this.showDescription = false;
this._showHelp = false;
}
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
showDescription: {},
_showHelp: {},
};
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
return html`
${this.renderStyle()}
<ha-config-section .is-wide="${this.isWide}">
<ha-config-section .isWide="${this.isWide}">
<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="${
this._onHelpTap
}" icon="hass:help-circle"></paper-icon-button>
@ -49,7 +52,7 @@ export class ZHANetwork extends LitElement {
this.hass
}" domain="zha" service="permit">Permit</ha-call-service-button>
${
this.showDescription
this._showHelp
? html`
<ha-service-description
.hass="${this.hass}"
@ -65,7 +68,7 @@ export class ZHANetwork extends LitElement {
}
private _onHelpTap(): void {
this.showDescription = !this.showDescription;
this._showHelp = !this._showHelp;
}
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 LocalizeMixin from "../../mixins/localize-mixin";
const OPT_IN_PANEL = "lovelace";
const OPT_IN_PANEL = "states";
let registeredDialog = false;
class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
@ -167,7 +167,7 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
</template>
</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
</p>
</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() {
return `>> ${
localStorage.defaultPage === OPT_IN_PANEL ? "Remove" : "Set"

View File

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

View File

@ -3,8 +3,8 @@ import {
LitElement,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
TemplateResult,
} from "lit-element";
import "../../../components/ha-card";
import "../components/hui-entities-toggle";
@ -21,7 +21,7 @@ import { createRowElement } from "../common/create-row-element";
import computeDomain from "../../../common/entity/compute_domain";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
export interface ConfigEntity extends EntityConfig {
export interface EntitiesCardEntityConfig extends EntityConfig {
type?: string;
secondary_info?: "entity-id" | "last-changed";
action_name?: string;
@ -30,10 +30,10 @@ export interface ConfigEntity extends EntityConfig {
url?: string;
}
export interface Config extends LovelaceCardConfig {
export interface EntitiesCardConfig extends LovelaceCardConfig {
show_header_toggle?: boolean;
title?: string;
entities: ConfigEntity[];
entities: EntitiesCardEntityConfig[];
theme?: string;
}
@ -49,8 +49,8 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
}
protected _hass?: HomeAssistant;
protected _config?: Config;
protected _configEntities?: ConfigEntity[];
protected _config?: EntitiesCardConfig;
protected _configEntities?: EntitiesCardEntityConfig[];
set hass(hass: HomeAssistant) {
this._hass = hass;
@ -81,7 +81,7 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
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);
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) {
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);
if (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;
fireEvent(this, "hass-more-info", { entityId });
}

View File

@ -3,10 +3,10 @@ import {
LitElement,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
TemplateResult,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket";
import { TemplateResult } from "lit-html";
import { styleMap } from "lit-html/directives/styleMap";
import { styleMap } from "lit-html/directives/style-map";
import "../../../components/ha-card";
@ -82,7 +82,7 @@ class HuiEntityButtonCard extends hassLocalizeLitMixin(LitElement)
return true;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config || !this.hass) {
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 { LovelaceCardConfig } from "../../../data/lovelace";
import { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types";
interface Config extends LovelaceCardConfig {
@ -40,7 +39,7 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config) {
return html``;
}

View File

@ -3,9 +3,9 @@ import {
LitElement,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import { styleMap } from "lit-html/directives/styleMap";
TemplateResult,
} from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
import "../../../components/ha-card";
import { LovelaceCardConfig } from "../../../data/lovelace";
@ -56,6 +56,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
public hass?: HomeAssistant;
private _config?: Config;
private _updated?: boolean;
static get properties(): PropertyDeclarations {
return {
@ -78,7 +79,12 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
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) {
return html``;
}
@ -148,12 +154,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
}
protected firstUpdated(): void {
(this.shadowRoot!.querySelector(
"ha-card"
)! as HTMLElement).style.setProperty(
"--base-unit",
this._computeBaseUnit()
);
this._updated = true;
this._setBaseUnit();
}
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 {
const sections = this._config!.severity;

View File

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

View File

@ -1,5 +1,4 @@
import { html } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import { html, TemplateResult } from "lit-element";
import { computeCardSize } from "../common/compute-card-size";
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 { LovelaceCard, LovelaceCardEditor } from "../types";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { TemplateResult } from "lit-html";
import { styleMap } from "lit-html/directives/styleMap";
import { styleMap } from "lit-html/directives/style-map";
export interface Config extends LovelaceCardConfig {
aspect_ratio?: string;
@ -31,7 +35,13 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
}
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 {
@ -42,7 +52,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config) {
return html``;
}
@ -75,7 +85,6 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
#root {
width: 100%;
position: relative;
padding-top: ${this._config!.aspect_ratio || "50%"};
}
iframe {
position: absolute;

View File

@ -3,11 +3,11 @@ import {
LitElement,
PropertyValues,
PropertyDeclarations,
} from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
TemplateResult,
} from "lit-element";
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 { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceCard, LovelaceCardEditor } from "../types";
@ -82,7 +82,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
this._config = { theme: "default", ...config };
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this.hass || !this._config) {
return html``;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import { html, LitElement, TemplateResult } from "lit-element";
import { createCardElement } from "../common/create-card-element";
@ -12,10 +11,6 @@ interface Config extends LovelaceCardConfig {
}
export abstract class HuiStackCard extends LitElement implements LovelaceCard {
protected _cards?: LovelaceCard[];
private _config?: Config;
private _hass?: HomeAssistant;
static get properties() {
return {
_config: {},
@ -33,6 +28,9 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
element.hass = this._hass;
}
}
protected _cards?: LovelaceCard[];
private _config?: Config;
private _hass?: HomeAssistant;
public abstract getCardSize(): number;
@ -42,15 +40,12 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
}
this._config = config;
this._cards = config.cards.map((card) => {
const element = createCardElement(card) as LovelaceCard;
if (this._hass) {
element.hass = this._hass;
}
const element = this._createCardElement(card) as LovelaceCard;
return element;
});
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config) {
return html``;
}
@ -62,4 +57,31 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
}
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,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
import { classMap } from "lit-html/directives/classMap";
import { TemplateResult } from "lit-html";
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../components/ha-card";
import "../../../components/ha-icon";
@ -51,18 +51,6 @@ export interface Config extends LovelaceCardConfig {
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)
implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -79,6 +67,8 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
private _roundSliderStyle?: TemplateResult;
private _jQuery?: any;
private _broadCard?: boolean;
private _loaded?: boolean;
private _updated?: boolean;
static get properties(): PropertyDeclarations {
return {
@ -101,7 +91,14 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
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) {
return html``;
}
@ -157,7 +154,10 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
}
protected firstUpdated(): void {
this._initialLoad();
this._updated = true;
if (this.isConnected && !this._loaded) {
this._initialLoad();
}
}
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> {
const radius = this.clientWidth / 3;
this._loaded = true;
const radius = this.clientWidth / 3.2;
this._broadCard = this.clientWidth > 390;
(this.shadowRoot!.querySelector(
"#thermostat"
)! as HTMLElement).style.minHeight = radius * 2 + "px";
)! as HTMLElement).style.height = radius * 2 + "px";
const loaded = await loadRoundslider();
await new Promise((resolve) => afterNextRender(resolve));
@ -212,7 +222,6 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
: "min-range";
const [sliderValue, uiValue] = this._genSliderValue(stateObj);
const step = computeTemperatureStepSize(this.hass!, this._config!);
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
...thermostatConfig,
@ -220,11 +229,10 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
min: stateObj.attributes.min_temp,
max: stateObj.attributes.max_temp,
sliderType: _sliderType,
create: () => this._loaded(),
change: (value) => this._setTemperature(value),
drag: (value) => this._dragEvent(value),
value: sliderValue,
step,
step: this._stepSize,
});
this._updateSetTemp(uiValue);
}
@ -240,10 +248,13 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
sliderValue = `${stateObj.attributes.target_temp_low}, ${
stateObj.attributes.target_temp_high
}`;
uiValue = formatTemp([
String(stateObj.attributes.target_temp_low),
String(stateObj.attributes.target_temp_high),
]);
uiValue = this.formatTemp(
[
String(stateObj.attributes.target_temp_low),
String(stateObj.attributes.target_temp_high),
],
false
);
} else {
sliderValue = stateObj.attributes.temperature;
uiValue = "" + stateObj.attributes.temperature;
@ -252,18 +263,12 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
return [sliderValue, uiValue];
}
private _loaded(): void {
(this.shadowRoot!.querySelector(
"#thermostat"
)! as HTMLElement).style.minHeight = null;
}
private _updateSetTemp(value: string): void {
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = value;
}
private _dragEvent(e): void {
this._updateSetTemp(formatTemp(String(e.value).split(",")));
this._updateSetTemp(this.formatTemp(String(e.value).split(","), true));
}
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 {
return html`
${this._roundSliderStyle}
@ -370,39 +390,37 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
--mode-color: var(--unknown-color);
}
.no-title {
--title-margin-top: 33% !important;
--title-position-top: 33% !important;
}
.large {
--thermostat-padding-top: 25px;
--thermostat-margin-bottom: 25px;
--title-font-size: 28px;
--title-margin-top: 20%;
--climate-info-margin-top: 17%;
--modes-margin-top: 2%;
--title-position-top: 27%;
--climate-info-position-top: 81%;
--set-temperature-font-size: 25px;
--current-temperature-font-size: 71px;
--current-temperature-margin-top: 10%;
--current-temperature-position-top: 10%;
--current-temperature-text-padding-left: 15px;
--uom-font-size: 20px;
--uom-margin-left: -18px;
--current-mode-font-size: 18px;
--set-temperature-padding-bottom: 5px;
--set-temperature-margin-bottom: -5px;
}
.small {
--thermostat-padding-top: 15px;
--thermostat-margin-bottom: 15px;
--title-font-size: 18px;
--title-margin-top: 20%;
--climate-info-margin-top: 7.5%;
--modes-margin-top: 1%;
--title-position-top: 28%;
--climate-info-position-top: 79%;
--set-temperature-font-size: 16px;
--current-temperature-font-size: 25px;
--current-temperature-margin-top: 5%;
--current-temperature-position-top: 5%;
--current-temperature-text-padding-left: 7px;
--uom-font-size: 12px;
--uom-margin-left: -5px;
--current-mode-font-size: 14px;
--set-temperature-padding-bottom: 0px;
--set-temperature-margin-bottom: 0px;
}
#thermostat {
margin: 0 auto var(--thermostat-margin-bottom);
@ -449,21 +467,28 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
}
#set-temperature {
font-size: var(--set-temperature-font-size);
padding-bottom: var(--set-temperature-padding-bottom);
margin-bottom: var(--set-temperature-margin-bottom);
}
.title {
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 {
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 {
font-size: var(--current-mode-font-size);
color: var(--secondary-text-color);
}
.modes {
margin-top: var(--modes-margin-top);
margin-top: 16px;
}
.modes ha-icon {
color: var(--disabled-text-color);
@ -475,7 +500,10 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
color: var(--mode-color);
}
.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);
}
.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 { HuiStackCard } from "./hui-stack-card";
import { TemplateResult } from "lit-html";
class HuiVerticalStackCard extends HuiStackCard {
public getCardSize() {

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
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 =
"ontouchstart" in window ||
@ -25,7 +28,7 @@ class LongPress extends HTMLElement implements LongPress {
constructor() {
super();
this.holdTime = 500;
this.ripple = document.createElement("mwc-ripple");
this.ripple = document.createElement("paper-ripple");
this.timer = undefined;
this.held = false;
this.cooldownStart = false;
@ -34,6 +37,7 @@ class LongPress extends HTMLElement implements LongPress {
public connectedCallback() {
Object.assign(this.style, {
borderRadius: "50%", // paper-ripple
position: "absolute",
width: isTouch ? "100px" : "50px",
height: isTouch ? "100px" : "50px",
@ -42,7 +46,9 @@ class LongPress extends HTMLElement implements LongPress {
});
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",
@ -140,14 +146,17 @@ class LongPress extends HTMLElement implements LongPress {
top: `${y}px`,
display: null,
});
this.ripple.disabled = false;
this.ripple.active = true;
this.ripple.unbounded = true;
this.ripple.holdDown = true; // paper-ripple
this.ripple.simulatedRipple(); // paper-ripple
// this.ripple.disabled = false;
// this.ripple.active = true;
// this.ripple.unbounded = true;
}
private stopAnimation() {
this.ripple.active = false;
this.ripple.disabled = true;
this.ripple.holdDown = false; // paper-ripple
// this.ripple.active = false;
// this.ripple.disabled = true;
this.style.display = "none";
}
}
@ -174,7 +183,6 @@ export const longPressBind = (element: LongPressElement) => {
longpress.bind(element);
};
export const longPress = () =>
directive((part: PropertyPart) => {
longPressBind(part.committer.element);
});
export const longPress = directive(() => (part: PropertyPart) => {
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 computeDomain from "../../../common/entity/compute_domain";
import { EntityRowConfig, WeblinkConfig } from "../entity-rows/types";
import { EntitiesCardConfig } from "../cards/hui-entities-card";
const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
const DOMAINS_BADGES = [
@ -24,11 +25,15 @@ const DOMAINS_BADGES = [
"sun",
"timer",
];
const HIDE_DOMAIN = new Set(["persistent_notification", "configurator"]);
const HIDE_DOMAIN = new Set([
"persistent_notification",
"configurator",
"geo_location",
]);
const computeCards = (
title: string,
states: Array<[string, HassEntity]>
states: Array<[string, HassEntity]>,
entityCardOptions: Partial<EntitiesCardConfig>
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
@ -68,7 +73,7 @@ const computeCards = (
type: "weather-forecast",
entity: entityId,
});
} else if (domain === "weblink") {
} else if (domain === "weblink" && stateObj) {
const conf: WeblinkConfig = {
type: "weblink",
url: stateObj.state,
@ -85,9 +90,9 @@ const computeCards = (
if (entities.length > 0) {
cards.unshift({
title,
type: "entities",
entities,
...entityCardOptions,
});
}
@ -152,10 +157,13 @@ const generateViewConfig = (
splitted.groups.forEach((groupEntity) => {
cards = cards.concat(
computeCards(
computeStateName(groupEntity),
groupEntity.attributes.entity_id.map(
(entityId): [string, HassEntity] => [entityId, entities[entityId]]
)
),
{
title: computeStateName(groupEntity),
show_header_toggle: groupEntity.attributes.control !== "hidden",
}
)
);
});
@ -165,10 +173,12 @@ const generateViewConfig = (
.forEach((domain) => {
cards = cards.concat(
computeCards(
localize(`domain.${domain}`),
ungroupedEntitites[domain].map(
(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.
if (views.length > 1 && title === "Home") {
title = "Home Assistant";

View File

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

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-textarea";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
@ -51,7 +55,7 @@ export class HuiActionEditor extends LitElement {
return config.service || "";
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this.hass || !this.actions) {
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-menu-button/paper-menu-button";
import "@polymer/paper-icon-button/paper-icon-button";
@ -46,10 +46,6 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) {
paper-button {
color: var(--primary-color);
font-weight: 500;
letter-spacing: 0.05em;
font-size: 16px;
padding: 0;
margin: 0;
}
paper-icon-button {
color: var(--primary-text-color);

View File

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

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../types";
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) {
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 { TemplateResult } from "lit-html";
import { HomeAssistant } from "../../../types";
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(
Object.keys(this.hass!.themes.themes).sort()
);

View File

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

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import { html, LitElement, TemplateResult } from "lit-element";
import "@polymer/paper-button/paper-button";
import { HomeAssistant } from "../../../../types";
@ -39,7 +38,7 @@ export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) {
public hass?: HomeAssistant;
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
protected render(): TemplateResult {
protected render(): TemplateResult | void {
return html`
${this.renderStyle()}
<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 { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../types";
import { HASSDomEvent } from "../../../../common/dom/fire_event";
@ -48,7 +52,7 @@ export class HuiDialogEditCard extends LitElement {
: undefined;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._params) {
return html``;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner";
import "@polymer/paper-dialog/paper-dialog";
// 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")!;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
return html`
${this.renderStyle()}
<paper-dialog with-backdrop>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import {
html,
LitElement,
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-input/paper-input";
import { EditorTarget } from "../types";
@ -59,7 +63,7 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this.hass) {
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";
@ -8,7 +8,6 @@ import { longPress } from "../common/directives/long-press-directive";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
interface Config extends LovelaceElementConfig {
icon: string;
@ -31,7 +30,7 @@ export class HuiIconElement extends hassLocalizeLitMixin(LitElement)
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config) {
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";
@ -8,7 +8,6 @@ import { longPress } from "../common/directives/long-press-directive";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
interface Config extends LovelaceElementConfig {
image?: string;
@ -40,7 +39,7 @@ export class HuiImageElement extends hassLocalizeLitMixin(LitElement)
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config) {
return html``;
}

View File

@ -1,5 +1,4 @@
import { html, LitElement } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import { html, LitElement, TemplateResult } from "lit-element";
import "../../../components/buttons/ha-call-service-button";
@ -37,7 +36,7 @@ export class HuiServiceButtonElement extends LitElement
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (!this._config) {
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 computeStateName from "../../../common/entity/compute_state_name";
import { LovelaceElement, LovelaceElementConfig } from "./types";
import { HomeAssistant } from "../../../types";
import { TemplateResult } from "lit-html";
export class HuiStateBadgeElement extends LitElement
implements LovelaceElement {
@ -24,7 +23,7 @@ export class HuiStateBadgeElement extends LitElement
this._config = config;
}
protected render(): TemplateResult {
protected render(): TemplateResult | void {
if (
!this._config ||
!this.hass ||

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