Merge pull request #4318 from home-assistant/dev

20191204.0
This commit is contained in:
Bram Kragten 2019-12-04 20:02:41 +01:00 committed by GitHub
commit d7e7798a55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
225 changed files with 7153 additions and 2621 deletions

View File

@ -41,7 +41,32 @@ Provide details about what browser (and version) you are seeing the issue in. An
**Description of problem:** **Description of problem:**
<!-- <!--
Explain what the issue is, and how things should look/behave. If possible provide a screenshot with a description. Explain what the issue is, and what is the current behaviour. If possible provide a screenshot with a description.
-->
**Expected behaviour:**
<!--
Explain how things should look/behave. If possible provide a screenshot with a description.
-->
**Relevant config:**
<!--
Give the config of both the integration that is used, the Lovelace config, scene, automation or otherwise relevant configuration.
-->
**Steps to reproduce this problem:**
<!--
Sum up all steps that are necesarry to reproduce this bug.
For example:
1. Add a climate integration
2. Navigate to Lovelace
3. Click more info of the climate entity
4. Set the hvac action to heat
5. Set the temperature higher than the current temperature
6. Set the hvac action to cool
--> -->
**Javascript errors shown in the web inspector (if applicable):** **Javascript errors shown in the web inspector (if applicable):**

View File

@ -2,9 +2,9 @@
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend. This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://home-assistant.io/demo/) [![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://demo.home-assistant.io/)
- [View demo of the Polymer frontend](https://home-assistant.io/demo/) - [View demo of Home Assistant](https://demo.home-assistant.io/)
- [More information about Home Assistant](https://home-assistant.io) - [More information about Home Assistant](https://home-assistant.io)
- [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html) - [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html)
@ -31,3 +31,5 @@ It is possible to compile the project and/or run commands in the development env
## License ## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects. Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variation of devices.

View File

@ -33,6 +33,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
pragma: "h", pragma: "h",
}, },
], ],
"@babel/plugin-proposal-optional-chaining",
[ [
require("@babel/plugin-proposal-decorators").default, require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true }, { decoratorsBeforeExport: true },

View File

@ -175,9 +175,9 @@ export class HcMain extends HassElement {
} catch (err) { } catch (err) {
// Generate a Lovelace config. // Generate a Lovelace config.
this._unsubLovelace = () => undefined; this._unsubLovelace = () => undefined;
const { const { generateLovelaceConfigFromHass } = await import(
generateLovelaceConfigFromHass, "../../../../src/panels/lovelace/common/generate-lovelace-config"
} = await import("../../../../src/panels/lovelace/common/generate-lovelace-config"); );
this._handleNewLovelaceConfig( this._handleNewLovelaceConfig(
await generateLovelaceConfigFromHass(this.hass!) await generateLovelaceConfigFromHass(this.hass!)
); );

View File

@ -53,7 +53,7 @@ class CardModder extends LitElement {
for (var k in this._config.style) { for (var k in this._config.style) {
if (window.cardTools.hasTemplate(this._config.style[k])) if (window.cardTools.hasTemplate(this._config.style[k]))
this.templated.push(k); this.templated.push(k);
this.card.style.setProperty(k, ''); this.card.style.setProperty(k, "");
target.style.setProperty( target.style.setProperty(
k, k,
window.cardTools.parseTemplate(this._config.style[k]) window.cardTools.parseTemplate(this._config.style[k])

View File

@ -12,5 +12,7 @@ import "./resources/hademo-icons";
/* polyfill for paper-dropdown */ /* polyfill for paper-dropdown */
setTimeout(() => { setTimeout(() => {
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"); import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
}, 1000); }, 1000);

View File

@ -65,74 +65,79 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"]; const incrementalUnits = ["clients", "queries", "ads"];
export const mockHistory = (mockHass: MockHomeAssistant) => { export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(new RegExp("history/period/.+"), ( mockHass.mockAPI(
hass, new RegExp("history/period/.+"),
// @ts-ignore (
method, hass,
path, // @ts-ignore
// @ts-ignore method,
parameters path,
) => { // @ts-ignore
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]); parameters
const entities = params.filter_entity_id.split(","); ) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
const results: HassEntity[][] = []; const results: HassEntity[][] = [];
for (const entityId of entities) { for (const entityId of entities) {
const state = hass.states[entityId]; const state = hass.states[entityId];
if (!state) { if (!state) {
continue; continue;
} }
if (!state.attributes.unit_of_measurement) { if (!state.attributes.unit_of_measurement) {
results.push(generateHistory(state, [state.state])); results.push(generateHistory(state, [state.state]));
continue; continue;
} }
const numberState = Number(state.state); const numberState = Number(state.state);
if (isNaN(numberState)) { if (isNaN(numberState)) {
// tslint:disable-next-line // tslint:disable-next-line
console.log( console.log(
"Ignoring state with unparsable state but with a unit", "Ignoring state with unparsable state but with a unit",
entityId, entityId,
state state
);
continue;
}
const statesToGenerate = 15;
let genFunc;
if (incrementalUnits.includes(state.attributes.unit_of_measurement)) {
let initial = Math.floor(
numberState * 0.4 + numberState * Math.random() * 0.2
);
const diff = Math.max(
1,
Math.floor((numberState - initial) / statesToGenerate)
);
genFunc = () => {
initial += diff;
return Math.min(numberState, initial);
};
} else {
const diff = Math.floor(
numberState * (numberState > 80 ? 0.05 : 0.5)
);
genFunc = () =>
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
); );
continue;
} }
return results;
const statesToGenerate = 15;
let genFunc;
if (incrementalUnits.includes(state.attributes.unit_of_measurement)) {
let initial = Math.floor(
numberState * 0.4 + numberState * Math.random() * 0.2
);
const diff = Math.max(
1,
Math.floor((numberState - initial) / statesToGenerate)
);
genFunc = () => {
initial += diff;
return Math.min(numberState, initial);
};
} else {
const diff = Math.floor(numberState * (numberState > 80 ? 0.05 : 0.5));
genFunc = () =>
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
);
} }
return results; );
});
}; };

View File

@ -12,9 +12,10 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc> localizePromise: Promise<LocalizeFunc>
) => { ) => {
hass.mockWS("lovelace/config", () => hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([
([config, localize]) => config.lovelace(localize) selectedDemoConfig,
) localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
); );
hass.mockWS("lovelace/config/save", () => Promise.resolve()); hass.mockWS("lovelace/config/save", () => Promise.resolve());

View File

@ -44,9 +44,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
selected="{{selectedInput}}" selected="{{selectedInput}}"
> >
<template is="dom-repeat" items="[[inputDevices]]"> <template is="dom-repeat" items="[[inputDevices]]">
<paper-item device\$="[[item.device]]" <paper-item device$="[[item.device]]">[[item.name]]</paper-item>
>[[item.name]]</paper-item
>
</template> </template>
</paper-listbox> </paper-listbox>
</paper-dropdown-menu> </paper-dropdown-menu>
@ -57,9 +55,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
selected="{{selectedOutput}}" selected="{{selectedOutput}}"
> >
<template is="dom-repeat" items="[[outputDevices]]"> <template is="dom-repeat" items="[[outputDevices]]">
<paper-item device\$="[[item.device]]" <paper-item device$="[[item.device]]">[[item.name]]</paper-item>
>[[item.name]]</paper-item
>
</template> </template>
</paper-listbox> </paper-listbox>
</paper-dropdown-menu> </paper-dropdown-menu>

View File

@ -569,7 +569,10 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
openChangelog() { openChangelog() {
this.hass this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`) .callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
.then((resp) => resp, () => "Error getting changelog") .then(
(resp) => resp,
() => "Error getting changelog"
)
.then((content) => { .then((content) => {
showHassioMarkdownDialog(this, { showHassioMarkdownDialog(this, {
title: "Changelog", title: "Changelog",

View File

@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement {
this.supervisorInfo.version, this.supervisorInfo.version,
this.supervisorInfo.last_version, this.supervisorInfo.last_version,
"hassio/supervisor/update", "hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${ `https://github.com//home-assistant/hassio/releases/tag/${this.supervisorInfo.last_version}`
this.supervisorInfo.last_version
}`
)} )}
${this.hassOsInfo ${this.hassOsInfo
? this._renderUpdateCard( ? this._renderUpdateCard(
@ -84,9 +82,7 @@ export class HassioUpdate extends LitElement {
this.hassOsInfo.version, this.hassOsInfo.version,
this.hassOsInfo.version_latest, this.hassOsInfo.version_latest,
"hassio/hassos/update", "hassio/hassos/update",
`https://github.com//home-assistant/hassos/releases/tag/${ `https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
this.hassOsInfo.version_latest
}`
) )
: ""} : ""}
</div> </div>

View File

@ -12,7 +12,9 @@ export const showHassioMarkdownDialog = (
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-markdown", dialogTag: "dialog-hassio-markdown",
dialogImport: () => dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"), import(
/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"
),
dialogParams, dialogParams,
}); });
}; };

View File

@ -12,7 +12,9 @@ export const showHassioSnapshotDialog = (
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot", dialogTag: "dialog-hassio-snapshot",
dialogImport: () => dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"), import(
/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"
),
dialogParams, dialogParams,
}); });
}; };

View File

@ -56,12 +56,16 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
addon: { addon: {
tag: "hassio-addon-view", tag: "hassio-addon-view",
load: () => load: () =>
import(/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"), import(
/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"
),
}, },
ingress: { ingress: {
tag: "hassio-ingress-view", tag: "hassio-ingress-view",
load: () => load: () =>
import(/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"), import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
}, },
}, },
}; };

View File

@ -19,13 +19,14 @@
"dependencies": { "dependencies": {
"@material/chips": "^3.2.0", "@material/chips": "^3.2.0",
"@material/data-table": "^3.2.0", "@material/data-table": "^3.2.0",
"@material/mwc-base": "^0.8.0", "@material/mwc-base": "^0.10.0",
"@material/mwc-button": "^0.8.0", "@material/mwc-button": "^0.10.0",
"@material/mwc-checkbox": "^0.8.0", "@material/mwc-checkbox": "^0.10.0",
"@material/mwc-fab": "^0.8.0", "@material/mwc-dialog": "^0.10.0",
"@material/mwc-ripple": "^0.8.0", "@material/mwc-fab": "^0.10.0",
"@material/mwc-switch": "^0.8.0", "@material/mwc-ripple": "^0.10.0",
"@mdi/svg": "4.5.95", "@material/mwc-switch": "^0.10.0",
"@mdi/svg": "4.6.95",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.0.2",
"@polymer/app-localize-behavior": "^3.0.1", "@polymer/app-localize-behavior": "^3.0.1",
"@polymer/app-route": "^3.0.2", "@polymer/app-route": "^3.0.2",
@ -68,8 +69,8 @@
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.3.7", "@thomasloven/round-slider": "0.3.7",
"@vaadin/vaadin-combo-box": "^4.2.8", "@vaadin/vaadin-combo-box": "^5.0.6",
"@vaadin/vaadin-date-picker": "^3.3.3", "@vaadin/vaadin-date-picker": "^4.0.3",
"@webcomponents/shadycss": "^1.9.0", "@webcomponents/shadycss": "^1.9.0",
"@webcomponents/webcomponentsjs": "^2.2.7", "@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0", "chart.js": "~2.8.0",
@ -98,21 +99,23 @@
"regenerator-runtime": "^0.13.2", "regenerator-runtime": "^0.13.2",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"superstruct": "^0.6.1", "superstruct": "^0.6.1",
"copy-to-clipboard": "^1.0.9",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"web-animations-js": "^2.3.1", "web-animations-js": "^2.3.1",
"xss": "^1.0.6" "xss": "^1.0.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.0", "@babel/core": "^7.7.4",
"@babel/plugin-external-helpers": "^7.2.0", "@babel/plugin-external-helpers": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.4.0", "@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.4.0", "@babel/plugin-proposal-decorators": "^7.7.4",
"@babel/plugin-proposal-object-rest-spread": "^7.4.0", "@babel/plugin-proposal-object-rest-spread": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-proposal-optional-chaining": "^7.7.4",
"@babel/plugin-transform-react-jsx": "^7.3.0", "@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/preset-env": "^7.4.2", "@babel/plugin-transform-react-jsx": "^7.7.4",
"@babel/preset-typescript": "^7.4.0", "@babel/preset-env": "^7.7.4",
"@babel/preset-typescript": "^7.7.4",
"@types/chai": "^4.1.7", "@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12", "@types/chromecast-caf-receiver": "^3.0.12",
"@types/chromecast-caf-sender": "^1.0.1", "@types/chromecast-caf-sender": "^1.0.1",
@ -154,18 +157,18 @@
"merge-stream": "^1.0.1", "merge-stream": "^1.0.1",
"mocha": "^6.0.2", "mocha": "^6.0.2",
"parse5": "^5.1.0", "parse5": "^5.1.0",
"prettier": "^1.16.4", "prettier": "^1.19.1",
"raw-loader": "^2.0.0", "raw-loader": "^2.0.0",
"reify": "^0.18.1", "reify": "^0.18.1",
"require-dir": "^1.2.0", "require-dir": "^1.2.0",
"sinon": "^7.3.1", "sinon": "^7.3.1",
"terser-webpack-plugin": "^1.2.3", "terser-webpack-plugin": "^1.2.3",
"ts-mocha": "^6.0.0", "ts-mocha": "^6.0.0",
"tslint": "^5.14.0", "tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"tslint-eslint-rules": "^5.4.0", "tslint-eslint-rules": "^5.4.0",
"tslint-plugin-prettier": "^2.0.1", "tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.6.3", "typescript": "^3.7.2",
"web-component-tester": "^6.9.2", "web-component-tester": "^6.9.2",
"webpack": "^4.40.2", "webpack": "^4.40.2",
"webpack-cli": "^3.3.9", "webpack-cli": "^3.3.9",
@ -178,7 +181,6 @@
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569", "_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"resolutions": { "resolutions": {
"@webcomponents/webcomponentsjs": "^2.2.10", "@webcomponents/webcomponentsjs": "^2.2.10",
"@vaadin/vaadin-lumo-styles": "^1.4.2",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"lit-html": "^1.1.2" "lit-html": "^1.1.2"
}, },

View File

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

View File

@ -98,9 +98,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
<ha-markdown <ha-markdown
allowsvg allowsvg
.content=${this.localize( .content=${this.localize(
`ui.panel.page-authorize.form.providers.${ `ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`
step.handler[0]
}.abort.${step.reason}`
)} )}
></ha-markdown> ></ha-markdown>
`; `;
@ -229,9 +227,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
} }
private _computeStepDescription(step: DataEntryFlowStepForm) { private _computeStepDescription(step: DataEntryFlowStepForm) {
const resourceKey = `ui.panel.page-authorize.form.providers.${ const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`;
step.handler[0]
}.step.${step.step_id}.description`;
const args: string[] = []; const args: string[] = [];
const placeholders = step.description_placeholders || {}; const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => { Object.keys(placeholders).forEach((key) => {
@ -245,9 +241,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
// Returns a callback for ha-form to calculate labels per schema object // Returns a callback for ha-form to calculate labels per schema object
return (schema) => return (schema) =>
this.localize( this.localize(
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${ `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.data.${schema.name}`
step.step_id
}.data.${schema.name}`
); );
} }
@ -255,9 +249,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
// Returns a callback for ha-form to calculate error messages // Returns a callback for ha-form to calculate error messages
return (error) => return (error) =>
this.localize( this.localize(
`ui.panel.page-authorize.form.providers.${ `ui.panel.page-authorize.form.providers.${step.handler[0]}.error.${error}`
step.handler[0]
}.error.${error}`
); );
} }

View File

@ -11,7 +11,9 @@ import "./ha-auth-flow";
import { AuthProvider, fetchAuthProviders } from "../data/auth"; import { AuthProvider, fetchAuthProviders } from "../data/auth";
import { registerServiceWorker } from "../util/register-service-worker"; import { registerServiceWorker } from "../util/register-service-worker";
import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider"); import(
/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider"
);
interface QueryParams { interface QueryParams {
client_id?: string; client_id?: string;

View File

@ -10,11 +10,11 @@ function toLocaleDateStringSupportsOptions() {
return false; return false;
} }
export default (toLocaleDateStringSupportsOptions() export default toLocaleDateStringSupportsOptions()
? (dateObj: Date, locales: string) => ? (dateObj: Date, locales: string) =>
dateObj.toLocaleDateString(locales, { dateObj.toLocaleDateString(locales, {
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
}) })
: (dateObj: Date) => fecha.format(dateObj, "mediumDate")); : (dateObj: Date) => fecha.format(dateObj, "mediumDate");

View File

@ -10,7 +10,7 @@ function toLocaleStringSupportsOptions() {
return false; return false;
} }
export default (toLocaleStringSupportsOptions() export default toLocaleStringSupportsOptions()
? (dateObj: Date, locales: string) => ? (dateObj: Date, locales: string) =>
dateObj.toLocaleString(locales, { dateObj.toLocaleString(locales, {
year: "numeric", year: "numeric",
@ -19,4 +19,4 @@ export default (toLocaleStringSupportsOptions()
hour: "numeric", hour: "numeric",
minute: "2-digit", minute: "2-digit",
}) })
: (dateObj: Date) => fecha.format(dateObj, "haDateTime")); : (dateObj: Date) => fecha.format(dateObj, "haDateTime");

View File

@ -10,10 +10,10 @@ function toLocaleTimeStringSupportsOptions() {
return false; return false;
} }
export default (toLocaleTimeStringSupportsOptions() export default toLocaleTimeStringSupportsOptions()
? (dateObj: Date, locales: string) => ? (dateObj: Date, locales: string) =>
dateObj.toLocaleTimeString(locales, { dateObj.toLocaleTimeString(locales, {
hour: "numeric", hour: "numeric",
minute: "2-digit", minute: "2-digit",
}) })
: (dateObj: Date) => fecha.format(dateObj, "shortTime")); : (dateObj: Date) => fecha.format(dateObj, "shortTime");

View File

@ -60,7 +60,7 @@ export const applyThemesOnElement = (
element.updateStyles(styles); element.updateStyles(styles);
} else if (window.ShadyCSS) { } else if (window.ShadyCSS) {
// implement updateStyles() method of Polymer elements // implement updateStyles() method of Polymer elements
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ (element), styles); window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
} }
if (!updateMeta) { if (!updateMeta) {

View File

@ -0,0 +1,29 @@
import { directive, Part, NodePart } from "lit-html";
export const dynamicContentDirective = directive(
(tag: string, properties: { [key: string]: any }) => (part: Part): void => {
if (!(part instanceof NodePart)) {
throw new Error(
"dynamicContentDirective can only be used in content bindings"
);
}
let element = part.value as HTMLElement | undefined;
if (
element !== undefined &&
tag.toUpperCase() === (element as HTMLElement).tagName
) {
Object.entries(properties).forEach(([key, value]) => {
element![key] = value;
});
return;
}
element = document.createElement(tag);
Object.entries(properties).forEach(([key, value]) => {
element![key] = value;
});
part.setValue(element);
}
);

View File

@ -11,7 +11,9 @@ export const setupLeafletMap = async (
throw new Error("Cannot setup Leaflet map on disconnected element"); throw new Error("Cannot setup Leaflet map on disconnected element");
} }
// tslint:disable-next-line // tslint:disable-next-line
const Leaflet = (await import(/* webpackChunkName: "leaflet" */ "leaflet")) as LeafletModuleType; const Leaflet = (await import(
/* webpackChunkName: "leaflet" */ "leaflet"
)) as LeafletModuleType;
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/"; Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
const map = Leaflet.map(mapElement); const map = Leaflet.map(mapElement);

View File

@ -7,8 +7,11 @@
// export function onChangeEvent(this: OnChangeComponent, prop, ev) { // export function onChangeEvent(this: OnChangeComponent, prop, ev) {
export function onChangeEvent(this: any, prop, ev) { export function onChangeEvent(this: any, prop, ev) {
const origData = this.props[prop]; if (!this.initialized) {
return;
}
const origData = this.props[prop];
if (ev.target.value === origData[ev.target.name]) { if (ev.target.value === origData[ev.target.name]) {
return; return;
} }

View File

@ -6,8 +6,9 @@ import {
MDCDataTableFoundation, MDCDataTableFoundation,
} from "@material/data-table"; } from "@material/data-table";
import { classMap } from "lit-html/directives/class-map";
import { import {
BaseElement,
html, html,
query, query,
queryAll, queryAll,
@ -15,10 +16,11 @@ import {
css, css,
customElement, customElement,
property, property,
classMap,
TemplateResult, TemplateResult,
PropertyValues, PropertyValues,
} from "@material/mwc-base/base-element"; } from "lit-element";
import { BaseElement } from "@material/mwc-base/base-element";
// eslint-disable-next-line import/no-webpack-loader-syntax // eslint-disable-next-line import/no-webpack-loader-syntax
// @ts-ignore // @ts-ignore

View File

@ -0,0 +1,412 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import "@polymer/paper-listbox/paper-listbox";
import memoizeOne from "memoize-one";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
customElement,
property,
PropertyValues,
} from "lit-element";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import "./ha-devices-picker";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../data/device_registry";
import { compare } from "../../common/string/compare";
import { PolymerChangedEvent } from "../../polymer-types";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../data/area_registry";
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { computeDomain } from "../../common/entity/compute_domain";
interface DevicesByArea {
[areaId: string]: AreaDevices;
}
interface AreaDevices {
id?: string;
name: string;
devices: string[];
}
const rowRenderer = (
root: HTMLElement,
_owner,
model: { item: AreaDevices }
) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-item {
width: 100%;
margin: -10px 0;
padding: 0;
}
paper-icon-button {
float: right;
}
.devices {
display: none;
}
.devices.visible {
display: block;
}
</style>
<paper-item>
<paper-item-body two-line="">
<div class='name'>[[item.name]]</div>
<div secondary>[[item.devices.length]] devices</div>
</paper-item-body>
</paper-item>
`;
}
root.querySelector(".name")!.textContent = model.item.name!;
root.querySelector(
"[secondary]"
)!.textContent = `${model.item.devices.length.toString()} devices`;
};
@customElement("ha-area-devices-picker")
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property() public area?: string;
@property() public devices?: string[];
/**
* Show only devices with entities from specific domains.
* @type {Array}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no devices with entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
/**
* Show only deviced with entities of these device classes.
* @type {Array}
* @attr include-device-classes
*/
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
@property({ type: Boolean })
private _opened?: boolean;
@property() private _areaPicker = true;
@property() private _devices?: DeviceRegistryEntry[];
@property() private _areas?: AreaRegistryEntry[];
@property() private _entities?: EntityRegistryEntry[];
private _selectedDevices: string[] = [];
private _filteredDevices: DeviceRegistryEntry[] = [];
private _getDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
areas: AreaRegistryEntry[],
entities: EntityRegistryEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"]
): AreaDevices[] => {
if (!devices.length) {
return [];
}
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
let inputDevices = [...devices];
if (includeDomains) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
}
if (excludeDomains) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
}
if (includeDeviceClasses) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
}
this._filteredDevices = inputDevices;
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
for (const area of areas) {
areaLookup[area.area_id] = area;
}
const devicesByArea: DevicesByArea = {};
for (const device of inputDevices) {
const areaId = device.area_id;
if (areaId) {
if (!(areaId in devicesByArea)) {
devicesByArea[areaId] = {
id: areaId,
name: areaLookup[areaId].name,
devices: [],
};
}
devicesByArea[areaId].devices.push(device.id);
}
}
const sorted = Object.keys(devicesByArea)
.sort((a, b) =>
compare(devicesByArea[a].name || "", devicesByArea[b].name || "")
)
.map((key) => devicesByArea[key]);
return sorted;
}
);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
}),
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = areas;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("area") && this.area) {
this._areaPicker = true;
this.value = this.area;
} else if (changedProps.has("devices") && this.devices) {
this._areaPicker = false;
const filteredDeviceIds = this._filteredDevices.map(
(device) => device.id
);
const selectedDevices = this.devices.filter((device) =>
filteredDeviceIds.includes(device)
);
this._setValue(selectedDevices);
}
}
protected render(): TemplateResult | void {
if (!this._devices || !this._areas || !this._entities) {
return;
}
const areas = this._getDevices(
this._devices,
this._areas,
this._entities,
this.includeDomains,
this.excludeDomains,
this.includeDeviceClasses
);
if (!this._areaPicker || areas.length === 0) {
return html`
<ha-devices-picker
@value-changed=${this._devicesPicked}
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.value=${this._selectedDevices}
.pickDeviceLabel=${`Add ${this.label} device`}
.pickedDeviceLabel=${`${this.label} device`}
></ha-devices-picker>
${areas.length > 0
? html`
<mwc-button @click=${this._switchPicker}
>Choose an area</mwc-button
>
`
: ""}
`;
}
return html`
<vaadin-combo-box-light
item-value-path="id"
item-id-path="id"
item-label-path="name"
.items=${areas}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._areaPicked}
>
<paper-input
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.device-picker.device")
: `${this.label} in area`}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</paper-icon-button>
`
: ""}
${areas.length > 0
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</paper-icon-button>
`
: ""}
</paper-input>
</vaadin-combo-box-light>
<mwc-button @click=${this._switchPicker}
>Choose individual devices</mwc-button
>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue([]);
}
private get _value() {
return this.value || [];
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private async _switchPicker() {
this._areaPicker = !this._areaPicker;
}
private async _areaPicked(ev: PolymerChangedEvent<string>) {
const value = ev.detail.value;
let selectedDevices = [];
const target = ev.target as any;
if (target.selectedItem) {
selectedDevices = target.selectedItem.devices;
}
if (value !== this._value || this._selectedDevices !== selectedDevices) {
this._setValue(selectedDevices, value);
}
}
private _devicesPicked(ev: CustomEvent) {
ev.stopPropagation();
const selectedDevices = ev.detail.value;
this._setValue(selectedDevices);
}
private _setValue(selectedDevices: string[], value = "") {
this.value = value;
this._selectedDevices = selectedDevices;
setTimeout(() => {
fireEvent(this, "value-changed", { value: selectedDevices });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResult {
return css`
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-area-devices-picker": HaAreaDevicesPicker;
}
}

View File

@ -176,6 +176,7 @@ export abstract class HaDeviceAutomationPicker<
this.value = automation; this.value = automation;
setTimeout(() => { setTimeout(() => {
fireEvent(this, "change"); fireEvent(this, "change");
fireEvent(this, "value-changed", { value: automation });
}, 0); }, 0);
} }

View File

@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { import {

View File

@ -0,0 +1,127 @@
import {
LitElement,
TemplateResult,
property,
html,
customElement,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button-light";
import { HomeAssistant } from "../../types";
import { PolymerChangedEvent } from "../../polymer-types";
import { fireEvent } from "../../common/dom/fire_event";
import "./ha-device-picker";
@customElement("ha-devices-picker")
class HaDevicesPicker extends LitElement {
@property() public hass?: HomeAssistant;
@property() public value?: string[];
/**
* Show entities from specific domains.
* @type {string}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
@property({ attribute: "picked-device-label" })
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
public pickedDeviceLabel?: string;
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
protected render(): TemplateResult | void {
if (!this.hass) {
return;
}
const currentDevices = this._currentDevices;
return html`
${currentDevices.map(
(entityId) => html`
<div>
<ha-device-picker
allow-custom-entity
.curValue=${entityId}
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.value=${entityId}
.label=${this.pickedDeviceLabel}
@value-changed=${this._deviceChanged}
></ha-device-picker>
</div>
`
)}
<div>
<ha-device-picker
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.label=${this.pickDeviceLabel}
@value-changed=${this._addDevice}
></ha-device-picker>
</div>
`;
}
private get _currentDevices() {
return this.value || [];
}
private async _updateDevices(devices) {
fireEvent(this, "value-changed", {
value: devices,
});
this.value = devices;
}
private _deviceChanged(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const curValue = (event.currentTarget as any).curValue;
const newValue = event.detail.value;
if (newValue === curValue || newValue !== "") {
return;
}
if (newValue === "") {
this._updateDevices(
this._currentDevices.filter((dev) => dev !== curValue)
);
} else {
this._updateDevices(
this._currentDevices.map((dev) => (dev === curValue ? newValue : dev))
);
}
}
private async _addDevice(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const toAdd = event.detail.value;
(event.currentTarget as any).value = "";
if (!toAdd) {
return;
}
const currentDevices = this._currentDevices;
if (currentDevices.includes(toAdd)) {
return;
}
this._updateDevices([...currentDevices, toAdd]);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-devices-picker": HaDevicesPicker;
}
}

View File

@ -215,7 +215,9 @@ class HaChartBase extends mixinBehaviors(
} }
if (scriptsLoaded === null) { if (scriptsLoaded === null) {
scriptsLoaded = import(/* webpackChunkName: "load_chart" */ "../../resources/ha-chart-scripts.js"); scriptsLoaded = import(
/* webpackChunkName: "load_chart" */ "../../resources/ha-chart-scripts.js"
);
} }
scriptsLoaded.then((ChartModule) => { scriptsLoaded.then((ChartModule) => {
this.ChartClass = ChartModule.default; this.ChartClass = ChartModule.default;

View File

@ -2,7 +2,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "./state-badge"; import "./state-badge";

View File

@ -122,8 +122,9 @@ class HaCameraStream extends LitElement {
private async _startHls(): Promise<void> { private async _startHls(): Promise<void> {
// tslint:disable-next-line // tslint:disable-next-line
const Hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any) const Hls = ((await import(
.default as HLSModule; /* webpackChunkName: "hls.js" */ "hls.js"
)) as any).default as HLSModule;
let hlsSupported = Hls.isSupported(); let hlsSupported = Hls.isSupported();
const videoEl = this._videoEl; const videoEl = this._videoEl;

View File

@ -72,9 +72,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
computeCurrentStatus(hass, stateObj) { computeCurrentStatus(hass, stateObj) {
if (!hass || !stateObj) return null; if (!hass || !stateObj) return null;
if (stateObj.attributes.current_temperature != null) { if (stateObj.attributes.current_temperature != null) {
return `${stateObj.attributes.current_temperature} ${ return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`;
hass.config.unit_system.temperature
}`;
} }
if (stateObj.attributes.current_humidity != null) { if (stateObj.attributes.current_humidity != null) {
return `${stateObj.attributes.current_humidity} %`; return `${stateObj.attributes.current_humidity} %`;
@ -89,22 +87,16 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
stateObj.attributes.target_temp_low != null && stateObj.attributes.target_temp_low != null &&
stateObj.attributes.target_temp_high != null stateObj.attributes.target_temp_high != null
) { ) {
return `${stateObj.attributes.target_temp_low}-${ return `${stateObj.attributes.target_temp_low}-${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
stateObj.attributes.target_temp_high
} ${hass.config.unit_system.temperature}`;
} }
if (stateObj.attributes.temperature != null) { if (stateObj.attributes.temperature != null) {
return `${stateObj.attributes.temperature} ${ return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
hass.config.unit_system.temperature
}`;
} }
if ( if (
stateObj.attributes.target_humidity_low != null && stateObj.attributes.target_humidity_low != null &&
stateObj.attributes.target_humidity_high != null stateObj.attributes.target_humidity_high != null
) { ) {
return `${stateObj.attributes.target_humidity_low}-${ return `${stateObj.attributes.target_humidity_low}-${stateObj.attributes.target_humidity_high}%`;
stateObj.attributes.target_humidity_high
}%`;
} }
if (stateObj.attributes.humidity != null) { if (stateObj.attributes.humidity != null) {
return `${stateObj.attributes.humidity} %`; return `${stateObj.attributes.humidity} %`;
@ -121,9 +113,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
const stateString = localize(`state.climate.${stateObj.state}`); const stateString = localize(`state.climate.${stateObj.state}`);
return stateObj.attributes.hvac_action return stateObj.attributes.hvac_action
? `${localize( ? `${localize(
`state_attributes.climate.hvac_action.${ `state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
stateObj.attributes.hvac_action
}`
)} (${stateString})` )} (${stateString})`
: stateString; : stateString;
} }

View File

@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light"; import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import { EventsMixin } from "../mixins/events-mixin"; import { EventsMixin } from "../mixins/events-mixin";

View File

@ -1,4 +1,5 @@
import { classMap, html, customElement } from "@material/mwc-base/base-element"; import { classMap } from "lit-html/directives/class-map";
import { html, customElement } from "lit-element";
import { ripple } from "@material/mwc-ripple/ripple-directive.js"; import { ripple } from "@material/mwc-ripple/ripple-directive.js";
import "@material/mwc-fab"; import "@material/mwc-fab";

View File

@ -58,14 +58,10 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) {
stateObj.attributes.target_temp_low != null && stateObj.attributes.target_temp_low != null &&
stateObj.attributes.target_temp_high != null stateObj.attributes.target_temp_high != null
) { ) {
return `${stateObj.attributes.target_temp_low} - ${ return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
stateObj.attributes.target_temp_high
} ${hass.config.unit_system.temperature}`;
} }
if (stateObj.attributes.temperature != null) { if (stateObj.attributes.temperature != null) {
return `${stateObj.attributes.temperature} ${ return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
hass.config.unit_system.temperature
}`;
} }
return ""; return "";

View File

@ -0,0 +1,81 @@
import { safeDump, safeLoad } from "js-yaml";
import "./ha-code-editor";
import { LitElement, property, customElement, html } from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
const isEmpty = (obj: object) => {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
return false;
}
}
return true;
};
@customElement("ha-yaml-editor")
export class HaYamlEditor extends LitElement {
@property() public value?: any;
@property() public isValid = true;
@property() public label?: string;
@property() private _yaml?: string;
protected firstUpdated() {
try {
this._yaml =
this.value && !isEmpty(this.value) ? safeDump(this.value) : "";
} catch (err) {
alert(`There was an error converting to YAML: ${err}`);
}
}
protected render() {
if (this._yaml === undefined) {
return;
}
return html`
${this.label
? html`
<p>${this.label}</p>
`
: ""}
<ha-code-editor
.value=${this._yaml}
mode="yaml"
.error=${this.isValid === false}
@value-changed=${this._onChange}
></ha-code-editor>
`;
}
private _onChange(ev: CustomEvent) {
ev.stopPropagation();
const value = ev.detail.value;
let parsed;
let isValid = true;
if (value) {
try {
parsed = safeLoad(value);
isValid = true;
} catch (err) {
// Invalid YAML
isValid = false;
}
} else {
parsed = {};
}
this.value = parsed;
this.isValid = isValid;
if (isValid) {
fireEvent(this, "value-changed", { value: parsed });
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-yaml-editor": HaYamlEditor;
}
}

View File

@ -19,9 +19,7 @@ export interface Stream {
} }
export const computeMJPEGStreamUrl = (entity: CameraEntity) => export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
`/api/camera_proxy_stream/${entity.entity_id}?token=${ `/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`;
entity.attributes.access_token
}`;
export const fetchThumbnailUrlWithCache = ( export const fetchThumbnailUrlWithCache = (
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -3,7 +3,7 @@ import { HomeAssistant } from "../types";
interface ProcessResults { interface ProcessResults {
card: { [key: string]: { [key: string]: string } }; card: { [key: string]: { [key: string]: string } };
speech: { speech: {
[SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string } [SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string };
}; };
} }

View File

@ -18,7 +18,7 @@ export interface DeviceCondition extends DeviceAutomation {
} }
export interface DeviceTrigger extends DeviceAutomation { export interface DeviceTrigger extends DeviceAutomation {
platform: string; platform: "device";
} }
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) => export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
@ -107,9 +107,7 @@ export const localizeDeviceAutomationAction = (
state ? computeStateName(state) : "<unknown>", state ? computeStateName(state) : "<unknown>",
"subtype", "subtype",
hass.localize( hass.localize(
`component.${action.domain}.device_automation.action_subtype.${ `component.${action.domain}.device_automation.action_subtype.${action.subtype}`
action.subtype
}`
) )
); );
}; };
@ -122,16 +120,12 @@ export const localizeDeviceAutomationCondition = (
? hass.states[condition.entity_id] ? hass.states[condition.entity_id]
: undefined; : undefined;
return hass.localize( return hass.localize(
`component.${condition.domain}.device_automation.condition_type.${ `component.${condition.domain}.device_automation.condition_type.${condition.type}`,
condition.type
}`,
"entity_name", "entity_name",
state ? computeStateName(state) : "<unknown>", state ? computeStateName(state) : "<unknown>",
"subtype", "subtype",
hass.localize( hass.localize(
`component.${condition.domain}.device_automation.condition_subtype.${ `component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
condition.subtype
}`
) )
); );
}; };
@ -142,16 +136,12 @@ export const localizeDeviceAutomationTrigger = (
) => { ) => {
const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined; const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined;
return hass.localize( return hass.localize(
`component.${trigger.domain}.device_automation.trigger_type.${ `component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
trigger.type
}`,
"entity_name", "entity_name",
state ? computeStateName(state) : "<unknown>", state ? computeStateName(state) : "<unknown>",
"subtype", "subtype",
hass.localize( hass.localize(
`component.${trigger.domain}.device_automation.trigger_subtype.${ `component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
trigger.subtype
}`
) )
); );
}; };

View File

@ -149,9 +149,7 @@ export const createHassioSession = async (hass: HomeAssistant) => {
"POST", "POST",
"hassio/ingress/session" "hassio/ingress/session"
); );
document.cookie = `ingress_session=${ document.cookie = `ingress_session=${response.data.session};path=/api/hassio_ingress/`;
response.data.session
};path=/api/hassio_ingress/`;
}; };
export const reloadHassioAddons = (hass: HomeAssistant) => export const reloadHassioAddons = (hass: HomeAssistant) =>

View File

@ -18,38 +18,6 @@ export const SCENE_IGNORED_DOMAINS = [
"zone", "zone",
]; ];
export const SCENE_SAVED_ATTRIBUTES = {
light: [
"brightness",
"color_temp",
"effect",
"rgb_color",
"xy_color",
"hs_color",
],
media_player: [
"is_volume_muted",
"volume_level",
"sound_mode",
"source",
"media_content_id",
"media_content_type",
],
climate: [
"target_temperature",
"target_temperature_high",
"target_temperature_low",
"target_humidity",
"fan_mode",
"swing_mode",
"hvac_mode",
"preset_mode",
],
vacuum: ["cleaning_mode"],
fan: ["speed", "current_direction"],
water_heather: ["temperature", "operation_mode"],
};
export interface SceneEntity extends HassEntityBase { export interface SceneEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & { id?: string }; attributes: HassEntityAttributeBase & { id?: string };
} }

View File

@ -98,9 +98,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
"ui.dialogs.config_entry_system_options.enable_new_entities_description", "ui.dialogs.config_entry_system_options.enable_new_entities_description",
"integration", "integration",
this.hass.localize( this.hass.localize(
`component.${ `component.${this._params.entry.domain}.config.title`
this._params.entry.domain
}.config.title`
) || this._params.entry.domain ) || this._params.entry.domain
)} )}
</p> </p>

View File

@ -10,7 +10,9 @@ export interface ConfigEntrySystemOptionsDialogParams {
} }
export const loadConfigEntrySystemOptionsDialog = () => export const loadConfigEntrySystemOptionsDialog = () =>
import(/* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options"); import(
/* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options"
);
export const showConfigEntrySystemOptionsDialog = ( export const showConfigEntrySystemOptionsDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -71,9 +71,7 @@ export const showConfigFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field) { renderShowFormStepFieldLabel(hass, step, field) {
return hass.localize( return hass.localize(
`component.${step.handler}.config.step.${step.step_id}.data.${ `component.${step.handler}.config.step.${step.step_id}.data.${field.name}`
field.name
}`
); );
}, },

View File

@ -79,7 +79,9 @@ export interface DataEntryFlowDialogParams {
} }
export const loadDataEntryFlowDialog = () => export const loadDataEntryFlowDialog = () =>
import(/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow"); import(
/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow"
);
export const showFlowDialog = ( export const showFlowDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -54,9 +54,7 @@ export const showOptionsFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field) { renderShowFormStepFieldLabel(hass, step, field) {
return hass.localize( return hass.localize(
`component.${configEntry.domain}.options.step.${step.step_id}.data.${ `component.${configEntry.domain}.options.step.${step.step_id}.data.${field.name}`
field.name
}`
); );
}, },

View File

@ -12,7 +12,9 @@ export interface DeviceRegistryDetailDialogParams {
} }
export const loadDeviceRegistryDetailDialog = () => export const loadDeviceRegistryDetailDialog = () =>
import(/* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail"); import(
/* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail"
);
export const showDeviceRegistryDetailDialog = ( export const showDeviceRegistryDetailDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -6,7 +6,9 @@ export interface HaDomainTogglerDialogParams {
} }
export const loadDomainTogglerDialog = () => export const loadDomainTogglerDialog = () =>
import(/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler"); import(
/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler"
);
export const showDomainTogglerDialog = ( export const showDomainTogglerDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -100,7 +100,7 @@ class MoreInfoClimate extends LitElement {
</div> </div>
` `
: ""} : ""}
${stateObj.attributes.temperature ${stateObj.attributes.temperature !== undefined
? html` ? html`
<ha-climate-control <ha-climate-control
.value=${stateObj.attributes.temperature} .value=${stateObj.attributes.temperature}
@ -112,8 +112,8 @@ class MoreInfoClimate extends LitElement {
></ha-climate-control> ></ha-climate-control>
` `
: ""} : ""}
${stateObj.attributes.target_temp_low || ${stateObj.attributes.target_temp_low !== undefined ||
stateObj.attributes.target_temp_high stateObj.attributes.target_temp_high !== undefined
? html` ? html`
<ha-climate-control <ha-climate-control
.value=${stateObj.attributes.target_temp_low} .value=${stateObj.attributes.target_temp_low}

View File

@ -2,7 +2,7 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@vaadin/vaadin-date-picker/vaadin-date-picker"; import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
import "../../../components/ha-relative-time"; import "../../../components/ha-relative-time";
import "../../../components/paper-time-input"; import "../../../components/paper-time-input";

View File

@ -16,13 +16,14 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const"; import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
import { EventsMixin } from "../../mixins/events-mixin"; import { EventsMixin } from "../../mixins/events-mixin";
import LocalizeMixin from "../../mixins/localize-mixin";
import { computeRTL } from "../../common/util/compute_rtl"; import { computeRTL } from "../../common/util/compute_rtl";
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"]; const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
/* /*
* @appliesMixin EventsMixin * @appliesMixin EventsMixin
*/ */
class MoreInfoControls extends EventsMixin(PolymerElement) { class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() { static get template() {
return html` return html`
<style include="ha-style-dialog"> <style include="ha-style-dialog">
@ -68,7 +69,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
<app-toolbar> <app-toolbar>
<paper-icon-button <paper-icon-button
aria-label="Dismiss dialog" aria-label$="[[localize('ui.dialogs.more_info_control.dismiss')]]"
icon="hass:close" icon="hass:close"
dialog-dismiss dialog-dismiss
></paper-icon-button> ></paper-icon-button>
@ -77,6 +78,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
</div> </div>
<template is="dom-if" if="[[canConfigure]]"> <template is="dom-if" if="[[canConfigure]]">
<paper-icon-button <paper-icon-button
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
icon="hass:settings" icon="hass:settings"
on-click="_gotoSettings" on-click="_gotoSettings"
></paper-icon-button> ></paper-icon-button>

View File

@ -47,6 +47,7 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
<app-toolbar> <app-toolbar>
<ha-paper-icon-button-arrow-prev <ha-paper-icon-button-arrow-prev
aria-label$="[[localize('ui.dialogs.more_info_settings.back')]]"
on-click="_backTapped" on-click="_backTapped"
></ha-paper-icon-button-arrow-prev> ></ha-paper-icon-button-arrow-prev>
<div main-title="">[[_computeStateName(stateObj)]]</div> <div main-title="">[[_computeStateName(stateObj)]]</div>

View File

@ -49,10 +49,10 @@ export class HuiNotificationDrawer extends EventsMixin(
text-align: center; text-align: center;
} }
</style> </style>
<app-drawer id='drawer' opened="{{open}}" disable-swipe align="start"> <app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
<app-toolbar> <app-toolbar>
<div main-title>[[localize('ui.notification_drawer.title')]]</div> <div main-title>[[localize('ui.notification_drawer.title')]]</div>
<ha-paper-icon-button-prev on-click="_closeDrawer"></paper-icon-button> <ha-paper-icon-button-prev on-click="_closeDrawer" aria-label$="[[localize('ui.notification_drawer.close')]]"></paper-icon-button>
</app-toolbar> </app-toolbar>
<div class="notifications"> <div class="notifications">
<template is="dom-if" if="[[!_empty(notifications)]]"> <template is="dom-if" if="[[!_empty(notifications)]]">

View File

@ -1,7 +1,9 @@
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
const loadVoiceCommandDialog = () => const loadVoiceCommandDialog = () =>
import(/* webpackChunkName: "ha-voice-command-dialog" */ "./ha-voice-command-dialog"); import(
/* webpackChunkName: "ha-voice-command-dialog" */ "./ha-voice-command-dialog"
);
export const showVoiceCommandDialog = (element: HTMLElement): void => { export const showVoiceCommandDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {

View File

@ -5,7 +5,9 @@ export interface ZHADeviceInfoDialogParams {
} }
export const loadZHADeviceInfoDialog = () => export const loadZHADeviceInfoDialog = () =>
import(/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info"); import(
/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info"
);
export const showZHADeviceInfoDialog = ( export const showZHADeviceInfoDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -10,6 +10,8 @@ import "../auth/ha-authorize";
/* polyfill for paper-dropdown */ /* polyfill for paper-dropdown */
setTimeout( setTimeout(
() => () =>
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"), import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
),
2000 2000
); );

View File

@ -23,13 +23,16 @@ declare global {
} }
} }
const isExternal = location.search.includes("external_auth=1"); const isExternal =
window.externalApp ||
window.webkit?.messageHandlers?.getExternalAuth ||
location.search.includes("external_auth=1");
const authProm = isExternal const authProm = isExternal
? () => ? () =>
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then( import(
({ createExternalAuth }) => createExternalAuth(hassUrl) /* webpackChunkName: "external_auth" */ "../external_app/external_auth"
) ).then(({ createExternalAuth }) => createExternalAuth(hassUrl))
: () => : () =>
getAuth({ getAuth({
hassUrl, hassUrl,

View File

@ -74,20 +74,23 @@ export const provideHass = (
restResponses.push([path, callback]); restResponses.push([path, callback]);
} }
mockAPI(new RegExp("states/.+"), ( mockAPI(
// @ts-ignore new RegExp("states/.+"),
method, (
path, // @ts-ignore
parameters method,
) => { path,
const [domain, objectId] = path.substr(7).split(".", 2); parameters
if (!domain || !objectId) { ) => {
return; const [domain, objectId] = path.substr(7).split(".", 2);
if (!domain || !objectId) {
return;
}
addEntities(
getEntity(domain, objectId, parameters.state, parameters.attributes)
);
} }
addEntities( );
getEntity(domain, objectId, parameters.state, parameters.attributes)
);
});
const localLanguage = getLocalLanguage(); const localLanguage = getLocalLanguage();
@ -117,9 +120,7 @@ export const provideHass = (
? callback(msg) ? callback(msg)
: Promise.reject({ : Promise.reject({
code: "command_not_mocked", code: "command_not_mocked",
message: `WS Command ${ message: `WS Command ${msg.type} is not implemented in provide_hass.`,
msg.type
} is not implemented in provide_hass.`,
}); });
}, },
subscribeMessage: async (onChange, msg) => { subscribeMessage: async (onChange, msg) => {
@ -128,9 +129,7 @@ export const provideHass = (
? callback(msg, onChange) ? callback(msg, onChange)
: Promise.reject({ : Promise.reject({
code: "command_not_mocked", code: "command_not_mocked",
message: `WS Command ${ message: `WS Command ${msg.type} is not implemented in provide_hass.`,
msg.type
} is not implemented in provide_hass.`,
}); });
}, },
subscribeEvents: async ( subscribeEvents: async (

View File

@ -45,7 +45,9 @@ export class HomeAssistantAppEl extends HassElement {
this._initialize(); this._initialize();
setTimeout(registerServiceWorker, 1000); setTimeout(registerServiceWorker, 1000);
/* polyfill for paper-dropdown */ /* polyfill for paper-dropdown */
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"); import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -55,9 +57,10 @@ export class HomeAssistantAppEl extends HassElement {
this._updateHass({ panelUrl: this._panelUrl }); this._updateHass({ panelUrl: this._panelUrl });
} }
if (changedProps.has("hass")) { if (changedProps.has("hass")) {
this.hassChanged(this.hass!, changedProps.get("hass") as this.hassChanged(
| HomeAssistant this.hass!,
| undefined); changedProps.get("hass") as HomeAssistant | undefined
);
} }
} }

View File

@ -12,33 +12,59 @@ import { removeInitSkeleton } from "../util/init-skeleton";
const CACHE_COMPONENTS = ["lovelace", "states", "developer-tools"]; const CACHE_COMPONENTS = ["lovelace", "states", "developer-tools"];
const COMPONENTS = { const COMPONENTS = {
calendar: () => calendar: () =>
import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"), import(
/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"
),
config: () => config: () =>
import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"), import(
/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"
),
custom: () => custom: () =>
import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"), import(
/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"
),
"developer-tools": () => "developer-tools": () =>
import(/* webpackChunkName: "panel-developer-tools" */ "../panels/developer-tools/ha-panel-developer-tools"), import(
/* webpackChunkName: "panel-developer-tools" */ "../panels/developer-tools/ha-panel-developer-tools"
),
lovelace: () => lovelace: () =>
import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"), import(
/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"
),
states: () => states: () =>
import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"), import(
/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"
),
history: () => history: () =>
import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"), import(
/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"
),
iframe: () => iframe: () =>
import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"), import(
/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"
),
kiosk: () => kiosk: () =>
import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"), import(
/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"
),
logbook: () => logbook: () =>
import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"), import(
/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"
),
mailbox: () => mailbox: () =>
import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"), import(
/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"
),
map: () => map: () =>
import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"), import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"),
profile: () => profile: () =>
import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"), import(
/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"
),
"shopping-list": () => "shopping-list": () =>
import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"), import(
/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"
),
}; };
const getRoutes = (panels: Panels): RouterOptions => { const getRoutes = (panels: Panels): RouterOptions => {

View File

@ -84,8 +84,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._fetchOnboardingSteps(); this._fetchOnboardingSteps();
import(/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"); import(
import(/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config"); /* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"
);
import(
/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config"
);
registerServiceWorker(false); registerServiceWorker(false);
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev)); this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
} }

View File

@ -124,7 +124,9 @@ class OnboardingIntegrations extends LitElement {
loadConfigFlowDialog(); loadConfigFlowDialog();
this._loadConfigEntries(); this._loadConfigEntries();
/* polyfill for paper-dropdown */ /* polyfill for paper-dropdown */
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"); import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
} }
private _createFlow() { private _createFlow() {

View File

@ -14,7 +14,9 @@ export interface AreaRegistryDetailDialogParams {
} }
export const loadAreaRegistryDetailDialog = () => export const loadAreaRegistryDetailDialog = () =>
import(/* webpackChunkName: "area-registry-detail-dialog" */ "./dialog-area-registry-detail"); import(
/* webpackChunkName: "area-registry-detail-dialog" */ "./dialog-area-registry-detail"
);
export const showAreaRegistryDetailDialog = ( export const showAreaRegistryDetailDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -32,6 +32,7 @@ import {
} from "../../../data/automation"; } from "../../../data/automation";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
function AutomationEditor(mountEl, props, mergeEl) { function AutomationEditor(mountEl, props, mergeEl) {
return render(h(Automation, props), mountEl, mergeEl); return render(h(Automation, props), mountEl, mergeEl);
@ -210,15 +211,18 @@ export class HaAutomationEditor extends LitElement {
} }
private _backTapped(): void { private _backTapped(): void {
if ( if (this._dirty) {
this._dirty && showConfirmationDialog(this, {
!confirm( text: this.hass!.localize(
this.hass!.localize("ui.panel.config.automation.editor.unsaved_confirm") "ui.panel.config.automation.editor.unsaved_confirm"
) ),
) { confirmBtnText: this.hass!.localize("ui.common.yes"),
return; cancelBtnText: this.hass!.localize("ui.common.no"),
confirm: () => history.back(),
});
} else {
history.back();
} }
history.back();
} }
private async _delete() { private async _delete() {

View File

@ -115,9 +115,7 @@ class HaAutomationPicker extends LitElement {
<a <a
href=${ifDefined( href=${ifDefined(
automation.attributes.id automation.attributes.id
? `/config/automation/edit/${ ? `/config/automation/edit/${automation.attributes.id}`
automation.attributes.id
}`
: undefined : undefined
)} )}
> >

View File

@ -6,7 +6,9 @@ export interface ThingtalkDialogParams {
} }
export const loadThingtalkDialog = () => export const loadThingtalkDialog = () =>
import(/* webpackChunkName: "thingtalk-dialog" */ "./thingtalk/dialog-thingtalk"); import(
/* webpackChunkName: "thingtalk-dialog" */ "./thingtalk/dialog-thingtalk"
);
export const showThingtalkDialog = ( export const showThingtalkDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -27,6 +27,7 @@ import { PlaceholderValues } from "./ha-thingtalk-placeholders";
import { convertThingTalk } from "../../../../data/cloud"; import { convertThingTalk } from "../../../../data/cloud";
export interface Placeholder { export interface Placeholder {
name: string;
index: number; index: number;
fields: string[]; fields: string[];
domains: string[]; domains: string[];
@ -177,8 +178,21 @@ class DialogThingtalk extends LitElement {
const placeholderValues = ev.detail.value as PlaceholderValues; const placeholderValues = ev.detail.value as PlaceholderValues;
Object.entries(placeholderValues).forEach(([type, values]) => { Object.entries(placeholderValues).forEach(([type, values]) => {
Object.entries(values).forEach(([index, placeholder]) => { Object.entries(values).forEach(([index, placeholder]) => {
Object.entries(placeholder).forEach(([field, value]) => { const devices = Object.values(placeholder);
this._config[type][index][field] = value; if (devices.length === 1) {
Object.entries(devices[0]).forEach(([field, value]) => {
this._config[type][index][field] = value;
return;
});
}
const automation = { ...this._config[type][index] };
delete this._config[type][index];
devices.forEach((fields) => {
const newAutomation = { ...automation };
Object.entries(fields).forEach(([field, value]) => {
newAutomation[field] = value;
});
this._config[type].push(newAutomation);
}); });
}); });
}); });

View File

@ -6,8 +6,11 @@ import {
customElement, customElement,
css, css,
CSSResult, CSSResult,
query, PropertyValues,
} from "lit-element"; } from "lit-element";
import "../../../../components/device/ha-area-devices-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { PolymerChangedEvent } from "../../../../polymer-types"; import { PolymerChangedEvent } from "../../../../polymer-types";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
@ -17,8 +20,15 @@ import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { subscribeEntityRegistry } from "../../../../data/entity_registry"; import { subscribeEntityRegistry } from "../../../../data/entity_registry";
import { computeDomain } from "../../../../common/entity/compute_domain"; import { computeDomain } from "../../../../common/entity/compute_domain";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { HaDevicePicker } from "../../../../components/device/ha-device-picker";
import { getPath, applyPatch } from "../../../../common/util/patch"; import { getPath, applyPatch } from "../../../../common/util/patch";
import {
subscribeAreaRegistry,
AreaRegistryEntry,
} from "../../../../data/area_registry";
import {
subscribeDeviceRegistry,
DeviceRegistryEntry,
} from "../../../../data/device_registry";
declare global { declare global {
// for fire event // for fire event
@ -28,7 +38,23 @@ declare global {
} }
export interface PlaceholderValues { export interface PlaceholderValues {
[key: string]: { [index: number]: { [key: string]: string } }; [key: string]: {
[index: number]: {
[index: number]: { device_id?: string; entity_id?: string };
};
};
}
export interface ExtraInfo {
[key: string]: {
[index: number]: {
[index: number]: {
area_id?: string;
device_ids?: string[];
manualEntity: boolean;
};
};
};
} }
interface DeviceEntitiesLookup { interface DeviceEntitiesLookup {
@ -43,9 +69,11 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
@property() public placeholders!: PlaceholderContainer; @property() public placeholders!: PlaceholderContainer;
@property() private _error?: string; @property() private _error?: string;
private _deviceEntityLookup: DeviceEntitiesLookup = {}; private _deviceEntityLookup: DeviceEntitiesLookup = {};
private _manualEntities: PlaceholderValues = {}; @property() private _extraInfo: ExtraInfo = {};
@property() private _placeholderValues: PlaceholderValues = {}; @property() private _placeholderValues: PlaceholderValues = {};
@query("#device-entity-picker") private _deviceEntityPicker?: HaDevicePicker; private _devices?: DeviceRegistryEntry[];
private _areas?: AreaRegistryEntry[];
private _search = false;
public hassSubscribe() { public hassSubscribe() {
return [ return [
@ -66,12 +94,28 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
} }
} }
}), }),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
this._searchNames();
}),
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = areas;
this._searchNames();
}),
]; ];
} }
protected updated(changedProps: PropertyValues) {
if (changedProps.has("placeholders")) {
this._search = true;
this._searchNames();
}
}
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<ha-paper-dialog <ha-paper-dialog
modal
with-backdrop with-backdrop
.opened=${this.opened} .opened=${this.opened}
@opened-changed="${this._openedChanged}" @opened-changed="${this._openedChanged}"
@ -93,55 +137,66 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
</h3> </h3>
${placeholders.map((placeholder) => { ${placeholders.map((placeholder) => {
if (placeholder.fields.includes("device_id")) { if (placeholder.fields.includes("device_id")) {
const extraInfo = getPath(this._extraInfo, [
type,
placeholder.index,
]);
return html` return html`
<ha-device-picker <ha-area-devices-picker
.type=${type} .type=${type}
.placeholder=${placeholder} .placeholder=${placeholder}
@change=${this._devicePicked} @value-changed=${this._devicePicked}
.hass=${this.hass} .hass=${this.hass}
.area=${extraInfo ? extraInfo.area_id : undefined}
.devices=${extraInfo && extraInfo.device_ids
? extraInfo.device_ids
: undefined}
.includeDomains=${placeholder.domains} .includeDomains=${placeholder.domains}
.includeDeviceClasses=${placeholder.device_classes} .includeDeviceClasses=${placeholder.device_classes}
.label=${this._getLabel( .label=${this._getLabel(
placeholder.domains, placeholder.domains,
placeholder.device_classes placeholder.device_classes
)} )}
></ha-device-picker> ></ha-area-devices-picker>
${(getPath(this._placeholderValues, [ ${extraInfo && extraInfo.manualEntity
type,
placeholder.index,
"device_id",
]) &&
placeholder.fields.includes("entity_id") &&
getPath(this._placeholderValues, [
type,
placeholder.index,
"entity_id",
]) === undefined) ||
getPath(this._manualEntities, [
type,
placeholder.index,
"manual",
]) === true
? html` ? html`
<ha-entity-picker <h3>
id="device-entity-picker" One or more devices have more than one matching
.type=${type} entity, please pick the one you want to use.
.placeholder=${placeholder} </h3>
@change=${this._entityPicked} ${Object.keys(extraInfo.manualEntity).map(
.includeDomains=${placeholder.domains} (idx) => html`
.includeDeviceClasses=${placeholder.device_classes} <ha-entity-picker
.hass=${this.hass} id="device-entity-picker"
.label=${this._getLabel( .type=${type}
placeholder.domains, .placeholder=${placeholder}
placeholder.device_classes .index=${idx}
)} @change=${this._entityPicked}
.entityFilter=${(state: HassEntity) => .includeDomains=${placeholder.domains}
this._deviceEntityLookup[ .includeDeviceClasses=${placeholder.device_classes}
this._placeholderValues[type][ .hass=${this.hass}
placeholder.index .label=${`${this._getLabel(
].device_id placeholder.domains,
].includes(state.entity_id)} placeholder.device_classes
></ha-entity-picker> )} of device ${this._getDeviceName(
getPath(this._placeholderValues, [
type,
placeholder.index,
idx,
"device_id",
])
)}`}
.entityFilter=${(state: HassEntity) => {
const devId = this._placeholderValues[type][
placeholder.index
][idx].device_id;
return this._deviceEntityLookup[
devId
].includes(state.entity_id);
}}
></ha-entity-picker>
`
)}
` `
: ""} : ""}
`; `;
@ -189,17 +244,74 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
`; `;
} }
private _getDeviceName(deviceId: string): string {
if (!this._devices) {
return "";
}
const foundDevice = this._devices.find((device) => device.id === deviceId);
if (!foundDevice) {
return "";
}
return foundDevice.name_by_user || foundDevice.name || "";
}
private _searchNames() {
if (!this._search || !this._areas || !this._devices) {
return;
}
this._search = false;
Object.entries(this.placeholders).forEach(([type, placeholders]) =>
placeholders.forEach((placeholder) => {
if (!placeholder.name) {
return;
}
const name = placeholder.name;
const foundArea = this._areas!.find((area) =>
area.name.toLowerCase().includes(name)
);
if (foundArea) {
applyPatch(
this._extraInfo,
[type, placeholder.index, "area_id"],
foundArea.area_id
);
this.requestUpdate("_extraInfo");
return;
}
const foundDevices = this._devices!.filter((device) => {
const deviceName = device.name_by_user || device.name;
if (!deviceName) {
return false;
}
return deviceName.toLowerCase().includes(name);
});
if (foundDevices.length) {
applyPatch(
this._extraInfo,
[type, placeholder.index, "device_ids"],
foundDevices.map((device) => device.id)
);
this.requestUpdate("_extraInfo");
}
})
);
}
private get _isDone(): boolean { private get _isDone(): boolean {
return Object.entries(this.placeholders).every(([type, placeholders]) => return Object.entries(this.placeholders).every(([type, placeholders]) =>
placeholders.every((placeholder) => placeholders.every((placeholder) =>
placeholder.fields.every( placeholder.fields.every((field) => {
(field) => const entries: {
getPath(this._placeholderValues, [ [key: number]: { device_id?: string; entity_id?: string };
type, } = getPath(this._placeholderValues, [type, placeholder.index]);
placeholder.index, if (!entries) {
field, return false;
]) !== undefined }
) const values = Object.values(entries);
return values.every(
(entry) => entry[field] !== undefined && entry[field] !== ""
);
})
) )
); );
} }
@ -212,76 +324,115 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
}`; }`;
} }
private _devicePicked(ev: Event): void { private _devicePicked(ev: CustomEvent): void {
const value: string[] = ev.detail.value;
if (!value) {
return;
}
const target = ev.target as any; const target = ev.target as any;
const placeholder = target.placeholder as Placeholder; const placeholder = target.placeholder as Placeholder;
const value = target.value;
const type = target.type; const type = target.type;
applyPatch(
this._placeholderValues, let oldValues = getPath(this._placeholderValues, [type, placeholder.index]);
[type, placeholder.index, "device_id"], if (oldValues) {
value oldValues = Object.values(oldValues);
);
if (!placeholder.fields.includes("entity_id")) {
return;
} }
if (value === "") { const oldExtraInfo = getPath(this._extraInfo, [type, placeholder.index]);
delete this._placeholderValues[type][placeholder.index].entity_id;
if (this._deviceEntityPicker) { if (this._placeholderValues[type]) {
this._deviceEntityPicker.value = undefined; delete this._placeholderValues[type][placeholder.index];
} }
applyPatch(
this._manualEntities, if (this._extraInfo[type]) {
[type, placeholder.index, "manual"], delete this._extraInfo[type][placeholder.index];
false }
);
if (!value.length) {
this.requestUpdate("_placeholderValues"); this.requestUpdate("_placeholderValues");
return; return;
} }
const devEntities = this._deviceEntityLookup[value];
const entities = devEntities.filter((eid) => { value.forEach((deviceId, index) => {
if (placeholder.device_classes) { let oldIndex;
const stateObj = this.hass.states[eid]; if (oldValues) {
if (!stateObj) { const oldDevice = oldValues.find((oldVal, idx) => {
return false; oldIndex = idx;
return oldVal.device_id === deviceId;
});
if (oldDevice) {
applyPatch(
this._placeholderValues,
[type, placeholder.index, index],
oldDevice
);
if (oldExtraInfo) {
applyPatch(
this._extraInfo,
[type, placeholder.index, index],
oldExtraInfo[oldIndex]
);
}
return;
} }
return (
placeholder.domains.includes(computeDomain(eid)) &&
stateObj.attributes.device_class &&
placeholder.device_classes.includes(stateObj.attributes.device_class)
);
} }
return placeholder.domains.includes(computeDomain(eid));
});
if (entities.length === 0) {
// Should not happen because we filter the device picker on domain
this._error = `No ${placeholder.domains
.map((domain) => this.hass.localize(`domain.${domain}`))
.join(", ")} entities found in this device.`;
} else if (entities.length === 1) {
applyPatch( applyPatch(
this._placeholderValues, this._placeholderValues,
[type, placeholder.index, "entity_id"], [type, placeholder.index, index, "device_id"],
entities[0] deviceId
); );
applyPatch(
this._manualEntities, if (!placeholder.fields.includes("entity_id")) {
[type, placeholder.index, "manual"], return;
false
);
this.requestUpdate("_placeholderValues");
} else {
delete this._placeholderValues[type][placeholder.index].entity_id;
if (this._deviceEntityPicker) {
this._deviceEntityPicker.value = undefined;
} }
applyPatch(
this._manualEntities, const devEntities = this._deviceEntityLookup[deviceId];
[type, placeholder.index, "manual"],
true const entities = devEntities.filter((eid) => {
); if (placeholder.device_classes) {
this.requestUpdate("_placeholderValues"); const stateObj = this.hass.states[eid];
} if (!stateObj) {
return false;
}
return (
placeholder.domains.includes(computeDomain(eid)) &&
stateObj.attributes.device_class &&
placeholder.device_classes.includes(
stateObj.attributes.device_class
)
);
}
return placeholder.domains.includes(computeDomain(eid));
});
if (entities.length === 0) {
// Should not happen because we filter the device picker on domain
this._error = `No ${placeholder.domains
.map((domain) => this.hass.localize(`domain.${domain}`))
.join(", ")} entities found in this device.`;
} else if (entities.length === 1) {
applyPatch(
this._placeholderValues,
[type, placeholder.index, index, "entity_id"],
entities[0]
);
this.requestUpdate("_placeholderValues");
} else {
delete this._placeholderValues[type][placeholder.index][index]
.entity_id;
applyPatch(
this._extraInfo,
[type, placeholder.index, "manualEntity", index],
true
);
this.requestUpdate("_placeholderValues");
}
});
fireEvent(
this.shadowRoot!.querySelector("ha-paper-dialog")! as HTMLElement,
"iron-resize"
);
} }
private _entityPicked(ev: Event): void { private _entityPicked(ev: Event): void {
@ -289,9 +440,10 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
const placeholder = target.placeholder as Placeholder; const placeholder = target.placeholder as Placeholder;
const value = target.value; const value = target.value;
const type = target.type; const type = target.type;
const index = target.index || 0;
applyPatch( applyPatch(
this._placeholderValues, this._placeholderValues,
[type, placeholder.index, "entity_id"], [type, placeholder.index, index, "entity_id"],
value value
); );
this.requestUpdate("_placeholderValues"); this.requestUpdate("_placeholderValues");

View File

@ -0,0 +1,345 @@
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
// tslint:disable-next-line
import { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
} from "lit-element";
import { dynamicContentDirective } from "../../../../common/dom/dynamic-content-directive";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card";
import { HomeAssistant } from "../../../../types";
import "./types/ha-automation-trigger-device";
import "./types/ha-automation-trigger-event";
import "./types/ha-automation-trigger-state";
import "./types/ha-automation-trigger-geo_location";
import "./types/ha-automation-trigger-homeassistant";
import "./types/ha-automation-trigger-mqtt";
import "./types/ha-automation-trigger-numeric_state";
import "./types/ha-automation-trigger-sun";
import "./types/ha-automation-trigger-template";
import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone";
import { DeviceTrigger } from "../../../../data/device_automation";
const OPTIONS = [
"device",
"event",
"state",
"geo_location",
"homeassistant",
"mqtt",
"numeric_state",
"sun",
"template",
"time",
"time_pattern",
"webhook",
"zone",
];
export interface ForDict {
hours?: number | string;
minutes?: number | string;
seconds?: number | string;
}
export interface StateTrigger {
platform: "state";
entity_id: string;
from?: string | number;
to?: string | number;
for?: string | number | ForDict;
}
export interface MqttTrigger {
platform: "mqtt";
topic: string;
payload?: string;
}
export interface GeoLocationTrigger {
platform: "geo_location";
source: "string";
zone: "string";
event: "enter" | "leave";
}
export interface HassTrigger {
platform: "homeassistant";
event: "start" | "shutdown";
}
export interface NumericStateTrigger {
platform: "numeric_state";
entity_id: string;
above?: number;
below?: number;
value_template?: string;
for?: string | number | ForDict;
}
export interface SunTrigger {
platform: "sun";
offset: number;
event: "sunrise" | "sunset";
}
export interface TimePatternTrigger {
platform: "time_pattern";
hours?: number | string;
minutes?: number | string;
seconds?: number | string;
}
export interface WebhookTrigger {
platform: "webhook";
webhook_id: string;
}
export interface ZoneTrigger {
platform: "zone";
entity_id: string;
zone: string;
event: "enter" | "leave";
}
export interface TimeTrigger {
platform: "time";
at: string;
}
export interface TemplateTrigger {
platform: "template";
value_template: string;
}
export interface EventTrigger {
platform: "event";
event_type: string;
event_data: any;
}
export type Trigger =
| StateTrigger
| MqttTrigger
| GeoLocationTrigger
| HassTrigger
| NumericStateTrigger
| SunTrigger
| TimePatternTrigger
| WebhookTrigger
| ZoneTrigger
| TimeTrigger
| TemplateTrigger
| EventTrigger
| DeviceTrigger;
export interface TriggerElement extends LitElement {
trigger: Trigger;
}
export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
ev.stopPropagation();
const name = (ev.target as any)?.name;
if (!name) {
return;
}
const newVal = ev.detail.value;
if ((element.trigger[name] || "") === newVal) {
return;
}
let newTrigger: Trigger;
if (!newVal) {
newTrigger = { ...element.trigger };
delete newTrigger[name];
} else {
newTrigger = { ...element.trigger, [name]: newVal };
}
fireEvent(element, "value-changed", { value: newTrigger });
};
@customElement("ha-automation-trigger-row")
export default class HaAutomationTriggerRow extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: Trigger;
@property() private _yamlMode = false;
protected render() {
if (!this.trigger) {
return html``;
}
const hasEditor = OPTIONS.includes(this.trigger.platform);
if (!hasEditor) {
this._yamlMode = true;
}
const selected = OPTIONS.indexOf(this.trigger.platform);
return html`
<ha-card>
<div class="card-content">
<div class="card-menu">
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<paper-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></paper-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item @click=${this._switchYamlMode}>
${this._yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate"
)}
</paper-item>
<paper-item @click=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.delete"
)}
</paper-item>
</paper-listbox>
</paper-menu-button>
</div>
${this._yamlMode
? html`
<div style="margin-right: 24px;">
${!hasEditor
? html`
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.unsupported_platform",
"platform",
this.trigger.platform
)}
`
: ""}
<ha-yaml-editor
.value=${this.trigger}
@value-changed=${this._onYamlChange}
></ha-yaml-editor>
</div>
`
: html`
<paper-dropdown-menu-light
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type_select"
)}
no-animations
>
<paper-listbox
slot="dropdown-content"
.selected=${selected}
@iron-select=${this._typeChanged}
>
${OPTIONS.map(
(opt) => html`
<paper-item .platform=${opt}>
${this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.${opt}.label`
)}
</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
<div>
${dynamicContentDirective(
`ha-automation-trigger-${this.trigger.platform}`,
{ hass: this.hass, trigger: this.trigger }
)}
</div>
`}
</div>
</ha-card>
`;
}
private _onDelete() {
if (
confirm(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.delete_confirm"
)
)
) {
fireEvent(this, "value-changed", { value: null });
}
}
private _typeChanged(ev: CustomEvent) {
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
?.platform;
if (!type) {
return;
}
const elClass = customElements.get(`ha-automation-trigger-${type}`);
if (type !== this.trigger.platform) {
fireEvent(this, "value-changed", {
value: {
platform: type,
...elClass.defaultConfig,
},
});
}
}
private _onYamlChange(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", { value: ev.detail.value });
}
private _switchYamlMode() {
this._yamlMode = !this._yamlMode;
}
static get styles(): CSSResult {
return css`
.card-menu {
position: absolute;
top: 0;
right: 0;
z-index: 3;
color: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
.card-menu paper-item {
cursor: pointer;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-row": HaAutomationTriggerRow;
}
}

View File

@ -0,0 +1,93 @@
import {
LitElement,
customElement,
html,
property,
CSSResult,
css,
} from "lit-element";
import "@material/mwc-button";
import "../../../../components/ha-card";
import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types";
import "./ha-automation-trigger-row";
@customElement("ha-automation-trigger")
export default class HaAutomationTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public triggers;
protected render() {
return html`
<div class="triggers">
${this.triggers.map(
(trg, idx) => html`
<ha-automation-trigger-row
.index=${idx}
.trigger=${trg}
@value-changed=${this._triggerChanged}
.hass=${this.hass}
></ha-automation-trigger-row>
`
)}
<ha-card>
<div class="card-actions add-card">
<mwc-button @click=${this._addTrigger}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.add"
)}
</mwc-button>
</div>
</ha-card>
</div>
`;
}
private _addTrigger() {
const triggers = this.triggers.concat({
platform: "state",
});
fireEvent(this, "value-changed", { value: triggers });
}
private _triggerChanged(ev: CustomEvent) {
ev.stopPropagation();
const triggers = [...this.triggers];
const newValue = ev.detail.value;
const index = (ev.target as any).index;
if (newValue === null) {
triggers.splice(index, 1);
} else {
triggers[index] = newValue;
}
fireEvent(this, "value-changed", { value: triggers });
}
static get styles(): CSSResult {
return css`
.triggers,
.script {
margin-top: -16px;
}
.triggers ha-card,
.script ha-card {
margin-top: 16px;
}
.add-card mwc-button {
display: block;
text-align: center;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger": HaAutomationTrigger;
}
}

View File

@ -0,0 +1,128 @@
import "../../../../../components/device/ha-device-picker";
import "../../../../../components/device/ha-device-trigger-picker";
import "../../../../../components/ha-form/ha-form";
import {
fetchDeviceTriggerCapabilities,
deviceAutomationsEqual,
DeviceTrigger,
} from "../../../../../data/device_automation";
import { LitElement, customElement, property, html } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../../types";
@customElement("ha-automation-trigger-device")
export class HaDeviceTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: DeviceTrigger;
@property() private _deviceId?: string;
@property() private _capabilities?;
private _origTrigger?: DeviceTrigger;
public static get defaultConfig() {
return {
device_id: "",
domain: "",
entity_id: "",
};
}
protected render() {
if (this._deviceId === undefined) {
this._deviceId = this.trigger.device_id;
}
const extraFieldsData =
this._capabilities && this._capabilities.extra_fields
? this._capabilities.extra_fields.map((item) => {
return { [item.name]: this.trigger[item.name] };
})
: undefined;
return html`
<ha-device-picker
.value=${this._deviceId}
@value-changed=${this._devicePicked}
.hass=${this.hass}
label="Device"
></ha-device-picker>
<ha-device-trigger-picker
.value=${this.trigger}
.deviceId=${this._deviceId}
@value-changed=${this._deviceTriggerPicked}
.hass=${this.hass}
label="Trigger"
></ha-device-trigger-picker>
${extraFieldsData
? html`
<ha-form
.data=${Object.assign({}, ...extraFieldsData)}
.schema=${this._capabilities.extra_fields}
.computeLabel=${this._extraFieldsComputeLabelCallback(
this.hass.localize
)}
@value-changed=${this._extraFieldsChanged}
></ha-form>
`
: ""}
`;
}
protected firstUpdated() {
if (!this._capabilities) {
this._getCapabilities();
}
if (this.trigger) {
this._origTrigger = this.trigger;
}
}
protected updated(changedPros) {
const prevTrigger = changedPros.get("trigger");
if (prevTrigger && !deviceAutomationsEqual(prevTrigger, this.trigger)) {
this._getCapabilities();
}
}
private async _getCapabilities() {
const trigger = this.trigger;
this._capabilities = trigger.domain
? await fetchDeviceTriggerCapabilities(this.hass, trigger)
: null;
}
private _devicePicked(ev) {
ev.stopPropagation();
this._deviceId = ev.target.value;
}
private _deviceTriggerPicked(ev) {
ev.stopPropagation();
let trigger = ev.detail.value;
if (
this._origTrigger &&
deviceAutomationsEqual(this._origTrigger, trigger)
) {
trigger = this._origTrigger;
}
fireEvent(this, "value-changed", { value: trigger });
}
private _extraFieldsChanged(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
...this.trigger,
...ev.detail.value,
},
});
}
private _extraFieldsComputeLabelCallback(localize) {
// Returns a callback for ha-form to calculate labels per schema object
return (schema) =>
localize(
`ui.panel.config.automation.editor.triggers.type.device.extra_fields.${schema.name}`
) || schema.name;
}
}

View File

@ -0,0 +1,53 @@
import "@polymer/paper-input/paper-input";
import "../../../../../components/ha-yaml-editor";
import { LitElement, property, customElement } from "lit-element";
import {
TriggerElement,
EventTrigger,
handleChangeEvent,
} from "../ha-automation-trigger-row";
import { HomeAssistant } from "../../../../../types";
import { html } from "lit-html";
@customElement("ha-automation-trigger-event")
export class HaEventTrigger extends LitElement implements TriggerElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: EventTrigger;
public static get defaultConfig() {
return { event_type: "", event_data: {} };
}
public render() {
const { event_type, event_data } = this.trigger;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.event.event_type"
)}
name="event_type"
.value="${event_type}"
@value-changed="${this._valueChanged}"
></paper-input>
<ha-yaml-editor
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.event.event_data"
)}
.name=${"event_data"}
.value=${event_data}
@value-changed=${this._valueChanged}
></ha-yaml-editor>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-event": HaEventTrigger;
}
}

View File

@ -0,0 +1,99 @@
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
// tslint:disable-next-line
import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
import "../../../../../components/entity/ha-entity-picker";
import { LitElement, customElement, property, html } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import {
GeoLocationTrigger,
handleChangeEvent,
} from "../ha-automation-trigger-row";
import { fireEvent } from "../../../../../common/dom/fire_event";
@customElement("ha-automation-trigger-geo_location")
export default class HaGeolocationTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: GeoLocationTrigger;
public static get defaultConfig() {
return {
source: "",
zone: "",
event: "enter",
};
}
protected render() {
const { source, zone, event } = this.trigger;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.geo_location.source"
)}
name="source"
.value=${source}
@value-changed="${this._valueChanged}"
></paper-input>
<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.geo_location.zone"
)}
.value=${zone}
@value-changed=${this._zonePicked}
.hass=${this.hass}
allow-custom-entity
.includeDomains=${["zone"]}
></ha-entity-picker>
<label id="eventlabel">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.geo_location.event"
)}
</label>
<paper-radio-group
.selected=${event}
aria-labelledby="eventlabel"
@paper-radio-group-changed=${this._radioGroupPicked}
>
<paper-radio-button name="enter">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.geo_location.enter"
)}
</paper-radio-button>
<paper-radio-button name="leave">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.geo_location.leave"
)}
</paper-radio-button>
</paper-radio-group>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _zonePicked(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, zone: ev.detail.value },
});
}
private _radioGroupPicked(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
...this.trigger,
event: (ev.target as PaperRadioGroupElement).selected,
},
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-geo_location": HaGeolocationTrigger;
}
}

View File

@ -0,0 +1,63 @@
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
// tslint:disable-next-line
import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
import { LitElement, html, property, customElement } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../../types";
import { HassTrigger } from "../ha-automation-trigger-row";
@customElement("ha-automation-trigger-homeassistant")
export default class HaHassTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: HassTrigger;
public static get defaultConfig() {
return {
event: "start",
};
}
public render() {
const { event } = this.trigger;
return html`
<label id="eventlabel">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.homeassistant.event"
)}
</label>
<paper-radio-group
.selected=${event}
aria-labelledby="eventlabel"
@paper-radio-group-changed="${this._radioGroupPicked}"
>
<paper-radio-button name="start">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.homeassistant.start"
)}
</paper-radio-button>
<paper-radio-button name="shutdown">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.homeassistant.shutdown"
)}
</paper-radio-button>
</paper-radio-group>
`;
}
private _radioGroupPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
...this.trigger,
event: (ev.target as PaperRadioGroupElement).selected,
},
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-homeassistant": HaHassTrigger;
}
}

View File

@ -0,0 +1,50 @@
import "@polymer/paper-input/paper-input";
import { LitElement, customElement, property, html } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import {
handleChangeEvent,
TriggerElement,
MqttTrigger,
} from "../ha-automation-trigger-row";
@customElement("ha-automation-trigger-mqtt")
export class HaMQTTTrigger extends LitElement implements TriggerElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: MqttTrigger;
public static get defaultConfig() {
return { topic: "" };
}
protected render() {
const { topic, payload } = this.trigger;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.mqtt.topic"
)}
name="topic"
.value=${topic}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.mqtt.payload"
)}
name="payload"
.value=${payload}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-mqtt": HaMQTTTrigger;
}
}

View File

@ -0,0 +1,103 @@
import "@polymer/paper-input/paper-input";
import "../../../../../components/ha-textarea";
import "../../../../../components/entity/ha-entity-picker";
import { LitElement, html, customElement, property } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import { fireEvent } from "../../../../../common/dom/fire_event";
import {
NumericStateTrigger,
ForDict,
handleChangeEvent,
} from "../ha-automation-trigger-row";
@customElement("ha-automation-trigger-numeric_state")
export default class HaNumericStateTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: NumericStateTrigger;
public static get defaultConfig() {
return {
entity_id: "",
};
}
public render() {
const { value_template, entity_id, below, above } = this.trigger;
let trgFor = this.trigger.for;
if (
trgFor &&
((trgFor as ForDict).hours ||
(trgFor as ForDict).minutes ||
(trgFor as ForDict).seconds)
) {
// If the trigger was defined using the yaml dict syntax, convert it to
// the equivalent string format
let { hours = 0, minutes = 0, seconds = 0 } = trgFor as ForDict;
hours = hours.toString();
minutes = minutes.toString().padStart(2, "0");
seconds = seconds.toString().padStart(2, "0");
trgFor = `${hours}:${minutes}:${seconds}`;
}
return html`
<ha-entity-picker
.value="${entity_id}"
@value-changed="${this._entityPicked}"
.hass="${this.hass}"
allow-custom-entity
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.numeric_state.above"
)}
name="above"
.value=${above}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.numeric_state.below"
)}
name="below"
.value=${below}
@value-changed=${this._valueChanged}
></paper-input>
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.numeric_state.value_template"
)}
name="value_template"
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.for"
)}
name="for"
.value=${trgFor}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-numeric_state": HaNumericStateTrigger;
}
}

View File

@ -0,0 +1,93 @@
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../../types";
import {
handleChangeEvent,
TriggerElement,
StateTrigger,
ForDict,
} from "../ha-automation-trigger-row";
import { PolymerChangedEvent } from "../../../../../polymer-types";
@customElement("ha-automation-trigger-state")
export class HaStateTrigger extends LitElement implements TriggerElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: StateTrigger;
public static get defaultConfig() {
return { entity_id: "" };
}
protected render() {
const { entity_id, to, from } = this.trigger;
let trgFor = this.trigger.for;
if (
trgFor &&
((trgFor as ForDict).hours ||
(trgFor as ForDict).minutes ||
(trgFor as ForDict).seconds)
) {
// If the trigger was defined using the yaml dict syntax, convert it to
// the equivalent string format
let { hours = 0, minutes = 0, seconds = 0 } = trgFor as ForDict;
hours = hours.toString();
minutes = minutes.toString().padStart(2, "0");
seconds = seconds.toString().padStart(2, "0");
trgFor = `${hours}:${minutes}:${seconds}`;
}
return html`
<ha-entity-picker
.value=${entity_id}
@value-changed=${this._entityPicked}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.from"
)}
.name=${"from"}
.value=${from}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.to"
)}
.name=${"to"}
.value=${to}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.for"
)}
.name=${"for"}
.value=${trgFor}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-state": HaStateTrigger;
}
}

View File

@ -0,0 +1,75 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
// tslint:disable-next-line
import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
import { LitElement, customElement, property, html } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import {
SunTrigger,
handleChangeEvent,
TriggerElement,
} from "../ha-automation-trigger-row";
import { fireEvent } from "../../../../../common/dom/fire_event";
@customElement("ha-automation-trigger-sun")
export class HaSunTrigger extends LitElement implements TriggerElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: SunTrigger;
public static get defaultConfig() {
return {
event: "sunrise",
};
}
protected render() {
const { offset, event } = this.trigger;
return html`
<label id="eventlabel">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.sun.event"
)}
</label>
<paper-radio-group
.selected=${event}
aria-labelledby="eventlabel"
@paper-radio-group-changed=${this._radioGroupPicked}
>
<paper-radio-button name="sunrise">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.sun.sunrise"
)}
</paper-radio-button>
<paper-radio-button name="sunset">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.sun.sunset"
)}
</paper-radio-button>
</paper-radio-group>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.sun.offset"
)}
name="offset"
.value=${offset}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _radioGroupPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
...this.trigger,
event: (ev.target as PaperRadioGroupElement).selected,
},
});
}
}

View File

@ -0,0 +1,36 @@
import "../../../../../components/ha-textarea";
import { LitElement, property, html, customElement } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import {
TemplateTrigger,
handleChangeEvent,
} from "../ha-automation-trigger-row";
@customElement("ha-automation-trigger-template")
export class HaTemplateTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: TemplateTrigger;
public static get defaultConfig() {
return { value_template: "" };
}
protected render() {
const { value_template } = this.trigger;
return html`
<ha-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.template.value_template"
)}
name="value_template"
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}

View File

@ -0,0 +1,36 @@
import "@polymer/paper-input/paper-input";
import { LitElement, html, property, customElement } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import {
TimeTrigger,
handleChangeEvent,
TriggerElement,
} from "../ha-automation-trigger-row";
@customElement("ha-automation-trigger-time")
export class HaTimeTrigger extends LitElement implements TriggerElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: TimeTrigger;
public static get defaultConfig() {
return { at: "" };
}
protected render() {
const { at } = this.trigger;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
)}
name="at"
.value=${at}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}

View File

@ -0,0 +1,58 @@
import "@polymer/paper-input/paper-input";
import { LitElement, property, html, customElement } from "lit-element";
import {
TriggerElement,
handleChangeEvent,
TimePatternTrigger,
} from "../ha-automation-trigger-row";
import { HomeAssistant } from "../../../../../types";
@customElement("ha-automation-trigger-time_pattern")
export class HaTimePatternTrigger extends LitElement implements TriggerElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: TimePatternTrigger;
public static get defaultConfig() {
return {};
}
protected render() {
const { hours, minutes, seconds } = this.trigger;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time_pattern.hours"
)}
name="hours"
.value=${hours}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time_pattern.minutes"
)}
name="minutes"
.value=${minutes}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time_pattern.seconds"
)}
name="seconds"
.value=${seconds}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-time_pattern": HaTimePatternTrigger;
}
}

View File

@ -0,0 +1,43 @@
import "@polymer/paper-input/paper-input";
import { LitElement, customElement, property, html } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import {
WebhookTrigger,
handleChangeEvent,
} from "../ha-automation-trigger-row";
@customElement("ha-automation-trigger-webhook")
export class HaWebhookTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: WebhookTrigger;
public static get defaultConfig() {
return {
webhook_id: "",
};
}
protected render() {
const { webhook_id: webhookId } = this.trigger;
return html`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.webhook.webhook_id"
)}
name="webhook_id"
.value=${webhookId}
@value-changed=${this._valueChanged}
></paper-input>
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-webhook": HaWebhookTrigger;
}
}

View File

@ -0,0 +1,108 @@
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
// tslint:disable-next-line
import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
import "../../../../../components/entity/ha-entity-picker";
import { hasLocation } from "../../../../../common/entity/has_location";
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
import { LitElement, property, html, customElement } from "lit-element";
import { HomeAssistant } from "../../../../../types";
import { ZoneTrigger } from "../ha-automation-trigger-row";
import { PolymerChangedEvent } from "../../../../../polymer-types";
import { fireEvent } from "../../../../../common/dom/fire_event";
function zoneAndLocationFilter(stateObj) {
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
}
@customElement("ha-automation-trigger-zone")
export class HaZoneTrigger extends LitElement {
@property() public hass!: HomeAssistant;
@property() public trigger!: ZoneTrigger;
public static get defaultConfig() {
return {
entity_id: "",
zone: "",
event: "enter",
};
}
protected render() {
const { entity_id, zone, event } = this.trigger;
return html`
<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.zone.entity"
)}
.value=${entity_id}
@value-changed=${this._entityPicked}
.hass=${this.hass}
allow-custom-entity
.entityFilter=${zoneAndLocationFilter}
></ha-entity-picker>
<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.zone.zone"
)}
.value=${zone}
@value-changed=${this._zonePicked}
.hass=${this.hass}
allow-custom-entity
.includeDomains=${["zone"]}
></ha-entity-picker>
<label id="eventlabel">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.zone.event"
)}
</label>
<paper-radio-group
.selected=${event}
aria-labelledby="eventlabel"
@paper-radio-group-changed=${this._radioGroupPicked}
>
<paper-radio-button name="enter">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.zone.enter"
)}
</paper-radio-button>
<paper-radio-button name="leave">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.zone.leave"
)}
</paper-radio-button>
</paper-radio-group>
`;
}
private _entityPicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
private _zonePicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, zone: ev.detail.value },
});
}
private _radioGroupPicked(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
...this.trigger,
event: (ev.target as PaperRadioGroupElement).selected,
},
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-trigger-zone": HaZoneTrigger;
}
}

View File

@ -12,7 +12,9 @@ export const showCloudCertificateDialog = (
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-cloud-certificate", dialogTag: "dialog-cloud-certificate",
dialogImport: () => dialogImport: () =>
import(/* webpackChunkName: "dialog-cloud-certificate" */ "./dialog-cloud-certificate"), import(
/* webpackChunkName: "dialog-cloud-certificate" */ "./dialog-cloud-certificate"
),
dialogParams: webhookDialogParams, dialogParams: webhookDialogParams,
}); });
}; };

View File

@ -15,7 +15,9 @@ export const showManageCloudhookDialog = (
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-manage-cloudhook", dialogTag: "dialog-manage-cloudhook",
dialogImport: () => dialogImport: () =>
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./dialog-manage-cloudhook"), import(
/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./dialog-manage-cloudhook"
),
dialogParams: webhookDialogParams, dialogParams: webhookDialogParams,
}); });
}; };

View File

@ -46,12 +46,16 @@ class HaConfigCloud extends HassRouterPage {
register: { register: {
tag: "cloud-register", tag: "cloud-register",
load: () => load: () =>
import(/* webpackChunkName: "cloud-register" */ "./register/cloud-register"), import(
/* webpackChunkName: "cloud-register" */ "./register/cloud-register"
),
}, },
"forgot-password": { "forgot-password": {
tag: "cloud-forgot-password", tag: "cloud-forgot-password",
load: () => load: () =>
import(/* webpackChunkName: "cloud-forgot-password" */ "./forgot-password/cloud-forgot-password"), import(
/* webpackChunkName: "cloud-forgot-password" */ "./forgot-password/cloud-forgot-password"
),
}, },
account: { account: {
tag: "cloud-account", tag: "cloud-account",
@ -59,7 +63,9 @@ class HaConfigCloud extends HassRouterPage {
"google-assistant": { "google-assistant": {
tag: "cloud-google-assistant", tag: "cloud-google-assistant",
load: () => load: () =>
import(/* webpackChunkName: "cloud-google-assistant" */ "./google-assistant/cloud-google-assistant"), import(
/* webpackChunkName: "cloud-google-assistant" */ "./google-assistant/cloud-google-assistant"
),
}, },
alexa: { alexa: {
tag: "cloud-alexa", tag: "cloud-alexa",

View File

@ -138,6 +138,9 @@ class HaConfigDashboard extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
ha-config-navigation:last-child {
margin-bottom: 24px;
}
ha-card { ha-card {
overflow: hidden; overflow: hidden;
} }
@ -148,6 +151,7 @@ class HaConfigDashboard extends LitElement {
.promo-advanced { .promo-advanced {
text-align: center; text-align: center;
color: var(--secondary-text-color); color: var(--secondary-text-color);
margin-bottom: 24px;
} }
.promo-advanced a { .promo-advanced a {
color: var(--secondary-text-color); color: var(--secondary-text-color);

View File

@ -6,7 +6,9 @@ export interface EntityRegistryDetailDialogParams {
} }
export const loadEntityRegistryDetailDialog = () => export const loadEntityRegistryDetailDialog = () =>
import(/* webpackChunkName: "entity-registry-detail-dialog" */ "./dialog-entity-registry-detail"); import(
/* webpackChunkName: "entity-registry-detail-dialog" */ "./dialog-entity-registry-detail"
);
export const showEntityRegistryDetailDialog = ( export const showEntityRegistryDetailDialog = (
element: HTMLElement, element: HTMLElement,

View File

@ -31,82 +31,114 @@ class HaPanelConfig extends HassRouterPage {
area_registry: { area_registry: {
tag: "ha-config-area-registry", tag: "ha-config-area-registry",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-area-registry" */ "./area_registry/ha-config-area-registry"), import(
/* webpackChunkName: "panel-config-area-registry" */ "./area_registry/ha-config-area-registry"
),
}, },
automation: { automation: {
tag: "ha-config-automation", tag: "ha-config-automation",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"), import(
/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"
),
}, },
cloud: { cloud: {
tag: "ha-config-cloud", tag: "ha-config-cloud",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"), import(
/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"
),
}, },
core: { core: {
tag: "ha-config-core", tag: "ha-config-core",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"), import(
/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"
),
}, },
devices: { devices: {
tag: "ha-config-devices", tag: "ha-config-devices",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"), import(
/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"
),
}, },
server_control: { server_control: {
tag: "ha-config-server-control", tag: "ha-config-server-control",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"), import(
/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"
),
}, },
customize: { customize: {
tag: "ha-config-customize", tag: "ha-config-customize",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"), import(
/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"
),
}, },
dashboard: { dashboard: {
tag: "ha-config-dashboard", tag: "ha-config-dashboard",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"), import(
/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"
),
}, },
entity_registry: { entity_registry: {
tag: "ha-config-entity-registry", tag: "ha-config-entity-registry",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-entity-registry" */ "./entity_registry/ha-config-entity-registry"), import(
/* webpackChunkName: "panel-config-entity-registry" */ "./entity_registry/ha-config-entity-registry"
),
}, },
integrations: { integrations: {
tag: "ha-config-integrations", tag: "ha-config-integrations",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"), import(
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
),
}, },
person: { person: {
tag: "ha-config-person", tag: "ha-config-person",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"), import(
/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"
),
}, },
script: { script: {
tag: "ha-config-script", tag: "ha-config-script",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"), import(
/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"
),
}, },
scene: { scene: {
tag: "ha-config-scene", tag: "ha-config-scene",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"), import(
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
),
}, },
users: { users: {
tag: "ha-config-users", tag: "ha-config-users",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"), import(
/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"
),
}, },
zha: { zha: {
tag: "zha-config-panel", tag: "zha-config-panel",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"), import(
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"
),
}, },
zwave: { zwave: {
tag: "ha-config-zwave", tag: "ha-config-zwave",
load: () => load: () =>
import(/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"), import(
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
),
}, },
}, },
}; };

View File

@ -133,7 +133,11 @@ export class HaConfigManagerDashboard extends LitElement {
)} )}
</div> </div>
</paper-item-body> </paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next
aria-label=${this.hass.localize(
"ui.panel.config.integrations.details"
)}
></ha-icon-next>
</paper-item> </paper-item>
</a> </a>
` `
@ -154,6 +158,7 @@ export class HaConfigManagerDashboard extends LitElement {
<ha-fab <ha-fab
icon="hass:plus" icon="hass:plus"
aria-label=${this.hass.localize("ui.panel.config.integrations.new")}
title=${this.hass.localize("ui.panel.config.integrations.new")} title=${this.hass.localize("ui.panel.config.integrations.new")}
@click=${this._createFlow} @click=${this._createFlow}
?rtl=${computeRTL(this.hass!)} ?rtl=${computeRTL(this.hass!)}

View File

@ -0,0 +1,23 @@
import { h, Component, ComponentChild } from "preact";
export class AutomationComponent<P = {}, S = {}> extends Component<P, S> {
// @ts-ignore
protected initialized: boolean;
constructor(props?, context?) {
super(props, context);
this.initialized = false;
}
public componentDidMount() {
this.initialized = true;
}
public componentWillUnmount() {
this.initialized = false;
}
public render(_props?, _state?, _context?: any): ComponentChild {
return <div />;
}
}

View File

@ -5,7 +5,8 @@ import "../ha-config-section";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-textarea"; import "../../../components/ha-textarea";
import Trigger from "./trigger/index"; import "../automation/trigger/ha-automation-trigger";
import Condition from "./condition/index"; import Condition from "./condition/index";
import Script from "./script/index"; import Script from "./script/index";
@ -26,8 +27,8 @@ export default class Automation extends Component<any> {
}); });
} }
public triggerChanged(trigger) { public triggerChanged(ev: CustomEvent) {
this.props.onChange({ ...this.props.automation, trigger }); this.props.onChange({ ...this.props.automation, trigger: ev.detail.value });
} }
public conditionChanged(condition) { public conditionChanged(condition) {
@ -90,11 +91,10 @@ export default class Automation extends Component<any> {
)} )}
</a> </a>
</span> </span>
<Trigger <ha-automation-trigger
trigger={trigger} triggers={trigger}
onChange={this.triggerChanged} onvalue-changed={this.triggerChanged}
hass={hass} hass={hass}
localize={localize}
/> />
</ha-config-section> </ha-config-section>

View File

@ -3,6 +3,8 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import YAMLTextArea from "../yaml_textarea";
import DeviceCondition from "./device"; import DeviceCondition from "./device";
import LogicalCondition from "./logical"; import LogicalCondition from "./logical";
import NumericStateCondition from "./numeric_state"; import NumericStateCondition from "./numeric_state";
@ -31,6 +33,7 @@ export default class ConditionEdit extends Component<any> {
super(); super();
this.typeChanged = this.typeChanged.bind(this); this.typeChanged = this.typeChanged.bind(this);
this.onYamlChange = this.onYamlChange.bind(this);
} }
public typeChanged(ev) { public typeChanged(ev) {
@ -44,20 +47,24 @@ export default class ConditionEdit extends Component<any> {
} }
} }
public render({ index, condition, onChange, hass, localize }) { public render({ index, condition, onChange, hass, localize, yamlMode }) {
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
const Comp = TYPES[condition.condition]; const Comp = TYPES[condition.condition];
const selected = OPTIONS.indexOf(condition.condition); const selected = OPTIONS.indexOf(condition.condition);
if (!Comp) { if (yamlMode || !Comp) {
return ( return (
<div> <div style="margin-right: 24px;">
{localize( {!Comp && (
"ui.panel.config.automation.editor.conditions.unsupported_condition", <div>
"condition", {localize(
condition.condition "ui.panel.config.automation.editor.conditions.unsupported_condition",
"condition",
condition.condition
)}
</div>
)} )}
<pre>{JSON.stringify(condition, null, 2)}</pre> <YAMLTextArea value={condition} onChange={this.onYamlChange} />
</div> </div>
); );
} }
@ -94,4 +101,8 @@ export default class ConditionEdit extends Component<any> {
</div> </div>
); );
} }
private onYamlChange(condition) {
this.props.onChange(this.props.index, condition);
}
} }

View File

@ -8,10 +8,16 @@ import "../../../../components/ha-card";
import ConditionEdit from "./condition_edit"; import ConditionEdit from "./condition_edit";
export default class ConditionRow extends Component<any> { export default class ConditionRow extends Component<any> {
public state: { yamlMode: boolean };
constructor() { constructor() {
super(); super();
this.state = {
yamlMode: false,
};
this.onDelete = this.onDelete.bind(this); this.onDelete = this.onDelete.bind(this);
this.switchYamlMode = this.switchYamlMode.bind(this);
} }
public onDelete() { public onDelete() {
@ -27,22 +33,32 @@ export default class ConditionRow extends Component<any> {
} }
} }
public render(props) { public render(props, { yamlMode }) {
return ( return (
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
<div class="card-menu"> <div class="card-menu" style="z-index: 3">
<paper-menu-button <paper-menu-button
no-animations no-animations
horizontal-align="right" horizontal-align="right"
horizontal-offset="-5" horizontal-offset="-5"
vertical-offset="-5" vertical-offset="-5"
close-on-activate
> >
<paper-icon-button <paper-icon-button
icon="hass:dots-vertical" icon="hass:dots-vertical"
slot="dropdown-trigger" slot="dropdown-trigger"
/> />
<paper-listbox slot="dropdown-content"> <paper-listbox slot="dropdown-content">
<paper-item onTap={this.switchYamlMode}>
{yamlMode
? props.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: props.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</paper-item>
<paper-item disabled> <paper-item disabled>
{props.localize( {props.localize(
"ui.panel.config.automation.editor.conditions.duplicate" "ui.panel.config.automation.editor.conditions.duplicate"
@ -56,9 +72,15 @@ export default class ConditionRow extends Component<any> {
</paper-listbox> </paper-listbox>
</paper-menu-button> </paper-menu-button>
</div> </div>
<ConditionEdit {...props} /> <ConditionEdit {...props} yamlMode={yamlMode} />
</div> </div>
</ha-card> </ha-card>
); );
} }
private switchYamlMode() {
this.setState({
yamlMode: !this.state.yamlMode,
});
}
} }

View File

@ -1,4 +1,4 @@
import { h, Component } from "preact"; import { h } from "preact";
import "../../../../components/device/ha-device-picker"; import "../../../../components/device/ha-device-picker";
import "../../../../components/device/ha-device-condition-picker"; import "../../../../components/device/ha-device-condition-picker";
@ -8,7 +8,10 @@ import {
fetchDeviceConditionCapabilities, fetchDeviceConditionCapabilities,
deviceAutomationsEqual, deviceAutomationsEqual,
} from "../../../../data/device_automation"; } from "../../../../data/device_automation";
export default class DeviceCondition extends Component<any, any> {
import { AutomationComponent } from "../automation-component";
export default class DeviceCondition extends AutomationComponent<any, any> {
private _origCondition; private _origCondition;
constructor() { constructor() {
@ -20,10 +23,16 @@ export default class DeviceCondition extends Component<any, any> {
} }
public devicePicked(ev) { public devicePicked(ev) {
if (!this.initialized) {
return;
}
this.setState({ ...this.state, device_id: ev.target.value }); this.setState({ ...this.state, device_id: ev.target.value });
} }
public deviceConditionPicked(ev) { public deviceConditionPicked(ev) {
if (!this.initialized) {
return;
}
let condition = ev.target.value; let condition = ev.target.value;
if ( if (
this._origCondition && this._origCondition &&
@ -74,6 +83,7 @@ export default class DeviceCondition extends Component<any, any> {
} }
public componentDidMount() { public componentDidMount() {
this.initialized = true;
if (!this.state.capabilities) { if (!this.state.capabilities) {
this._getCapabilities(); this._getCapabilities();
} }
@ -98,6 +108,9 @@ export default class DeviceCondition extends Component<any, any> {
} }
private _extraFieldsChanged(ev) { private _extraFieldsChanged(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.condition, ...this.props.condition,
...ev.detail.value, ...ev.detail.value,
@ -108,9 +121,7 @@ export default class DeviceCondition extends Component<any, any> {
// Returns a callback for ha-form to calculate labels per schema object // Returns a callback for ha-form to calculate labels per schema object
return (schema) => return (schema) =>
localize( localize(
`ui.panel.config.automation.editor.condition.type.device.extra_fields.${ `ui.panel.config.automation.editor.condition.type.device.extra_fields.${schema.name}`
schema.name
}`
) || schema.name; ) || schema.name;
} }
} }

View File

@ -1,29 +1,22 @@
import { h, Component } from "preact"; import { h } from "preact";
import Condition from "./index"; import Condition from "./index";
import { AutomationComponent } from "../automation-component";
export default class LogicalCondition extends Component<any, any> { export default class LogicalCondition extends AutomationComponent<any> {
private _mounted = false;
constructor() { constructor() {
super(); super();
this.conditionChanged = this.conditionChanged.bind(this); this.conditionChanged = this.conditionChanged.bind(this);
} }
public conditionChanged(conditions) { public conditionChanged(conditions) {
if (this._mounted) { if (!this.initialized) {
this.props.onChange(this.props.index, { return;
...this.props.condition,
conditions,
});
} }
} this.props.onChange(this.props.index, {
...this.props.condition,
public componentWillMount() { conditions,
this._mounted = true; });
}
public componentWillUnmount() {
this._mounted = false;
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */

View File

@ -1,11 +1,12 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "../../../../components/ha-textarea"; import "../../../../components/ha-textarea";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class NumericStateCondition extends Component<any> { export default class NumericStateCondition extends AutomationComponent<any> {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -15,6 +16,9 @@ export default class NumericStateCondition extends Component<any> {
} }
public entityPicked(ev) { public entityPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.condition, ...this.props.condition,
entity_id: ev.target.value, entity_id: ev.target.value,

View File

@ -1,10 +1,11 @@
import { h, Component } from "preact"; import { h } from "preact";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { onChangeEvent } from "../../../../common/preact/event"; import { onChangeEvent } from "../../../../common/preact/event";
import { AutomationComponent } from "../automation-component";
export default class StateCondition extends Component<any> { export default class StateCondition extends AutomationComponent<any> {
private onChange: (obj: any) => void; private onChange: (obj: any) => void;
constructor() { constructor() {
super(); super();
@ -14,6 +15,9 @@ export default class StateCondition extends Component<any> {
} }
public entityPicked(ev) { public entityPicked(ev) {
if (!this.initialized) {
return;
}
this.props.onChange(this.props.index, { this.props.onChange(this.props.index, {
...this.props.condition, ...this.props.condition,
entity_id: ev.target.value, entity_id: ev.target.value,

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