mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
commit
d7e7798a55
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -41,7 +41,32 @@ Provide details about what browser (and version) you are seeing the issue in. An
|
||||
**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):**
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||
|
||||
[](https://home-assistant.io/demo/)
|
||||
[](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)
|
||||
- [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
|
||||
|
||||
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.
|
||||
|
@ -33,6 +33,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
|
||||
pragma: "h",
|
||||
},
|
||||
],
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
[
|
||||
require("@babel/plugin-proposal-decorators").default,
|
||||
{ decoratorsBeforeExport: true },
|
||||
|
@ -175,9 +175,9 @@ export class HcMain extends HassElement {
|
||||
} catch (err) {
|
||||
// Generate a Lovelace config.
|
||||
this._unsubLovelace = () => undefined;
|
||||
const {
|
||||
generateLovelaceConfigFromHass,
|
||||
} = await import("../../../../src/panels/lovelace/common/generate-lovelace-config");
|
||||
const { generateLovelaceConfigFromHass } = await import(
|
||||
"../../../../src/panels/lovelace/common/generate-lovelace-config"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceConfigFromHass(this.hass!)
|
||||
);
|
||||
|
@ -53,7 +53,7 @@ class CardModder extends LitElement {
|
||||
for (var k in this._config.style) {
|
||||
if (window.cardTools.hasTemplate(this._config.style[k]))
|
||||
this.templated.push(k);
|
||||
this.card.style.setProperty(k, '');
|
||||
this.card.style.setProperty(k, "");
|
||||
target.style.setProperty(
|
||||
k,
|
||||
window.cardTools.parseTemplate(this._config.style[k])
|
||||
|
@ -12,5 +12,7 @@ import "./resources/hademo-icons";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
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);
|
||||
|
@ -65,74 +65,79 @@ const generateHistory = (state, deltas) => {
|
||||
const incrementalUnits = ["clients", "queries", "ads"];
|
||||
|
||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
mockHass.mockAPI(new RegExp("history/period/.+"), (
|
||||
hass,
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
// @ts-ignore
|
||||
parameters
|
||||
) => {
|
||||
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
|
||||
const entities = params.filter_entity_id.split(",");
|
||||
mockHass.mockAPI(
|
||||
new RegExp("history/period/.+"),
|
||||
(
|
||||
hass,
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
// @ts-ignore
|
||||
parameters
|
||||
) => {
|
||||
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) {
|
||||
const state = hass.states[entityId];
|
||||
for (const entityId of entities) {
|
||||
const state = hass.states[entityId];
|
||||
|
||||
if (!state) {
|
||||
continue;
|
||||
}
|
||||
if (!state) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!state.attributes.unit_of_measurement) {
|
||||
results.push(generateHistory(state, [state.state]));
|
||||
continue;
|
||||
}
|
||||
if (!state.attributes.unit_of_measurement) {
|
||||
results.push(generateHistory(state, [state.state]));
|
||||
continue;
|
||||
}
|
||||
|
||||
const numberState = Number(state.state);
|
||||
const numberState = Number(state.state);
|
||||
|
||||
if (isNaN(numberState)) {
|
||||
// tslint:disable-next-line
|
||||
console.log(
|
||||
"Ignoring state with unparsable state but with a unit",
|
||||
entityId,
|
||||
state
|
||||
if (isNaN(numberState)) {
|
||||
// tslint:disable-next-line
|
||||
console.log(
|
||||
"Ignoring state with unparsable state but with a unit",
|
||||
entityId,
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return results;
|
||||
});
|
||||
);
|
||||
};
|
||||
|
@ -12,9 +12,10 @@ export const mockLovelace = (
|
||||
localizePromise: Promise<LocalizeFunc>
|
||||
) => {
|
||||
hass.mockWS("lovelace/config", () =>
|
||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||
([config, localize]) => config.lovelace(localize)
|
||||
)
|
||||
Promise.all([
|
||||
selectedDemoConfig,
|
||||
localizePromise,
|
||||
]).then(([config, localize]) => config.lovelace(localize))
|
||||
);
|
||||
|
||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||
|
@ -44,9 +44,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
selected="{{selectedInput}}"
|
||||
>
|
||||
<template is="dom-repeat" items="[[inputDevices]]">
|
||||
<paper-item device\$="[[item.device]]"
|
||||
>[[item.name]]</paper-item
|
||||
>
|
||||
<paper-item device$="[[item.device]]">[[item.name]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
@ -57,9 +55,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
selected="{{selectedOutput}}"
|
||||
>
|
||||
<template is="dom-repeat" items="[[outputDevices]]">
|
||||
<paper-item device\$="[[item.device]]"
|
||||
>[[item.name]]</paper-item
|
||||
>
|
||||
<paper-item device$="[[item.device]]">[[item.name]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
|
@ -569,7 +569,10 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
openChangelog() {
|
||||
this.hass
|
||||
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
|
||||
.then((resp) => resp, () => "Error getting changelog")
|
||||
.then(
|
||||
(resp) => resp,
|
||||
() => "Error getting changelog"
|
||||
)
|
||||
.then((content) => {
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Changelog",
|
||||
|
@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement {
|
||||
this.supervisorInfo.version,
|
||||
this.supervisorInfo.last_version,
|
||||
"hassio/supervisor/update",
|
||||
`https://github.com//home-assistant/hassio/releases/tag/${
|
||||
this.supervisorInfo.last_version
|
||||
}`
|
||||
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisorInfo.last_version}`
|
||||
)}
|
||||
${this.hassOsInfo
|
||||
? this._renderUpdateCard(
|
||||
@ -84,9 +82,7 @@ export class HassioUpdate extends LitElement {
|
||||
this.hassOsInfo.version,
|
||||
this.hassOsInfo.version_latest,
|
||||
"hassio/hassos/update",
|
||||
`https://github.com//home-assistant/hassos/releases/tag/${
|
||||
this.hassOsInfo.version_latest
|
||||
}`
|
||||
`https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
|
||||
)
|
||||
: ""}
|
||||
</div>
|
||||
|
@ -12,7 +12,9 @@ export const showHassioMarkdownDialog = (
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-markdown",
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"),
|
||||
import(
|
||||
/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"
|
||||
),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
|
@ -12,7 +12,9 @@ export const showHassioSnapshotDialog = (
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-snapshot",
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"),
|
||||
import(
|
||||
/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"
|
||||
),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
|
@ -56,12 +56,16 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
addon: {
|
||||
tag: "hassio-addon-view",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"),
|
||||
import(
|
||||
/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"
|
||||
),
|
||||
},
|
||||
ingress: {
|
||||
tag: "hassio-ingress-view",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"),
|
||||
import(
|
||||
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
46
package.json
46
package.json
@ -19,13 +19,14 @@
|
||||
"dependencies": {
|
||||
"@material/chips": "^3.2.0",
|
||||
"@material/data-table": "^3.2.0",
|
||||
"@material/mwc-base": "^0.8.0",
|
||||
"@material/mwc-button": "^0.8.0",
|
||||
"@material/mwc-checkbox": "^0.8.0",
|
||||
"@material/mwc-fab": "^0.8.0",
|
||||
"@material/mwc-ripple": "^0.8.0",
|
||||
"@material/mwc-switch": "^0.8.0",
|
||||
"@mdi/svg": "4.5.95",
|
||||
"@material/mwc-base": "^0.10.0",
|
||||
"@material/mwc-button": "^0.10.0",
|
||||
"@material/mwc-checkbox": "^0.10.0",
|
||||
"@material/mwc-dialog": "^0.10.0",
|
||||
"@material/mwc-fab": "^0.10.0",
|
||||
"@material/mwc-ripple": "^0.10.0",
|
||||
"@material/mwc-switch": "^0.10.0",
|
||||
"@mdi/svg": "4.6.95",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-localize-behavior": "^3.0.1",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
@ -68,8 +69,8 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@thomasloven/round-slider": "0.3.7",
|
||||
"@vaadin/vaadin-combo-box": "^4.2.8",
|
||||
"@vaadin/vaadin-date-picker": "^3.3.3",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.6",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.3",
|
||||
"@webcomponents/shadycss": "^1.9.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "~2.8.0",
|
||||
@ -98,21 +99,23 @@
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"superstruct": "^0.6.1",
|
||||
"copy-to-clipboard": "^1.0.9",
|
||||
"tslib": "^1.10.0",
|
||||
"unfetch": "^4.1.0",
|
||||
"web-animations-js": "^2.3.1",
|
||||
"xss": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.0",
|
||||
"@babel/plugin-external-helpers": "^7.2.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.4.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.4.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.3.0",
|
||||
"@babel/preset-env": "^7.4.2",
|
||||
"@babel/preset-typescript": "^7.4.0",
|
||||
"@babel/core": "^7.7.4",
|
||||
"@babel/plugin-external-helpers": "^7.7.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.7.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
|
||||
"@babel/plugin-transform-react-jsx": "^7.7.4",
|
||||
"@babel/preset-env": "^7.7.4",
|
||||
"@babel/preset-typescript": "^7.7.4",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||
"@types/chromecast-caf-sender": "^1.0.1",
|
||||
@ -154,18 +157,18 @@
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^6.0.2",
|
||||
"parse5": "^5.1.0",
|
||||
"prettier": "^1.16.4",
|
||||
"prettier": "^1.19.1",
|
||||
"raw-loader": "^2.0.0",
|
||||
"reify": "^0.18.1",
|
||||
"require-dir": "^1.2.0",
|
||||
"sinon": "^7.3.1",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-mocha": "^6.0.0",
|
||||
"tslint": "^5.14.0",
|
||||
"tslint": "^5.20.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-eslint-rules": "^5.4.0",
|
||||
"tslint-plugin-prettier": "^2.0.1",
|
||||
"typescript": "^3.6.3",
|
||||
"typescript": "^3.7.2",
|
||||
"web-component-tester": "^6.9.2",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-cli": "^3.3.9",
|
||||
@ -178,7 +181,6 @@
|
||||
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
|
||||
"resolutions": {
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@vaadin/vaadin-lumo-styles": "^1.4.2",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "^1.1.2"
|
||||
},
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20191119.6",
|
||||
version="20191204.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -98,9 +98,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
<ha-markdown
|
||||
allowsvg
|
||||
.content=${this.localize(
|
||||
`ui.panel.page-authorize.form.providers.${
|
||||
step.handler[0]
|
||||
}.abort.${step.reason}`
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`
|
||||
)}
|
||||
></ha-markdown>
|
||||
`;
|
||||
@ -229,9 +227,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _computeStepDescription(step: DataEntryFlowStepForm) {
|
||||
const resourceKey = `ui.panel.page-authorize.form.providers.${
|
||||
step.handler[0]
|
||||
}.step.${step.step_id}.description`;
|
||||
const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`;
|
||||
const args: string[] = [];
|
||||
const placeholders = step.description_placeholders || {};
|
||||
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
|
||||
return (schema) =>
|
||||
this.localize(
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${
|
||||
step.step_id
|
||||
}.data.${schema.name}`
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${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
|
||||
return (error) =>
|
||||
this.localize(
|
||||
`ui.panel.page-authorize.form.providers.${
|
||||
step.handler[0]
|
||||
}.error.${error}`
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.error.${error}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,9 @@ import "./ha-auth-flow";
|
||||
import { AuthProvider, fetchAuthProviders } from "../data/auth";
|
||||
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 {
|
||||
client_id?: string;
|
||||
|
@ -10,11 +10,11 @@ function toLocaleDateStringSupportsOptions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export default (toLocaleDateStringSupportsOptions()
|
||||
export default toLocaleDateStringSupportsOptions()
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleDateString(locales, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "mediumDate"));
|
||||
: (dateObj: Date) => fecha.format(dateObj, "mediumDate");
|
||||
|
@ -10,7 +10,7 @@ function toLocaleStringSupportsOptions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export default (toLocaleStringSupportsOptions()
|
||||
export default toLocaleStringSupportsOptions()
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleString(locales, {
|
||||
year: "numeric",
|
||||
@ -19,4 +19,4 @@ export default (toLocaleStringSupportsOptions()
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "haDateTime"));
|
||||
: (dateObj: Date) => fecha.format(dateObj, "haDateTime");
|
||||
|
@ -10,10 +10,10 @@ function toLocaleTimeStringSupportsOptions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export default (toLocaleTimeStringSupportsOptions()
|
||||
export default toLocaleTimeStringSupportsOptions()
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleTimeString(locales, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "shortTime"));
|
||||
: (dateObj: Date) => fecha.format(dateObj, "shortTime");
|
||||
|
@ -60,7 +60,7 @@ export const applyThemesOnElement = (
|
||||
element.updateStyles(styles);
|
||||
} else if (window.ShadyCSS) {
|
||||
// implement updateStyles() method of Polymer elements
|
||||
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ (element), styles);
|
||||
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
|
||||
}
|
||||
|
||||
if (!updateMeta) {
|
||||
|
29
src/common/dom/dynamic-content-directive.ts
Normal file
29
src/common/dom/dynamic-content-directive.ts
Normal 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);
|
||||
}
|
||||
);
|
@ -11,7 +11,9 @@ export const setupLeafletMap = async (
|
||||
throw new Error("Cannot setup Leaflet map on disconnected element");
|
||||
}
|
||||
// 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/";
|
||||
|
||||
const map = Leaflet.map(mapElement);
|
||||
|
@ -7,8 +7,11 @@
|
||||
|
||||
// export function onChangeEvent(this: OnChangeComponent, 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]) {
|
||||
return;
|
||||
}
|
||||
|
@ -6,8 +6,9 @@ import {
|
||||
MDCDataTableFoundation,
|
||||
} from "@material/data-table";
|
||||
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
import {
|
||||
BaseElement,
|
||||
html,
|
||||
query,
|
||||
queryAll,
|
||||
@ -15,10 +16,11 @@ import {
|
||||
css,
|
||||
customElement,
|
||||
property,
|
||||
classMap,
|
||||
TemplateResult,
|
||||
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
|
||||
// @ts-ignore
|
||||
|
412
src/components/device/ha-area-devices-picker.ts
Normal file
412
src/components/device/ha-area-devices-picker.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -176,6 +176,7 @@ export abstract class HaDeviceAutomationPicker<
|
||||
this.value = automation;
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "change");
|
||||
fireEvent(this, "value-changed", { value: automation });
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
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 memoizeOne from "memoize-one";
|
||||
import {
|
||||
|
127
src/components/device/ha-devices-picker.ts
Normal file
127
src/components/device/ha-devices-picker.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -215,7 +215,9 @@ class HaChartBase extends mixinBehaviors(
|
||||
}
|
||||
|
||||
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) => {
|
||||
this.ChartClass = ChartModule.default;
|
||||
|
@ -2,7 +2,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
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 "./state-badge";
|
||||
|
@ -122,8 +122,9 @@ class HaCameraStream extends LitElement {
|
||||
|
||||
private async _startHls(): Promise<void> {
|
||||
// tslint:disable-next-line
|
||||
const Hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
|
||||
.default as HLSModule;
|
||||
const Hls = ((await import(
|
||||
/* webpackChunkName: "hls.js" */ "hls.js"
|
||||
)) as any).default as HLSModule;
|
||||
let hlsSupported = Hls.isSupported();
|
||||
const videoEl = this._videoEl;
|
||||
|
||||
|
@ -72,9 +72,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
|
||||
computeCurrentStatus(hass, stateObj) {
|
||||
if (!hass || !stateObj) return null;
|
||||
if (stateObj.attributes.current_temperature != null) {
|
||||
return `${stateObj.attributes.current_temperature} ${
|
||||
hass.config.unit_system.temperature
|
||||
}`;
|
||||
return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`;
|
||||
}
|
||||
if (stateObj.attributes.current_humidity != null) {
|
||||
return `${stateObj.attributes.current_humidity} %`;
|
||||
@ -89,22 +87,16 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
|
||||
stateObj.attributes.target_temp_low != null &&
|
||||
stateObj.attributes.target_temp_high != null
|
||||
) {
|
||||
return `${stateObj.attributes.target_temp_low}-${
|
||||
stateObj.attributes.target_temp_high
|
||||
} ${hass.config.unit_system.temperature}`;
|
||||
return `${stateObj.attributes.target_temp_low}-${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
|
||||
}
|
||||
if (stateObj.attributes.temperature != null) {
|
||||
return `${stateObj.attributes.temperature} ${
|
||||
hass.config.unit_system.temperature
|
||||
}`;
|
||||
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
|
||||
}
|
||||
if (
|
||||
stateObj.attributes.target_humidity_low != null &&
|
||||
stateObj.attributes.target_humidity_high != null
|
||||
) {
|
||||
return `${stateObj.attributes.target_humidity_low}-${
|
||||
stateObj.attributes.target_humidity_high
|
||||
}%`;
|
||||
return `${stateObj.attributes.target_humidity_low}-${stateObj.attributes.target_humidity_high}%`;
|
||||
}
|
||||
if (stateObj.attributes.humidity != null) {
|
||||
return `${stateObj.attributes.humidity} %`;
|
||||
@ -121,9 +113,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
|
||||
const stateString = localize(`state.climate.${stateObj.state}`);
|
||||
return stateObj.attributes.hvac_action
|
||||
? `${localize(
|
||||
`state_attributes.climate.hvac_action.${
|
||||
stateObj.attributes.hvac_action
|
||||
}`
|
||||
`state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
|
||||
)} (${stateString})`
|
||||
: stateString;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
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";
|
||||
|
||||
|
@ -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 "@material/mwc-fab";
|
||||
|
@ -58,14 +58,10 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) {
|
||||
stateObj.attributes.target_temp_low != null &&
|
||||
stateObj.attributes.target_temp_high != null
|
||||
) {
|
||||
return `${stateObj.attributes.target_temp_low} - ${
|
||||
stateObj.attributes.target_temp_high
|
||||
} ${hass.config.unit_system.temperature}`;
|
||||
return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
|
||||
}
|
||||
if (stateObj.attributes.temperature != null) {
|
||||
return `${stateObj.attributes.temperature} ${
|
||||
hass.config.unit_system.temperature
|
||||
}`;
|
||||
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
|
81
src/components/ha-yaml-editor.ts
Normal file
81
src/components/ha-yaml-editor.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -19,9 +19,7 @@ export interface Stream {
|
||||
}
|
||||
|
||||
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
|
||||
`/api/camera_proxy_stream/${entity.entity_id}?token=${
|
||||
entity.attributes.access_token
|
||||
}`;
|
||||
`/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`;
|
||||
|
||||
export const fetchThumbnailUrlWithCache = (
|
||||
hass: HomeAssistant,
|
||||
|
@ -3,7 +3,7 @@ import { HomeAssistant } from "../types";
|
||||
interface ProcessResults {
|
||||
card: { [key: string]: { [key: string]: string } };
|
||||
speech: {
|
||||
[SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string }
|
||||
[SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string };
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ export interface DeviceCondition extends DeviceAutomation {
|
||||
}
|
||||
|
||||
export interface DeviceTrigger extends DeviceAutomation {
|
||||
platform: string;
|
||||
platform: "device";
|
||||
}
|
||||
|
||||
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
|
||||
@ -107,9 +107,7 @@ export const localizeDeviceAutomationAction = (
|
||||
state ? computeStateName(state) : "<unknown>",
|
||||
"subtype",
|
||||
hass.localize(
|
||||
`component.${action.domain}.device_automation.action_subtype.${
|
||||
action.subtype
|
||||
}`
|
||||
`component.${action.domain}.device_automation.action_subtype.${action.subtype}`
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -122,16 +120,12 @@ export const localizeDeviceAutomationCondition = (
|
||||
? hass.states[condition.entity_id]
|
||||
: undefined;
|
||||
return hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_type.${
|
||||
condition.type
|
||||
}`,
|
||||
`component.${condition.domain}.device_automation.condition_type.${condition.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : "<unknown>",
|
||||
"subtype",
|
||||
hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_subtype.${
|
||||
condition.subtype
|
||||
}`
|
||||
`component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -142,16 +136,12 @@ export const localizeDeviceAutomationTrigger = (
|
||||
) => {
|
||||
const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined;
|
||||
return hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_type.${
|
||||
trigger.type
|
||||
}`,
|
||||
`component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : "<unknown>",
|
||||
"subtype",
|
||||
hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_subtype.${
|
||||
trigger.subtype
|
||||
}`
|
||||
`component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
|
||||
)
|
||||
);
|
||||
};
|
||||
|
@ -149,9 +149,7 @@ export const createHassioSession = async (hass: HomeAssistant) => {
|
||||
"POST",
|
||||
"hassio/ingress/session"
|
||||
);
|
||||
document.cookie = `ingress_session=${
|
||||
response.data.session
|
||||
};path=/api/hassio_ingress/`;
|
||||
document.cookie = `ingress_session=${response.data.session};path=/api/hassio_ingress/`;
|
||||
};
|
||||
|
||||
export const reloadHassioAddons = (hass: HomeAssistant) =>
|
||||
|
@ -18,38 +18,6 @@ export const SCENE_IGNORED_DOMAINS = [
|
||||
"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 {
|
||||
attributes: HassEntityAttributeBase & { id?: string };
|
||||
}
|
||||
|
@ -98,9 +98,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_description",
|
||||
"integration",
|
||||
this.hass.localize(
|
||||
`component.${
|
||||
this._params.entry.domain
|
||||
}.config.title`
|
||||
`component.${this._params.entry.domain}.config.title`
|
||||
) || this._params.entry.domain
|
||||
)}
|
||||
</p>
|
||||
|
@ -10,7 +10,9 @@ export interface ConfigEntrySystemOptionsDialogParams {
|
||||
}
|
||||
|
||||
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 = (
|
||||
element: HTMLElement,
|
||||
|
@ -71,9 +71,7 @@ export const showConfigFlowDialog = (
|
||||
|
||||
renderShowFormStepFieldLabel(hass, step, field) {
|
||||
return hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.data.${
|
||||
field.name
|
||||
}`
|
||||
`component.${step.handler}.config.step.${step.step_id}.data.${field.name}`
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -79,7 +79,9 @@ export interface DataEntryFlowDialogParams {
|
||||
}
|
||||
|
||||
export const loadDataEntryFlowDialog = () =>
|
||||
import(/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow");
|
||||
import(
|
||||
/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow"
|
||||
);
|
||||
|
||||
export const showFlowDialog = (
|
||||
element: HTMLElement,
|
||||
|
@ -54,9 +54,7 @@ export const showOptionsFlowDialog = (
|
||||
|
||||
renderShowFormStepFieldLabel(hass, step, field) {
|
||||
return hass.localize(
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.data.${
|
||||
field.name
|
||||
}`
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.data.${field.name}`
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -12,7 +12,9 @@ export interface DeviceRegistryDetailDialogParams {
|
||||
}
|
||||
|
||||
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 = (
|
||||
element: HTMLElement,
|
||||
|
@ -6,7 +6,9 @@ export interface HaDomainTogglerDialogParams {
|
||||
}
|
||||
|
||||
export const loadDomainTogglerDialog = () =>
|
||||
import(/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler");
|
||||
import(
|
||||
/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler"
|
||||
);
|
||||
|
||||
export const showDomainTogglerDialog = (
|
||||
element: HTMLElement,
|
||||
|
@ -100,7 +100,7 @@ class MoreInfoClimate extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${stateObj.attributes.temperature
|
||||
${stateObj.attributes.temperature !== undefined
|
||||
? html`
|
||||
<ha-climate-control
|
||||
.value=${stateObj.attributes.temperature}
|
||||
@ -112,8 +112,8 @@ class MoreInfoClimate extends LitElement {
|
||||
></ha-climate-control>
|
||||
`
|
||||
: ""}
|
||||
${stateObj.attributes.target_temp_low ||
|
||||
stateObj.attributes.target_temp_high
|
||||
${stateObj.attributes.target_temp_low !== undefined ||
|
||||
stateObj.attributes.target_temp_high !== undefined
|
||||
? html`
|
||||
<ha-climate-control
|
||||
.value=${stateObj.attributes.target_temp_low}
|
||||
|
@ -2,7 +2,7 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
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/paper-time-input";
|
||||
|
@ -16,13 +16,14 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
class MoreInfoControls extends EventsMixin(PolymerElement) {
|
||||
class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style-dialog">
|
||||
@ -68,7 +69,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
|
||||
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
aria-label="Dismiss dialog"
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.dismiss')]]"
|
||||
icon="hass:close"
|
||||
dialog-dismiss
|
||||
></paper-icon-button>
|
||||
@ -77,6 +78,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
|
||||
</div>
|
||||
<template is="dom-if" if="[[canConfigure]]">
|
||||
<paper-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
on-click="_gotoSettings"
|
||||
></paper-icon-button>
|
||||
|
@ -47,6 +47,7 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
<app-toolbar>
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
aria-label$="[[localize('ui.dialogs.more_info_settings.back')]]"
|
||||
on-click="_backTapped"
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
<div main-title="">[[_computeStateName(stateObj)]]</div>
|
||||
|
@ -49,10 +49,10 @@ export class HuiNotificationDrawer extends EventsMixin(
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<app-drawer id='drawer' opened="{{open}}" disable-swipe align="start">
|
||||
<app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
|
||||
<app-toolbar>
|
||||
<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>
|
||||
<div class="notifications">
|
||||
<template is="dom-if" if="[[!_empty(notifications)]]">
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
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 => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
|
@ -5,7 +5,9 @@ export interface ZHADeviceInfoDialogParams {
|
||||
}
|
||||
|
||||
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 = (
|
||||
element: HTMLElement,
|
||||
|
@ -10,6 +10,8 @@ import "../auth/ha-authorize";
|
||||
/* polyfill for paper-dropdown */
|
||||
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
|
||||
);
|
||||
|
@ -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
|
||||
? () =>
|
||||
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
|
||||
({ createExternalAuth }) => createExternalAuth(hassUrl)
|
||||
)
|
||||
import(
|
||||
/* webpackChunkName: "external_auth" */ "../external_app/external_auth"
|
||||
).then(({ createExternalAuth }) => createExternalAuth(hassUrl))
|
||||
: () =>
|
||||
getAuth({
|
||||
hassUrl,
|
||||
|
@ -74,20 +74,23 @@ export const provideHass = (
|
||||
restResponses.push([path, callback]);
|
||||
}
|
||||
|
||||
mockAPI(new RegExp("states/.+"), (
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
parameters
|
||||
) => {
|
||||
const [domain, objectId] = path.substr(7).split(".", 2);
|
||||
if (!domain || !objectId) {
|
||||
return;
|
||||
mockAPI(
|
||||
new RegExp("states/.+"),
|
||||
(
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
parameters
|
||||
) => {
|
||||
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();
|
||||
|
||||
@ -117,9 +120,7 @@ export const provideHass = (
|
||||
? callback(msg)
|
||||
: Promise.reject({
|
||||
code: "command_not_mocked",
|
||||
message: `WS Command ${
|
||||
msg.type
|
||||
} is not implemented in provide_hass.`,
|
||||
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
|
||||
});
|
||||
},
|
||||
subscribeMessage: async (onChange, msg) => {
|
||||
@ -128,9 +129,7 @@ export const provideHass = (
|
||||
? callback(msg, onChange)
|
||||
: Promise.reject({
|
||||
code: "command_not_mocked",
|
||||
message: `WS Command ${
|
||||
msg.type
|
||||
} is not implemented in provide_hass.`,
|
||||
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
|
||||
});
|
||||
},
|
||||
subscribeEvents: async (
|
||||
|
@ -45,7 +45,9 @@ export class HomeAssistantAppEl extends HassElement {
|
||||
this._initialize();
|
||||
setTimeout(registerServiceWorker, 1000);
|
||||
/* 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 {
|
||||
@ -55,9 +57,10 @@ export class HomeAssistantAppEl extends HassElement {
|
||||
this._updateHass({ panelUrl: this._panelUrl });
|
||||
}
|
||||
if (changedProps.has("hass")) {
|
||||
this.hassChanged(this.hass!, changedProps.get("hass") as
|
||||
| HomeAssistant
|
||||
| undefined);
|
||||
this.hassChanged(
|
||||
this.hass!,
|
||||
changedProps.get("hass") as HomeAssistant | undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,33 +12,59 @@ import { removeInitSkeleton } from "../util/init-skeleton";
|
||||
const CACHE_COMPONENTS = ["lovelace", "states", "developer-tools"];
|
||||
const COMPONENTS = {
|
||||
calendar: () =>
|
||||
import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"
|
||||
),
|
||||
config: () =>
|
||||
import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"
|
||||
),
|
||||
custom: () =>
|
||||
import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"
|
||||
),
|
||||
"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: () =>
|
||||
import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"
|
||||
),
|
||||
states: () =>
|
||||
import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"
|
||||
),
|
||||
history: () =>
|
||||
import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"
|
||||
),
|
||||
iframe: () =>
|
||||
import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"
|
||||
),
|
||||
kiosk: () =>
|
||||
import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"
|
||||
),
|
||||
logbook: () =>
|
||||
import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"
|
||||
),
|
||||
mailbox: () =>
|
||||
import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"
|
||||
),
|
||||
map: () =>
|
||||
import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"),
|
||||
profile: () =>
|
||||
import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"
|
||||
),
|
||||
"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 => {
|
||||
|
@ -84,8 +84,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchOnboardingSteps();
|
||||
import(/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations");
|
||||
import(/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config");
|
||||
import(
|
||||
/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"
|
||||
);
|
||||
import(
|
||||
/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config"
|
||||
);
|
||||
registerServiceWorker(false);
|
||||
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
||||
}
|
||||
|
@ -124,7 +124,9 @@ class OnboardingIntegrations extends LitElement {
|
||||
loadConfigFlowDialog();
|
||||
this._loadConfigEntries();
|
||||
/* 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() {
|
||||
|
@ -14,7 +14,9 @@ export interface AreaRegistryDetailDialogParams {
|
||||
}
|
||||
|
||||
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 = (
|
||||
element: HTMLElement,
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
} from "../../../data/automation";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
|
||||
|
||||
function AutomationEditor(mountEl, props, mergeEl) {
|
||||
return render(h(Automation, props), mountEl, mergeEl);
|
||||
@ -210,15 +211,18 @@ export class HaAutomationEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _backTapped(): void {
|
||||
if (
|
||||
this._dirty &&
|
||||
!confirm(
|
||||
this.hass!.localize("ui.panel.config.automation.editor.unsaved_confirm")
|
||||
)
|
||||
) {
|
||||
return;
|
||||
if (this._dirty) {
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.unsaved_confirm"
|
||||
),
|
||||
confirmBtnText: this.hass!.localize("ui.common.yes"),
|
||||
cancelBtnText: this.hass!.localize("ui.common.no"),
|
||||
confirm: () => history.back(),
|
||||
});
|
||||
} else {
|
||||
history.back();
|
||||
}
|
||||
history.back();
|
||||
}
|
||||
|
||||
private async _delete() {
|
||||
|
@ -115,9 +115,7 @@ class HaAutomationPicker extends LitElement {
|
||||
<a
|
||||
href=${ifDefined(
|
||||
automation.attributes.id
|
||||
? `/config/automation/edit/${
|
||||
automation.attributes.id
|
||||
}`
|
||||
? `/config/automation/edit/${automation.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
|
@ -6,7 +6,9 @@ export interface ThingtalkDialogParams {
|
||||
}
|
||||
|
||||
export const loadThingtalkDialog = () =>
|
||||
import(/* webpackChunkName: "thingtalk-dialog" */ "./thingtalk/dialog-thingtalk");
|
||||
import(
|
||||
/* webpackChunkName: "thingtalk-dialog" */ "./thingtalk/dialog-thingtalk"
|
||||
);
|
||||
|
||||
export const showThingtalkDialog = (
|
||||
element: HTMLElement,
|
||||
|
@ -27,6 +27,7 @@ import { PlaceholderValues } from "./ha-thingtalk-placeholders";
|
||||
import { convertThingTalk } from "../../../../data/cloud";
|
||||
|
||||
export interface Placeholder {
|
||||
name: string;
|
||||
index: number;
|
||||
fields: string[];
|
||||
domains: string[];
|
||||
@ -177,8 +178,21 @@ class DialogThingtalk extends LitElement {
|
||||
const placeholderValues = ev.detail.value as PlaceholderValues;
|
||||
Object.entries(placeholderValues).forEach(([type, values]) => {
|
||||
Object.entries(values).forEach(([index, placeholder]) => {
|
||||
Object.entries(placeholder).forEach(([field, value]) => {
|
||||
this._config[type][index][field] = value;
|
||||
const devices = Object.values(placeholder);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,8 +6,11 @@ import {
|
||||
customElement,
|
||||
css,
|
||||
CSSResult,
|
||||
query,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
|
||||
import "../../../../components/device/ha-area-devices-picker";
|
||||
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@ -17,8 +20,15 @@ import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HaDevicePicker } from "../../../../components/device/ha-device-picker";
|
||||
import { getPath, applyPatch } from "../../../../common/util/patch";
|
||||
import {
|
||||
subscribeAreaRegistry,
|
||||
AreaRegistryEntry,
|
||||
} from "../../../../data/area_registry";
|
||||
import {
|
||||
subscribeDeviceRegistry,
|
||||
DeviceRegistryEntry,
|
||||
} from "../../../../data/device_registry";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@ -28,7 +38,23 @@ declare global {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -43,9 +69,11 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
@property() public placeholders!: PlaceholderContainer;
|
||||
@property() private _error?: string;
|
||||
private _deviceEntityLookup: DeviceEntitiesLookup = {};
|
||||
private _manualEntities: PlaceholderValues = {};
|
||||
@property() private _extraInfo: ExtraInfo = {};
|
||||
@property() private _placeholderValues: PlaceholderValues = {};
|
||||
@query("#device-entity-picker") private _deviceEntityPicker?: HaDevicePicker;
|
||||
private _devices?: DeviceRegistryEntry[];
|
||||
private _areas?: AreaRegistryEntry[];
|
||||
private _search = false;
|
||||
|
||||
public hassSubscribe() {
|
||||
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 {
|
||||
return html`
|
||||
<ha-paper-dialog
|
||||
modal
|
||||
with-backdrop
|
||||
.opened=${this.opened}
|
||||
@opened-changed="${this._openedChanged}"
|
||||
@ -93,55 +137,66 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
</h3>
|
||||
${placeholders.map((placeholder) => {
|
||||
if (placeholder.fields.includes("device_id")) {
|
||||
const extraInfo = getPath(this._extraInfo, [
|
||||
type,
|
||||
placeholder.index,
|
||||
]);
|
||||
return html`
|
||||
<ha-device-picker
|
||||
<ha-area-devices-picker
|
||||
.type=${type}
|
||||
.placeholder=${placeholder}
|
||||
@change=${this._devicePicked}
|
||||
@value-changed=${this._devicePicked}
|
||||
.hass=${this.hass}
|
||||
.area=${extraInfo ? extraInfo.area_id : undefined}
|
||||
.devices=${extraInfo && extraInfo.device_ids
|
||||
? extraInfo.device_ids
|
||||
: undefined}
|
||||
.includeDomains=${placeholder.domains}
|
||||
.includeDeviceClasses=${placeholder.device_classes}
|
||||
.label=${this._getLabel(
|
||||
placeholder.domains,
|
||||
placeholder.device_classes
|
||||
)}
|
||||
></ha-device-picker>
|
||||
${(getPath(this._placeholderValues, [
|
||||
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
|
||||
></ha-area-devices-picker>
|
||||
${extraInfo && extraInfo.manualEntity
|
||||
? html`
|
||||
<ha-entity-picker
|
||||
id="device-entity-picker"
|
||||
.type=${type}
|
||||
.placeholder=${placeholder}
|
||||
@change=${this._entityPicked}
|
||||
.includeDomains=${placeholder.domains}
|
||||
.includeDeviceClasses=${placeholder.device_classes}
|
||||
.hass=${this.hass}
|
||||
.label=${this._getLabel(
|
||||
placeholder.domains,
|
||||
placeholder.device_classes
|
||||
)}
|
||||
.entityFilter=${(state: HassEntity) =>
|
||||
this._deviceEntityLookup[
|
||||
this._placeholderValues[type][
|
||||
placeholder.index
|
||||
].device_id
|
||||
].includes(state.entity_id)}
|
||||
></ha-entity-picker>
|
||||
<h3>
|
||||
One or more devices have more than one matching
|
||||
entity, please pick the one you want to use.
|
||||
</h3>
|
||||
${Object.keys(extraInfo.manualEntity).map(
|
||||
(idx) => html`
|
||||
<ha-entity-picker
|
||||
id="device-entity-picker"
|
||||
.type=${type}
|
||||
.placeholder=${placeholder}
|
||||
.index=${idx}
|
||||
@change=${this._entityPicked}
|
||||
.includeDomains=${placeholder.domains}
|
||||
.includeDeviceClasses=${placeholder.device_classes}
|
||||
.hass=${this.hass}
|
||||
.label=${`${this._getLabel(
|
||||
placeholder.domains,
|
||||
placeholder.device_classes
|
||||
)} 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 {
|
||||
return Object.entries(this.placeholders).every(([type, placeholders]) =>
|
||||
placeholders.every((placeholder) =>
|
||||
placeholder.fields.every(
|
||||
(field) =>
|
||||
getPath(this._placeholderValues, [
|
||||
type,
|
||||
placeholder.index,
|
||||
field,
|
||||
]) !== undefined
|
||||
)
|
||||
placeholder.fields.every((field) => {
|
||||
const entries: {
|
||||
[key: number]: { device_id?: string; entity_id?: string };
|
||||
} = getPath(this._placeholderValues, [type, placeholder.index]);
|
||||
if (!entries) {
|
||||
return false;
|
||||
}
|
||||
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 placeholder = target.placeholder as Placeholder;
|
||||
const value = target.value;
|
||||
const type = target.type;
|
||||
applyPatch(
|
||||
this._placeholderValues,
|
||||
[type, placeholder.index, "device_id"],
|
||||
value
|
||||
);
|
||||
if (!placeholder.fields.includes("entity_id")) {
|
||||
return;
|
||||
|
||||
let oldValues = getPath(this._placeholderValues, [type, placeholder.index]);
|
||||
if (oldValues) {
|
||||
oldValues = Object.values(oldValues);
|
||||
}
|
||||
if (value === "") {
|
||||
delete this._placeholderValues[type][placeholder.index].entity_id;
|
||||
if (this._deviceEntityPicker) {
|
||||
this._deviceEntityPicker.value = undefined;
|
||||
}
|
||||
applyPatch(
|
||||
this._manualEntities,
|
||||
[type, placeholder.index, "manual"],
|
||||
false
|
||||
);
|
||||
const oldExtraInfo = getPath(this._extraInfo, [type, placeholder.index]);
|
||||
|
||||
if (this._placeholderValues[type]) {
|
||||
delete this._placeholderValues[type][placeholder.index];
|
||||
}
|
||||
|
||||
if (this._extraInfo[type]) {
|
||||
delete this._extraInfo[type][placeholder.index];
|
||||
}
|
||||
|
||||
if (!value.length) {
|
||||
this.requestUpdate("_placeholderValues");
|
||||
return;
|
||||
}
|
||||
const devEntities = this._deviceEntityLookup[value];
|
||||
const entities = devEntities.filter((eid) => {
|
||||
if (placeholder.device_classes) {
|
||||
const stateObj = this.hass.states[eid];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
|
||||
value.forEach((deviceId, index) => {
|
||||
let oldIndex;
|
||||
if (oldValues) {
|
||||
const oldDevice = oldValues.find((oldVal, idx) => {
|
||||
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(
|
||||
this._placeholderValues,
|
||||
[type, placeholder.index, "entity_id"],
|
||||
entities[0]
|
||||
[type, placeholder.index, index, "device_id"],
|
||||
deviceId
|
||||
);
|
||||
applyPatch(
|
||||
this._manualEntities,
|
||||
[type, placeholder.index, "manual"],
|
||||
false
|
||||
);
|
||||
this.requestUpdate("_placeholderValues");
|
||||
} else {
|
||||
delete this._placeholderValues[type][placeholder.index].entity_id;
|
||||
if (this._deviceEntityPicker) {
|
||||
this._deviceEntityPicker.value = undefined;
|
||||
|
||||
if (!placeholder.fields.includes("entity_id")) {
|
||||
return;
|
||||
}
|
||||
applyPatch(
|
||||
this._manualEntities,
|
||||
[type, placeholder.index, "manual"],
|
||||
true
|
||||
);
|
||||
this.requestUpdate("_placeholderValues");
|
||||
}
|
||||
|
||||
const devEntities = this._deviceEntityLookup[deviceId];
|
||||
|
||||
const entities = devEntities.filter((eid) => {
|
||||
if (placeholder.device_classes) {
|
||||
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 {
|
||||
@ -289,9 +440,10 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
const placeholder = target.placeholder as Placeholder;
|
||||
const value = target.value;
|
||||
const type = target.type;
|
||||
const index = target.index || 0;
|
||||
applyPatch(
|
||||
this._placeholderValues,
|
||||
[type, placeholder.index, "entity_id"],
|
||||
[type, placeholder.index, index, "entity_id"],
|
||||
value
|
||||
);
|
||||
this.requestUpdate("_placeholderValues");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -12,7 +12,9 @@ export const showCloudCertificateDialog = (
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-cloud-certificate",
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "dialog-cloud-certificate" */ "./dialog-cloud-certificate"),
|
||||
import(
|
||||
/* webpackChunkName: "dialog-cloud-certificate" */ "./dialog-cloud-certificate"
|
||||
),
|
||||
dialogParams: webhookDialogParams,
|
||||
});
|
||||
};
|
||||
|
@ -15,7 +15,9 @@ export const showManageCloudhookDialog = (
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-manage-cloudhook",
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./dialog-manage-cloudhook"),
|
||||
import(
|
||||
/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./dialog-manage-cloudhook"
|
||||
),
|
||||
dialogParams: webhookDialogParams,
|
||||
});
|
||||
};
|
||||
|
@ -46,12 +46,16 @@ class HaConfigCloud extends HassRouterPage {
|
||||
register: {
|
||||
tag: "cloud-register",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "cloud-register" */ "./register/cloud-register"),
|
||||
import(
|
||||
/* webpackChunkName: "cloud-register" */ "./register/cloud-register"
|
||||
),
|
||||
},
|
||||
"forgot-password": {
|
||||
tag: "cloud-forgot-password",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "cloud-forgot-password" */ "./forgot-password/cloud-forgot-password"),
|
||||
import(
|
||||
/* webpackChunkName: "cloud-forgot-password" */ "./forgot-password/cloud-forgot-password"
|
||||
),
|
||||
},
|
||||
account: {
|
||||
tag: "cloud-account",
|
||||
@ -59,7 +63,9 @@ class HaConfigCloud extends HassRouterPage {
|
||||
"google-assistant": {
|
||||
tag: "cloud-google-assistant",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "cloud-google-assistant" */ "./google-assistant/cloud-google-assistant"),
|
||||
import(
|
||||
/* webpackChunkName: "cloud-google-assistant" */ "./google-assistant/cloud-google-assistant"
|
||||
),
|
||||
},
|
||||
alexa: {
|
||||
tag: "cloud-alexa",
|
||||
|
@ -138,6 +138,9 @@ class HaConfigDashboard extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-config-navigation:last-child {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -148,6 +151,7 @@ class HaConfigDashboard extends LitElement {
|
||||
.promo-advanced {
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.promo-advanced a {
|
||||
color: var(--secondary-text-color);
|
||||
|
@ -6,7 +6,9 @@ export interface EntityRegistryDetailDialogParams {
|
||||
}
|
||||
|
||||
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 = (
|
||||
element: HTMLElement,
|
||||
|
@ -31,82 +31,114 @@ class HaPanelConfig extends HassRouterPage {
|
||||
area_registry: {
|
||||
tag: "ha-config-area-registry",
|
||||
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: {
|
||||
tag: "ha-config-automation",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"
|
||||
),
|
||||
},
|
||||
cloud: {
|
||||
tag: "ha-config-cloud",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"
|
||||
),
|
||||
},
|
||||
core: {
|
||||
tag: "ha-config-core",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"
|
||||
),
|
||||
},
|
||||
devices: {
|
||||
tag: "ha-config-devices",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"
|
||||
),
|
||||
},
|
||||
server_control: {
|
||||
tag: "ha-config-server-control",
|
||||
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: {
|
||||
tag: "ha-config-customize",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"
|
||||
),
|
||||
},
|
||||
dashboard: {
|
||||
tag: "ha-config-dashboard",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"
|
||||
),
|
||||
},
|
||||
entity_registry: {
|
||||
tag: "ha-config-entity-registry",
|
||||
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: {
|
||||
tag: "ha-config-integrations",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
|
||||
),
|
||||
},
|
||||
person: {
|
||||
tag: "ha-config-person",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"
|
||||
),
|
||||
},
|
||||
script: {
|
||||
tag: "ha-config-script",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"
|
||||
),
|
||||
},
|
||||
scene: {
|
||||
tag: "ha-config-scene",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
|
||||
),
|
||||
},
|
||||
users: {
|
||||
tag: "ha-config-users",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"
|
||||
),
|
||||
},
|
||||
zha: {
|
||||
tag: "zha-config-panel",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"
|
||||
),
|
||||
},
|
||||
zwave: {
|
||||
tag: "ha-config-zwave",
|
||||
load: () =>
|
||||
import(/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"),
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -133,7 +133,11 @@ export class HaConfigManagerDashboard extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
</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>
|
||||
</a>
|
||||
`
|
||||
@ -154,6 +158,7 @@ export class HaConfigManagerDashboard extends LitElement {
|
||||
|
||||
<ha-fab
|
||||
icon="hass:plus"
|
||||
aria-label=${this.hass.localize("ui.panel.config.integrations.new")}
|
||||
title=${this.hass.localize("ui.panel.config.integrations.new")}
|
||||
@click=${this._createFlow}
|
||||
?rtl=${computeRTL(this.hass!)}
|
||||
|
23
src/panels/config/js/automation-component.tsx
Normal file
23
src/panels/config/js/automation-component.tsx
Normal 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 />;
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ import "../ha-config-section";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-textarea";
|
||||
|
||||
import Trigger from "./trigger/index";
|
||||
import "../automation/trigger/ha-automation-trigger";
|
||||
|
||||
import Condition from "./condition/index";
|
||||
import Script from "./script/index";
|
||||
|
||||
@ -26,8 +27,8 @@ export default class Automation extends Component<any> {
|
||||
});
|
||||
}
|
||||
|
||||
public triggerChanged(trigger) {
|
||||
this.props.onChange({ ...this.props.automation, trigger });
|
||||
public triggerChanged(ev: CustomEvent) {
|
||||
this.props.onChange({ ...this.props.automation, trigger: ev.detail.value });
|
||||
}
|
||||
|
||||
public conditionChanged(condition) {
|
||||
@ -90,11 +91,10 @@ export default class Automation extends Component<any> {
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<Trigger
|
||||
trigger={trigger}
|
||||
onChange={this.triggerChanged}
|
||||
<ha-automation-trigger
|
||||
triggers={trigger}
|
||||
onvalue-changed={this.triggerChanged}
|
||||
hass={hass}
|
||||
localize={localize}
|
||||
/>
|
||||
</ha-config-section>
|
||||
|
||||
|
@ -3,6 +3,8 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
|
||||
import YAMLTextArea from "../yaml_textarea";
|
||||
|
||||
import DeviceCondition from "./device";
|
||||
import LogicalCondition from "./logical";
|
||||
import NumericStateCondition from "./numeric_state";
|
||||
@ -31,6 +33,7 @@ export default class ConditionEdit extends Component<any> {
|
||||
super();
|
||||
|
||||
this.typeChanged = this.typeChanged.bind(this);
|
||||
this.onYamlChange = this.onYamlChange.bind(this);
|
||||
}
|
||||
|
||||
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
|
||||
const Comp = TYPES[condition.condition];
|
||||
const selected = OPTIONS.indexOf(condition.condition);
|
||||
|
||||
if (!Comp) {
|
||||
if (yamlMode || !Comp) {
|
||||
return (
|
||||
<div>
|
||||
{localize(
|
||||
"ui.panel.config.automation.editor.conditions.unsupported_condition",
|
||||
"condition",
|
||||
condition.condition
|
||||
<div style="margin-right: 24px;">
|
||||
{!Comp && (
|
||||
<div>
|
||||
{localize(
|
||||
"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>
|
||||
);
|
||||
}
|
||||
@ -94,4 +101,8 @@ export default class ConditionEdit extends Component<any> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onYamlChange(condition) {
|
||||
this.props.onChange(this.props.index, condition);
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,16 @@ import "../../../../components/ha-card";
|
||||
import ConditionEdit from "./condition_edit";
|
||||
|
||||
export default class ConditionRow extends Component<any> {
|
||||
public state: { yamlMode: boolean };
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
yamlMode: false,
|
||||
};
|
||||
|
||||
this.onDelete = this.onDelete.bind(this);
|
||||
this.switchYamlMode = this.switchYamlMode.bind(this);
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
@ -27,22 +33,32 @@ export default class ConditionRow extends Component<any> {
|
||||
}
|
||||
}
|
||||
|
||||
public render(props) {
|
||||
public render(props, { yamlMode }) {
|
||||
return (
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="card-menu">
|
||||
<div class="card-menu" style="z-index: 3">
|
||||
<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-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>
|
||||
{props.localize(
|
||||
"ui.panel.config.automation.editor.conditions.duplicate"
|
||||
@ -56,9 +72,15 @@ export default class ConditionRow extends Component<any> {
|
||||
</paper-listbox>
|
||||
</paper-menu-button>
|
||||
</div>
|
||||
<ConditionEdit {...props} />
|
||||
<ConditionEdit {...props} yamlMode={yamlMode} />
|
||||
</div>
|
||||
</ha-card>
|
||||
);
|
||||
}
|
||||
|
||||
private switchYamlMode() {
|
||||
this.setState({
|
||||
yamlMode: !this.state.yamlMode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { h, Component } from "preact";
|
||||
import { h } from "preact";
|
||||
|
||||
import "../../../../components/device/ha-device-picker";
|
||||
import "../../../../components/device/ha-device-condition-picker";
|
||||
@ -8,7 +8,10 @@ import {
|
||||
fetchDeviceConditionCapabilities,
|
||||
deviceAutomationsEqual,
|
||||
} 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;
|
||||
|
||||
constructor() {
|
||||
@ -20,10 +23,16 @@ export default class DeviceCondition extends Component<any, any> {
|
||||
}
|
||||
|
||||
public devicePicked(ev) {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.setState({ ...this.state, device_id: ev.target.value });
|
||||
}
|
||||
|
||||
public deviceConditionPicked(ev) {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
let condition = ev.target.value;
|
||||
if (
|
||||
this._origCondition &&
|
||||
@ -74,6 +83,7 @@ export default class DeviceCondition extends Component<any, any> {
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.initialized = true;
|
||||
if (!this.state.capabilities) {
|
||||
this._getCapabilities();
|
||||
}
|
||||
@ -98,6 +108,9 @@ export default class DeviceCondition extends Component<any, any> {
|
||||
}
|
||||
|
||||
private _extraFieldsChanged(ev) {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
...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
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.condition.type.device.extra_fields.${
|
||||
schema.name
|
||||
}`
|
||||
`ui.panel.config.automation.editor.condition.type.device.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,22 @@
|
||||
import { h, Component } from "preact";
|
||||
import { h } from "preact";
|
||||
|
||||
import Condition from "./index";
|
||||
import { AutomationComponent } from "../automation-component";
|
||||
|
||||
export default class LogicalCondition extends Component<any, any> {
|
||||
private _mounted = false;
|
||||
export default class LogicalCondition extends AutomationComponent<any> {
|
||||
constructor() {
|
||||
super();
|
||||
this.conditionChanged = this.conditionChanged.bind(this);
|
||||
}
|
||||
|
||||
public conditionChanged(conditions) {
|
||||
if (this._mounted) {
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
conditions,
|
||||
});
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
this._mounted = true;
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this._mounted = false;
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
conditions,
|
||||
});
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { h, Component } from "preact";
|
||||
import { h } from "preact";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../../components/ha-textarea";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
|
||||
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;
|
||||
constructor() {
|
||||
super();
|
||||
@ -15,6 +16,9 @@ export default class NumericStateCondition extends Component<any> {
|
||||
}
|
||||
|
||||
public entityPicked(ev) {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
entity_id: ev.target.value,
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { h, Component } from "preact";
|
||||
import { h } from "preact";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
|
||||
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;
|
||||
constructor() {
|
||||
super();
|
||||
@ -14,6 +15,9 @@ export default class StateCondition extends Component<any> {
|
||||
}
|
||||
|
||||
public entityPicked(ev) {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
entity_id: ev.target.value,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user