mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 12:46:35 +00:00
20200107.0
20200107.0
This commit is contained in:
commit
e9231fc17e
@ -13,15 +13,6 @@ script:
|
|||||||
- npm run test
|
- npm run test
|
||||||
# - xvfb-run wct --module-resolution=node --npm
|
# - xvfb-run wct --module-resolution=node --npm
|
||||||
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
|
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
before_deploy:
|
|
||||||
- "docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21"
|
|
||||||
deploy:
|
|
||||||
provider: script
|
|
||||||
script: script/travis_deploy
|
|
||||||
"on":
|
|
||||||
branch: master
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
sauce_connect: true
|
sauce_connect: true
|
||||||
|
70
azure-pipelines-translation.yml
Normal file
70
azure-pipelines-translation.yml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# https://dev.azure.com/home-assistant
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
batch: true
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
include:
|
||||||
|
- translation/en.json
|
||||||
|
pr: none
|
||||||
|
schedules:
|
||||||
|
- cron: "30 0 * * *"
|
||||||
|
displayName: "translation update"
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- dev
|
||||||
|
always: true
|
||||||
|
variables:
|
||||||
|
- group: translation
|
||||||
|
resources:
|
||||||
|
repositories:
|
||||||
|
- repository: azure
|
||||||
|
type: github
|
||||||
|
name: 'home-assistant/ci-azure'
|
||||||
|
endpoint: 'home-assistant'
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
- job: 'Upload'
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Use Node 12.x'
|
||||||
|
inputs:
|
||||||
|
versionSpec: '12.x'
|
||||||
|
- script: |
|
||||||
|
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||||
|
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||||
|
|
||||||
|
./script/translations_upload_base
|
||||||
|
displayName: 'Upload Translation'
|
||||||
|
|
||||||
|
- job: 'Download'
|
||||||
|
dependsOn:
|
||||||
|
- 'Upload'
|
||||||
|
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Use Node 12.x'
|
||||||
|
inputs:
|
||||||
|
versionSpec: '12.x'
|
||||||
|
- template: templates/azp-step-git-init.yaml@azure
|
||||||
|
- script: |
|
||||||
|
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||||
|
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||||
|
|
||||||
|
npm install
|
||||||
|
./script/translations_download
|
||||||
|
displayName: 'Download Translation'
|
||||||
|
- script: |
|
||||||
|
git checkout dev
|
||||||
|
git add translation
|
||||||
|
git commit -am "[ci skip] Translation update"
|
||||||
|
git push
|
||||||
|
displayName: 'Update translation'
|
@ -91,7 +91,7 @@ const createWebpackConfig = ({
|
|||||||
),
|
),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js", ".json", ".tsx"],
|
extensions: [".ts", ".js", ".json"],
|
||||||
alias: {
|
alias: {
|
||||||
react: "preact-compat",
|
react: "preact-compat",
|
||||||
"react-dom": "preact-compat",
|
"react-dom": "preact-compat",
|
||||||
|
@ -373,19 +373,21 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="state">
|
<template is="dom-if" if="[[_computeUsesProtectedOptions(addon)]]">
|
||||||
<div>
|
<div class="state">
|
||||||
Protection mode
|
<div>
|
||||||
<span>
|
Protection mode
|
||||||
<iron-icon icon="hassio:information"></iron-icon>
|
<span>
|
||||||
<paper-tooltip>Grant the add-on elevated system access.</paper-tooltip>
|
<iron-icon icon="hassio:information"></iron-icon>
|
||||||
</span>
|
<paper-tooltip>Grant the add-on elevated system access.</paper-tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ha-switch
|
||||||
|
on-change="protectionToggled"
|
||||||
|
checked="[[addon.protected]]"
|
||||||
|
></ha-switch>
|
||||||
</div>
|
</div>
|
||||||
<ha-switch
|
</template>
|
||||||
on-change="protectionToggled"
|
|
||||||
checked="[[addon.protected]]"
|
|
||||||
></ha-switch>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@ -610,6 +612,10 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
return !addon.ingress || !this._computeHA92plus(hass);
|
return !addon.ingress || !this._computeHA92plus(hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeUsesProtectedOptions(addon) {
|
||||||
|
return addon.docker_api || addon.full_access || addon.host_pid;
|
||||||
|
}
|
||||||
|
|
||||||
_computeHA92plus(hass) {
|
_computeHA92plus(hass) {
|
||||||
const [major, minor] = hass.config.version.split(".", 2);
|
const [major, minor] = hass.config.version.split(".", 2);
|
||||||
return Number(major) > 0 || (major === "0" && Number(minor) >= 92);
|
return Number(major) > 0 || (major === "0" && Number(minor) >= 92);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "script/build_frontend",
|
"build": "script/build_frontend",
|
||||||
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'src/**/*.tsx' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts' && tsc",
|
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts' && tsc",
|
||||||
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
|
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
|
||||||
"test": "npm run lint && npm run mocha",
|
"test": "npm run lint && npm run mocha",
|
||||||
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
|
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"@material/mwc-fab": "^0.10.0",
|
"@material/mwc-fab": "^0.10.0",
|
||||||
"@material/mwc-ripple": "^0.10.0",
|
"@material/mwc-ripple": "^0.10.0",
|
||||||
"@material/mwc-switch": "^0.10.0",
|
"@material/mwc-switch": "^0.10.0",
|
||||||
"@mdi/svg": "4.6.95",
|
"@mdi/svg": "4.7.95",
|
||||||
"@polymer/app-layout": "^3.0.2",
|
"@polymer/app-layout": "^3.0.2",
|
||||||
"@polymer/app-localize-behavior": "^3.0.1",
|
"@polymer/app-localize-behavior": "^3.0.1",
|
||||||
"@polymer/app-route": "^3.0.2",
|
"@polymer/app-route": "^3.0.2",
|
||||||
@ -99,7 +99,6 @@
|
|||||||
"regenerator-runtime": "^0.13.2",
|
"regenerator-runtime": "^0.13.2",
|
||||||
"roboto-fontface": "^0.10.0",
|
"roboto-fontface": "^0.10.0",
|
||||||
"superstruct": "^0.6.1",
|
"superstruct": "^0.6.1",
|
||||||
"copy-to-clipboard": "^1.0.9",
|
|
||||||
"tslib": "^1.10.0",
|
"tslib": "^1.10.0",
|
||||||
"unfetch": "^4.1.0",
|
"unfetch": "^4.1.0",
|
||||||
"web-animations-js": "^2.3.1",
|
"web-animations-js": "^2.3.1",
|
||||||
|
@ -26,8 +26,8 @@ LANG_ISO=en
|
|||||||
|
|
||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
|
||||||
if [ "${CURRENT_BRANCH-}" != "master" ] && [ "${TRAVIS_BRANCH-}" != "master" ] ; then
|
if [ "${CURRENT_BRANCH-}" != "dev" ] && [ "${AZURE_BRANCH-}" != "dev" ] ; then
|
||||||
echo "Please only run the translations upload script from a clean checkout of master."
|
echo "Please only run the translations upload script from a clean checkout of dev."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Safe bash settings
|
|
||||||
# -e Exit on command fail
|
|
||||||
# -u Exit on unset variable
|
|
||||||
# -o pipefail Exit if piped command has error code
|
|
||||||
set -eu -o pipefail
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
script/translations_upload_base
|
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20191204.1",
|
version="20200107.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { directive, Part, NodePart } from "lit-html";
|
import { directive, Part, NodePart } from "lit-html";
|
||||||
|
|
||||||
export const dynamicContentDirective = directive(
|
export const dynamicElement = directive(
|
||||||
(tag: string, properties: { [key: string]: any }) => (part: Part): void => {
|
(tag: string, properties?: { [key: string]: any }) => (part: Part): void => {
|
||||||
if (!(part instanceof NodePart)) {
|
if (!(part instanceof NodePart)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"dynamicContentDirective can only be used in content bindings"
|
"dynamicContentDirective can only be used in content bindings"
|
||||||
@ -14,16 +14,20 @@ export const dynamicContentDirective = directive(
|
|||||||
element !== undefined &&
|
element !== undefined &&
|
||||||
tag.toUpperCase() === (element as HTMLElement).tagName
|
tag.toUpperCase() === (element as HTMLElement).tagName
|
||||||
) {
|
) {
|
||||||
Object.entries(properties).forEach(([key, value]) => {
|
if (properties) {
|
||||||
element![key] = value;
|
Object.entries(properties).forEach(([key, value]) => {
|
||||||
});
|
element![key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
element = document.createElement(tag);
|
element = document.createElement(tag);
|
||||||
Object.entries(properties).forEach(([key, value]) => {
|
if (properties) {
|
||||||
element![key] = value;
|
Object.entries(properties).forEach(([key, value]) => {
|
||||||
});
|
element![key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
part.setValue(element);
|
part.setValue(element);
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -1,28 +0,0 @@
|
|||||||
// interface OnChangeComponent {
|
|
||||||
// props: {
|
|
||||||
// index: number;
|
|
||||||
// onChange(index: number, data: object);
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function onChangeEvent(this: OnChangeComponent, prop, ev) {
|
|
||||||
export function onChangeEvent(this: any, prop, ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const origData = this.props[prop];
|
|
||||||
if (ev.target.value === origData[ev.target.name]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = { ...origData };
|
|
||||||
|
|
||||||
if (ev.target.value) {
|
|
||||||
data[ev.target.name] = ev.target.value;
|
|
||||||
} else {
|
|
||||||
delete data[ev.target.name];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(this.props.index, data);
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import { render } from "preact";
|
|
||||||
|
|
||||||
export default function unmount(mountEl) {
|
|
||||||
render(
|
|
||||||
// @ts-ignore
|
|
||||||
() => null,
|
|
||||||
mountEl
|
|
||||||
);
|
|
||||||
}
|
|
@ -14,7 +14,11 @@ import "@material/mwc-button";
|
|||||||
|
|
||||||
@customElement("search-input")
|
@customElement("search-input")
|
||||||
class SearchInput extends LitElement {
|
class SearchInput extends LitElement {
|
||||||
@property() private filter?: string;
|
@property() public filter?: string;
|
||||||
|
|
||||||
|
public focus() {
|
||||||
|
this.shadowRoot!.querySelector("paper-input")!.focus();
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
|
@ -75,7 +75,7 @@ export interface DataTableSortColumnData {
|
|||||||
export interface DataTableColumnData extends DataTableSortColumnData {
|
export interface DataTableColumnData extends DataTableSortColumnData {
|
||||||
title: string;
|
title: string;
|
||||||
type?: "numeric" | "icon";
|
type?: "numeric" | "icon";
|
||||||
template?: <T>(data: any, row: T) => TemplateResult;
|
template?: <T>(data: any, row: T) => TemplateResult | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTableRowData {
|
export interface DataTableRowData {
|
||||||
@ -88,11 +88,11 @@ export class HaDataTable extends BaseElement {
|
|||||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||||
@property({ type: Boolean }) public selectable = false;
|
@property({ type: Boolean }) public selectable = false;
|
||||||
@property({ type: String }) public id = "id";
|
@property({ type: String }) public id = "id";
|
||||||
|
@property({ type: String }) public filter = "";
|
||||||
protected mdcFoundation!: MDCDataTableFoundation;
|
protected mdcFoundation!: MDCDataTableFoundation;
|
||||||
protected readonly mdcFoundationClass = MDCDataTableFoundation;
|
protected readonly mdcFoundationClass = MDCDataTableFoundation;
|
||||||
@query(".mdc-data-table") protected mdcRoot!: HTMLElement;
|
@query(".mdc-data-table") protected mdcRoot!: HTMLElement;
|
||||||
@queryAll(".mdc-data-table__row") protected rowElements!: HTMLElement[];
|
@queryAll(".mdc-data-table__row") protected rowElements!: HTMLElement[];
|
||||||
@query("#header-checkbox") private _headerCheckbox!: HaCheckbox;
|
|
||||||
@property({ type: Boolean }) private _filterable = false;
|
@property({ type: Boolean }) private _filterable = false;
|
||||||
@property({ type: Boolean }) private _headerChecked = false;
|
@property({ type: Boolean }) private _headerChecked = false;
|
||||||
@property({ type: Boolean }) private _headerIndeterminate = false;
|
@property({ type: Boolean }) private _headerIndeterminate = false;
|
||||||
@ -108,13 +108,19 @@ export class HaDataTable extends BaseElement {
|
|||||||
private _worker: any | undefined;
|
private _worker: any | undefined;
|
||||||
|
|
||||||
private _debounceSearch = debounce(
|
private _debounceSearch = debounce(
|
||||||
(ev) => {
|
(value: string) => {
|
||||||
this._filter = ev.detail.value;
|
this._filter = value;
|
||||||
},
|
},
|
||||||
200,
|
200,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public clearSelection(): void {
|
||||||
|
this._headerChecked = false;
|
||||||
|
this._headerIndeterminate = false;
|
||||||
|
this.mdcFoundation.handleHeaderRowCheckboxChange();
|
||||||
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
super.firstUpdated();
|
super.firstUpdated();
|
||||||
this._worker = sortFilterWorker();
|
this._worker = sortFilterWorker();
|
||||||
@ -146,6 +152,10 @@ export class HaDataTable extends BaseElement {
|
|||||||
this._sortColumns = clonedColumns;
|
this._sortColumns = clonedColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (properties.has("filter")) {
|
||||||
|
this._debounceSearch(this.filter);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
properties.has("data") ||
|
properties.has("data") ||
|
||||||
properties.has("columns") ||
|
properties.has("columns") ||
|
||||||
@ -159,14 +169,18 @@ export class HaDataTable extends BaseElement {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
${this._filterable
|
|
||||||
? html`
|
|
||||||
<search-input
|
|
||||||
@value-changed=${this._handleSearchChange}
|
|
||||||
></search-input>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<div class="mdc-data-table">
|
<div class="mdc-data-table">
|
||||||
|
<slot name="header">
|
||||||
|
${this._filterable
|
||||||
|
? html`
|
||||||
|
<div class="table-header">
|
||||||
|
<search-input
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
></search-input>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</slot>
|
||||||
<table class="mdc-data-table__table">
|
<table class="mdc-data-table__table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="mdc-data-table__header-row">
|
<tr class="mdc-data-table__header-row">
|
||||||
@ -178,7 +192,6 @@ export class HaDataTable extends BaseElement {
|
|||||||
scope="col"
|
scope="col"
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
id="header-checkbox"
|
|
||||||
class="mdc-data-table__row-checkbox"
|
class="mdc-data-table__row-checkbox"
|
||||||
@change=${this._handleHeaderRowCheckboxChange}
|
@change=${this._handleHeaderRowCheckboxChange}
|
||||||
.indeterminate=${this._headerIndeterminate}
|
.indeterminate=${this._headerIndeterminate}
|
||||||
@ -372,9 +385,10 @@ export class HaDataTable extends BaseElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleHeaderRowCheckboxChange() {
|
private _handleHeaderRowCheckboxChange(ev: Event) {
|
||||||
this._headerChecked = this._headerCheckbox.checked;
|
const checkbox = ev.target as HaCheckbox;
|
||||||
this._headerIndeterminate = this._headerCheckbox.indeterminate;
|
this._headerChecked = checkbox.checked;
|
||||||
|
this._headerIndeterminate = checkbox.indeterminate;
|
||||||
this.mdcFoundation.handleHeaderRowCheckboxChange();
|
this.mdcFoundation.handleHeaderRowCheckboxChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,20 +401,26 @@ export class HaDataTable extends BaseElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleRowClick(ev: Event) {
|
private _handleRowClick(ev: Event) {
|
||||||
const rowId = (ev.target as HTMLElement)
|
const target = ev.target as HTMLElement;
|
||||||
.closest("tr")!
|
if (target.tagName === "HA-CHECKBOX") {
|
||||||
.getAttribute("data-row-id")!;
|
return;
|
||||||
|
}
|
||||||
|
const rowId = target.closest("tr")!.getAttribute("data-row-id")!;
|
||||||
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setRowChecked(rowId: string, checked: boolean) {
|
private _setRowChecked(rowId: string, checked: boolean) {
|
||||||
if (checked && !this._checkedRows.includes(rowId)) {
|
if (checked) {
|
||||||
this._checkedRows = [...this._checkedRows, rowId];
|
if (this._checkedRows.includes(rowId)) {
|
||||||
} else if (!checked) {
|
return;
|
||||||
const index = this._checkedRows.indexOf(rowId);
|
|
||||||
if (index !== -1) {
|
|
||||||
this._checkedRows.splice(index, 1);
|
|
||||||
}
|
}
|
||||||
|
this._checkedRows = [...this._checkedRows, rowId];
|
||||||
|
} else {
|
||||||
|
const index = this._checkedRows.indexOf(rowId);
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._checkedRows.splice(index, 1);
|
||||||
}
|
}
|
||||||
fireEvent(this, "selection-changed", {
|
fireEvent(this, "selection-changed", {
|
||||||
id: rowId,
|
id: rowId,
|
||||||
@ -409,7 +429,7 @@ export class HaDataTable extends BaseElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent): void {
|
private _handleSearchChange(ev: CustomEvent): void {
|
||||||
this._debounceSearch(ev);
|
this._debounceSearch(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
@ -589,6 +609,9 @@ export class HaDataTable extends BaseElement {
|
|||||||
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
|
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
.table-header {
|
||||||
|
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,8 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
html,
|
html,
|
||||||
property,
|
property,
|
||||||
query,
|
|
||||||
CSSResult,
|
CSSResult,
|
||||||
css,
|
css,
|
||||||
PropertyValues,
|
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "./ha-form-string";
|
import "./ha-form-string";
|
||||||
@ -16,6 +14,7 @@ import "./ha-form-boolean";
|
|||||||
import "./ha-form-select";
|
import "./ha-form-select";
|
||||||
import "./ha-form-positive_time_period_dict";
|
import "./ha-form-positive_time_period_dict";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
|
|
||||||
export type HaFormSchema =
|
export type HaFormSchema =
|
||||||
| HaFormStringSchema
|
| HaFormStringSchema
|
||||||
@ -100,20 +99,14 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
||||||
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
||||||
@property() public computeSuffix?: (schema: HaFormSchema) => string;
|
@property() public computeSuffix?: (schema: HaFormSchema) => string;
|
||||||
@query("ha-form") private _childForm?: HaForm;
|
|
||||||
@query("#element") private _elementContainer?: HTMLDivElement;
|
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
const input = this._childForm
|
const input =
|
||||||
? this._childForm
|
this.shadowRoot!.getElementById("child-form") ||
|
||||||
: this._elementContainer
|
this.shadowRoot!.querySelector("ha-form");
|
||||||
? this._elementContainer.lastChild
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (!input) {
|
if (!input) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(input as HTMLElement).focus();
|
(input as HTMLElement).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,40 +144,16 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div id="element"></div>
|
${dynamicElement(`ha-form-${this.schema.type}`, {
|
||||||
|
schema: this.schema,
|
||||||
|
data: this.data,
|
||||||
|
label: this._computeLabel(this.schema),
|
||||||
|
suffix: this._computeSuffix(this.schema),
|
||||||
|
id: "child-form",
|
||||||
|
})}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
|
||||||
const schemaChanged = changedProperties.has("schema");
|
|
||||||
const oldSchema = schemaChanged
|
|
||||||
? changedProperties.get("schema")
|
|
||||||
: undefined;
|
|
||||||
if (
|
|
||||||
!Array.isArray(this.schema) &&
|
|
||||||
schemaChanged &&
|
|
||||||
(!oldSchema || (oldSchema as HaFormSchema).type !== this.schema.type)
|
|
||||||
) {
|
|
||||||
const element = document.createElement(
|
|
||||||
`ha-form-${this.schema.type}`
|
|
||||||
) as HaFormElement;
|
|
||||||
element.schema = this.schema;
|
|
||||||
element.data = this.data;
|
|
||||||
element.label = this._computeLabel(this.schema);
|
|
||||||
element.suffix = this._computeSuffix(this.schema);
|
|
||||||
if (this._elementContainer!.lastChild) {
|
|
||||||
this._elementContainer!.removeChild(this._elementContainer!.lastChild);
|
|
||||||
}
|
|
||||||
this._elementContainer!.appendChild(element);
|
|
||||||
} else if (this._elementContainer && this._elementContainer.lastChild) {
|
|
||||||
const element = this._elementContainer!.lastChild as HaFormElement;
|
|
||||||
element.schema = this.schema;
|
|
||||||
element.data = this.data;
|
|
||||||
element.label = this._computeLabel(this.schema);
|
|
||||||
element.suffix = this._computeSuffix(this.schema);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeLabel(schema: HaFormSchema) {
|
private _computeLabel(schema: HaFormSchema) {
|
||||||
return this.computeLabel
|
return this.computeLabel
|
||||||
? this.computeLabel(schema)
|
? this.computeLabel(schema)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { safeDump, safeLoad } from "js-yaml";
|
import { safeDump, safeLoad } from "js-yaml";
|
||||||
import "./ha-code-editor";
|
import "./ha-code-editor";
|
||||||
import { LitElement, property, customElement, html } from "lit-element";
|
import { LitElement, property, customElement, html, query } from "lit-element";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { afterNextRender } from "../common/util/render-status";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { HaCodeEditor } from "./ha-code-editor";
|
||||||
|
|
||||||
const isEmpty = (obj: object) => {
|
const isEmpty = (obj: object) => {
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
@ -18,14 +21,23 @@ export class HaYamlEditor extends LitElement {
|
|||||||
@property() public isValid = true;
|
@property() public isValid = true;
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
@property() private _yaml?: string;
|
@property() private _yaml?: string;
|
||||||
|
@query("ha-code-editor") private _editor?: HaCodeEditor;
|
||||||
|
|
||||||
protected firstUpdated() {
|
public setValue(value) {
|
||||||
try {
|
try {
|
||||||
this._yaml =
|
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
||||||
this.value && !isEmpty(this.value) ? safeDump(this.value) : "";
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(`There was an error converting to YAML: ${err}`);
|
alert(`There was an error converting to YAML: ${err}`);
|
||||||
}
|
}
|
||||||
|
afterNextRender(() => {
|
||||||
|
if (this._editor?.codemirror) {
|
||||||
|
this._editor.codemirror.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated() {
|
||||||
|
this.setValue(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
@ -4,6 +4,8 @@ import {
|
|||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
|
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||||
|
import { Action } from "./script";
|
||||||
|
|
||||||
export interface AutomationEntity extends HassEntityBase {
|
export interface AutomationEntity extends HassEntityBase {
|
||||||
attributes: HassEntityAttributeBase & {
|
attributes: HassEntityAttributeBase & {
|
||||||
@ -15,11 +17,162 @@ export interface AutomationEntity extends HassEntityBase {
|
|||||||
export interface AutomationConfig {
|
export interface AutomationConfig {
|
||||||
alias: string;
|
alias: string;
|
||||||
description: string;
|
description: string;
|
||||||
trigger: any[];
|
trigger: Trigger[];
|
||||||
condition?: any[];
|
condition?: Condition[];
|
||||||
action: any[];
|
action: Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 LogicalCondition {
|
||||||
|
condition: "and" | "or";
|
||||||
|
conditions: Condition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StateCondition {
|
||||||
|
condition: "state";
|
||||||
|
entity_id: string;
|
||||||
|
state: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumericStateCondition {
|
||||||
|
condition: "numeric_state";
|
||||||
|
entity_id: string;
|
||||||
|
above?: number;
|
||||||
|
below?: number;
|
||||||
|
value_template?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SunCondition {
|
||||||
|
condition: "sun";
|
||||||
|
after_offset: number;
|
||||||
|
before_offset: number;
|
||||||
|
after: "sunrise" | "sunset";
|
||||||
|
before: "sunrise" | "sunset";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoneCondition {
|
||||||
|
condition: "zone";
|
||||||
|
entity_id: string;
|
||||||
|
zone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeCondition {
|
||||||
|
condition: "time";
|
||||||
|
after: string;
|
||||||
|
before: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemplateCondition {
|
||||||
|
condition: "template";
|
||||||
|
value_template: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Condition =
|
||||||
|
| StateCondition
|
||||||
|
| NumericStateCondition
|
||||||
|
| SunCondition
|
||||||
|
| ZoneCondition
|
||||||
|
| TimeCondition
|
||||||
|
| TemplateCondition
|
||||||
|
| DeviceCondition
|
||||||
|
| LogicalCondition;
|
||||||
|
|
||||||
export const deleteAutomation = (hass: HomeAssistant, id: string) =>
|
export const deleteAutomation = (hass: HomeAssistant, id: string) =>
|
||||||
hass.callApi("DELETE", `config/automation/config/${id}`);
|
hass.callApi("DELETE", `config/automation/config/${id}`);
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import { debounce } from "../common/util/debounce";
|
|||||||
import { getCollection, Connection } from "home-assistant-js-websocket";
|
import { getCollection, Connection } from "home-assistant-js-websocket";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
|
|
||||||
|
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
|
||||||
|
|
||||||
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
||||||
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
|
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
|
||||||
handler,
|
handler,
|
||||||
@ -26,6 +28,9 @@ export const handleConfigFlowStep = (
|
|||||||
data
|
data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ignoreConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||||
|
hass.callWS({ type: "config_entries/ignore_flow", flow_id: flowId });
|
||||||
|
|
||||||
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||||
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
||||||
|
|
||||||
|
@ -69,6 +69,10 @@ export interface NoActionConfig extends BaseActionConfig {
|
|||||||
action: "none";
|
action: "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomActionConfig extends BaseActionConfig {
|
||||||
|
action: "fire-dom-event";
|
||||||
|
}
|
||||||
|
|
||||||
export interface BaseActionConfig {
|
export interface BaseActionConfig {
|
||||||
confirmation?: ConfirmationRestrictionConfig;
|
confirmation?: ConfirmationRestrictionConfig;
|
||||||
}
|
}
|
||||||
@ -88,7 +92,8 @@ export type ActionConfig =
|
|||||||
| NavigateActionConfig
|
| NavigateActionConfig
|
||||||
| UrlActionConfig
|
| UrlActionConfig
|
||||||
| MoreInfoActionConfig
|
| MoreInfoActionConfig
|
||||||
| NoActionConfig;
|
| NoActionConfig
|
||||||
|
| CustomActionConfig;
|
||||||
|
|
||||||
export const fetchConfig = (
|
export const fetchConfig = (
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
|
@ -1,5 +1,21 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||||
|
import { Condition } from "./automation";
|
||||||
|
import {
|
||||||
|
HassEntityBase,
|
||||||
|
HassEntityAttributeBase,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
export interface ScriptEntity extends HassEntityBase {
|
||||||
|
attributes: HassEntityAttributeBase & {
|
||||||
|
last_triggered: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptConfig {
|
||||||
|
alias: string;
|
||||||
|
sequence: Action[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface EventAction {
|
export interface EventAction {
|
||||||
event: string;
|
event: string;
|
||||||
@ -7,12 +23,40 @@ export interface EventAction {
|
|||||||
event_data_template?: { [key: string]: any };
|
event_data_template?: { [key: string]: any };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServiceAction {
|
||||||
|
service: string;
|
||||||
|
entity_id?: string;
|
||||||
|
data?: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeviceAction {
|
export interface DeviceAction {
|
||||||
device_id: string;
|
device_id: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
entity_id: string;
|
entity_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DelayAction {
|
||||||
|
delay: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SceneAction {
|
||||||
|
scene: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WaitAction {
|
||||||
|
wait_template: string;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| EventAction
|
||||||
|
| DeviceAction
|
||||||
|
| ServiceAction
|
||||||
|
| Condition
|
||||||
|
| DelayAction
|
||||||
|
| SceneAction
|
||||||
|
| WaitAction;
|
||||||
|
|
||||||
export const triggerScript = (
|
export const triggerScript = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
|
@ -51,6 +51,12 @@ export interface ReadAttributeServiceData {
|
|||||||
manufacturer?: number;
|
manufacturer?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ZHAGroup {
|
||||||
|
name: string;
|
||||||
|
group_id: number;
|
||||||
|
members: ZHADevice[];
|
||||||
|
}
|
||||||
|
|
||||||
export const reconfigureNode = (
|
export const reconfigureNode = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
ieeeAddress: string
|
ieeeAddress: string
|
||||||
@ -153,3 +159,66 @@ export const fetchClustersForZhaNode = (
|
|||||||
type: "zha/devices/clusters",
|
type: "zha/devices/clusters",
|
||||||
ieee: ieeeAddress,
|
ieee: ieeeAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchGroups = (hass: HomeAssistant): Promise<ZHAGroup[]> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/groups",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeGroups = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupIdsToRemove: number[]
|
||||||
|
): Promise<ZHAGroup[]> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group/remove",
|
||||||
|
group_ids: groupIdsToRemove,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupId: number
|
||||||
|
): Promise<ZHAGroup> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group",
|
||||||
|
group_id: groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchGroupableDevices = (
|
||||||
|
hass: HomeAssistant
|
||||||
|
): Promise<ZHADevice[]> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/devices/groupable",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addMembersToGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupId: number,
|
||||||
|
membersToAdd: string[]
|
||||||
|
): Promise<ZHAGroup> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group/members/add",
|
||||||
|
group_id: groupId,
|
||||||
|
members: membersToAdd,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeMembersFromGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupId: number,
|
||||||
|
membersToRemove: string[]
|
||||||
|
): Promise<ZHAGroup> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group/members/remove",
|
||||||
|
group_id: groupId,
|
||||||
|
members: membersToRemove,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
groupName: string,
|
||||||
|
membersToAdd?: string[]
|
||||||
|
): Promise<ZHAGroup> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "zha/group/add",
|
||||||
|
group_name: groupName,
|
||||||
|
members: membersToAdd,
|
||||||
|
});
|
||||||
|
@ -109,6 +109,7 @@ class StepFlowForm extends LitElement {
|
|||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
setTimeout(() => this.shadowRoot!.querySelector("ha-form")!.focus(), 0);
|
||||||
this.addEventListener("keypress", (ev) => {
|
this.addEventListener("keypress", (ev) => {
|
||||||
if (ev.keyCode === 13) {
|
if (ev.keyCode === 13) {
|
||||||
this._submitStep();
|
this._submitStep();
|
||||||
|
@ -101,6 +101,14 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
setTimeout(
|
||||||
|
() => this.shadowRoot!.querySelector("search-input")!.focus(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProps) {
|
protected updated(changedProps) {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
// Store the width so that when we search, box doesn't jump
|
// Store the width so that when we search, box doesn't jump
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@material/mwc-button";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
@ -18,6 +19,8 @@ import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
|
|||||||
import { EventsMixin } from "../../mixins/events-mixin";
|
import { EventsMixin } from "../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
|
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
||||||
|
import { showConfirmationDialog } from "../confirmation/show-dialog-confirmation";
|
||||||
|
|
||||||
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
||||||
/*
|
/*
|
||||||
@ -57,6 +60,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mwc-button.warning {
|
||||||
|
--mdc-theme-primary: var(--google-red-500);
|
||||||
|
}
|
||||||
|
|
||||||
:host([domain="camera"]) paper-dialog-scrollable {
|
:host([domain="camera"]) paper-dialog-scrollable {
|
||||||
margin: 0 -24px -21px;
|
margin: 0 -24px -21px;
|
||||||
}
|
}
|
||||||
@ -117,6 +124,15 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
state-obj="[[stateObj]]"
|
state-obj="[[stateObj]]"
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
></more-info-content>
|
></more-info-content>
|
||||||
|
<template
|
||||||
|
is="dom-if"
|
||||||
|
if="[[_computeShowRestored(stateObj)]]"
|
||||||
|
restamp=""
|
||||||
|
>
|
||||||
|
[[localize('ui.dialogs.more_info_control.restored.not_provided')]] <br />
|
||||||
|
[[localize('ui.dialogs.more_info_control.restored.remove_intro')]] <br />
|
||||||
|
<mwc-button class="warning" on-click="_removeEntity">[[localize('ui.dialogs.more_info_control.restored.remove_action')]]</mwc-buttom>
|
||||||
|
</template>
|
||||||
</paper-dialog-scrollable>
|
</paper-dialog-scrollable>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -172,6 +188,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
return !stateObj || !DOMAINS_NO_INFO.includes(computeStateDomain(stateObj));
|
return !stateObj || !DOMAINS_NO_INFO.includes(computeStateDomain(stateObj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeShowRestored(stateObj) {
|
||||||
|
return stateObj && stateObj.attributes.restored;
|
||||||
|
}
|
||||||
|
|
||||||
_computeShowHistoryComponent(hass, stateObj) {
|
_computeShowHistoryComponent(hass, stateObj) {
|
||||||
return (
|
return (
|
||||||
hass &&
|
hass &&
|
||||||
@ -202,6 +222,21 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_removeEntity() {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.localize(
|
||||||
|
"ui.dialogs.more_info_control.restored.confirm_remove_title"
|
||||||
|
),
|
||||||
|
text: this.localize(
|
||||||
|
"ui.dialogs.more_info_control.restored.confirm_remove_text"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.localize("ui.common.yes"),
|
||||||
|
cancelBtnText: this.localize("ui.common.no"),
|
||||||
|
confirm: () =>
|
||||||
|
removeEntityRegistryEntry(this.hass, this.stateObj.entity_id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_gotoSettings() {
|
_gotoSettings() {
|
||||||
this.fire("more-info-page", { page: "settings" });
|
this.fire("more-info-page", { page: "settings" });
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,9 @@ class HassSubpage extends LitElement {
|
|||||||
height: 64px;
|
height: 64px;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--app-header-background-color);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--text-primary-color, white);
|
color: var(--app-header-text-color, white);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-menu-button,
|
ha-menu-button,
|
||||||
|
272
src/panels/config/automation/action/ha-automation-action-row.ts
Normal file
272
src/panels/config/automation/action/ha-automation-action-row.ts
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
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 { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
import { Action } from "../../../../data/script";
|
||||||
|
|
||||||
|
import "./types/ha-automation-action-service";
|
||||||
|
import "./types/ha-automation-action-device_id";
|
||||||
|
import "./types/ha-automation-action-delay";
|
||||||
|
import "./types/ha-automation-action-event";
|
||||||
|
import "./types/ha-automation-action-condition";
|
||||||
|
import "./types/ha-automation-action-scene";
|
||||||
|
import "./types/ha-automation-action-wait_template";
|
||||||
|
|
||||||
|
const OPTIONS = [
|
||||||
|
"condition",
|
||||||
|
"delay",
|
||||||
|
"device_id",
|
||||||
|
"event",
|
||||||
|
"scene",
|
||||||
|
"service",
|
||||||
|
"wait_template",
|
||||||
|
];
|
||||||
|
|
||||||
|
const getType = (action: Action) => {
|
||||||
|
return OPTIONS.find((option) => option in action);
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"move-action": { direction: "up" | "down" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActionElement extends LitElement {
|
||||||
|
action: Action;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const name = (ev.target as any)?.name;
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
|
if ((element.action[name] || "") === newVal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newAction: Action;
|
||||||
|
if (!newVal) {
|
||||||
|
newAction = { ...element.action };
|
||||||
|
delete newAction[name];
|
||||||
|
} else {
|
||||||
|
newAction = { ...element.action, [name]: newVal };
|
||||||
|
}
|
||||||
|
fireEvent(element, "value-changed", { value: newAction });
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-row")
|
||||||
|
export default class HaAutomationActionRow extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: Action;
|
||||||
|
@property() public index!: number;
|
||||||
|
@property() public totalActions!: number;
|
||||||
|
@property() private _yamlMode = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const type = getType(this.action);
|
||||||
|
const selected = type ? OPTIONS.indexOf(type) : -1;
|
||||||
|
const yamlMode = this._yamlMode || selected === -1;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-menu">
|
||||||
|
${this.index !== 0
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:arrow-up"
|
||||||
|
@click=${this._moveUp}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.index !== this.totalActions - 1
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:arrow-down"
|
||||||
|
@click=${this._moveDown}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<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}
|
||||||
|
.disabled=${selected === -1}
|
||||||
|
>
|
||||||
|
${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.actions.duplicate"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
<paper-item @click=${this._onDelete}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.delete"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-menu-button>
|
||||||
|
</div>
|
||||||
|
${yamlMode
|
||||||
|
? html`
|
||||||
|
<div style="margin-right: 24px;">
|
||||||
|
${selected === -1
|
||||||
|
? html`
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.unsupported_action",
|
||||||
|
"action",
|
||||||
|
type
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-yaml-editor
|
||||||
|
.value=${this.action}
|
||||||
|
@value-changed=${this._onYamlChange}
|
||||||
|
></ha-yaml-editor>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<paper-dropdown-menu-light
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type_select"
|
||||||
|
)}
|
||||||
|
no-animations
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected=${selected}
|
||||||
|
@iron-select=${this._typeChanged}
|
||||||
|
>
|
||||||
|
${OPTIONS.map(
|
||||||
|
(opt) => html`
|
||||||
|
<paper-item .action=${opt}>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.actions.type.${opt}.label`
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu-light>
|
||||||
|
<div>
|
||||||
|
${dynamicElement(`ha-automation-action-${type}`, {
|
||||||
|
hass: this.hass,
|
||||||
|
action: this.action,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _moveUp() {
|
||||||
|
fireEvent(this, "move-action", { direction: "up" });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _moveDown() {
|
||||||
|
fireEvent(this, "move-action", { direction: "down" });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDelete() {
|
||||||
|
if (
|
||||||
|
confirm(
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.delete_confirm"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fireEvent(this, "value-changed", { value: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _typeChanged(ev: CustomEvent) {
|
||||||
|
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||||
|
?.action;
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type !== getType(this.action)) {
|
||||||
|
const elClass = customElements.get(`ha-automation-action-${type}`);
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...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-action-row": HaAutomationActionRow;
|
||||||
|
}
|
||||||
|
}
|
98
src/panels/config/automation/action/ha-automation-action.ts
Normal file
98
src/panels/config/automation/action/ha-automation-action.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import { Action } from "../../../../data/script";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import "./ha-automation-action-row";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action")
|
||||||
|
export default class HaAutomationAction extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public actions!: Action[];
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
${this.actions.map(
|
||||||
|
(action, idx) => html`
|
||||||
|
<ha-automation-action-row
|
||||||
|
.index=${idx}
|
||||||
|
.totalActions=${this.actions.length}
|
||||||
|
.action=${action}
|
||||||
|
@move-action=${this._move}
|
||||||
|
@value-changed=${this._actionChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-action-row>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-actions add-card">
|
||||||
|
<mwc-button @click=${this._addAction}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.add"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addAction() {
|
||||||
|
const actions = this.actions.concat({
|
||||||
|
service: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: actions });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _move(ev: CustomEvent) {
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
const newIndex = ev.detail.direction === "up" ? index - 1 : index + 1;
|
||||||
|
const actions = this.actions.concat();
|
||||||
|
const action = actions.splice(index, 1)[0];
|
||||||
|
actions.splice(newIndex, 0, action);
|
||||||
|
fireEvent(this, "value-changed", { value: actions });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _actionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const actions = [...this.actions];
|
||||||
|
const newValue = ev.detail.value;
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
|
||||||
|
if (newValue === null) {
|
||||||
|
actions.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
actions[index] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: actions });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-automation-action-row,
|
||||||
|
ha-card {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.add-card mwc-button {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action": HaAutomationAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import "../../condition/ha-automation-condition-editor";
|
||||||
|
|
||||||
|
import { LitElement, property, customElement, html } from "lit-element";
|
||||||
|
import { ActionElement } from "../ha-automation-action-row";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { Condition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-condition")
|
||||||
|
export class HaConditionAction extends LitElement implements ActionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: Condition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { condition: "state" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<ha-automation-condition-editor
|
||||||
|
.condition=${this.action}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
></ha-automation-condition-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: ev.detail.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-condition": HaConditionAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "../../../../../components/ha-service-picker";
|
||||||
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../../../components/ha-yaml-editor";
|
||||||
|
|
||||||
|
import { LitElement, property, customElement, html } from "lit-element";
|
||||||
|
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { DelayAction } from "../../../../../data/script";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-delay")
|
||||||
|
export class HaDelayAction extends LitElement implements ActionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: DelayAction;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { delay: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { delay } = this.action;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.delay.delay"
|
||||||
|
)}
|
||||||
|
name="delay"
|
||||||
|
.value=${delay}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
handleChangeEvent(this, ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-delay": HaDelayAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
import "../../../../../components/device/ha-device-picker";
|
||||||
|
import "../../../../../components/device/ha-device-action-picker";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchDeviceActionCapabilities,
|
||||||
|
deviceAutomationsEqual,
|
||||||
|
DeviceAction,
|
||||||
|
} 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-action-device_id")
|
||||||
|
export class HaDeviceAction extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: DeviceAction;
|
||||||
|
@property() private _deviceId?: string;
|
||||||
|
@property() private _capabilities?;
|
||||||
|
private _origAction?: DeviceAction;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return {
|
||||||
|
device_id: "",
|
||||||
|
domain: "",
|
||||||
|
entity_id: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const deviceId = this._deviceId || this.action.device_id;
|
||||||
|
const extraFieldsData =
|
||||||
|
this._capabilities && this._capabilities.extra_fields
|
||||||
|
? this._capabilities.extra_fields.map((item) => {
|
||||||
|
return { [item.name]: this.action[item.name] };
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-device-picker
|
||||||
|
.value=${deviceId}
|
||||||
|
@value-changed=${this._devicePicked}
|
||||||
|
.hass=${this.hass}
|
||||||
|
label="Device"
|
||||||
|
></ha-device-picker>
|
||||||
|
<ha-device-action-picker
|
||||||
|
.value=${this.action}
|
||||||
|
.deviceId=${deviceId}
|
||||||
|
@value-changed=${this._deviceActionPicked}
|
||||||
|
.hass=${this.hass}
|
||||||
|
label="Action"
|
||||||
|
></ha-device-action-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.action) {
|
||||||
|
this._origAction = this.action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedPros) {
|
||||||
|
const prevAction = changedPros.get("action");
|
||||||
|
if (prevAction && !deviceAutomationsEqual(prevAction, this.action)) {
|
||||||
|
this._getCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getCapabilities() {
|
||||||
|
const action = this.action;
|
||||||
|
|
||||||
|
this._capabilities = action.domain
|
||||||
|
? await fetchDeviceActionCapabilities(this.hass, action)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _devicePicked(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._deviceId = ev.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _deviceActionPicked(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
let action = ev.detail.value;
|
||||||
|
if (this._origAction && deviceAutomationsEqual(this._origAction, action)) {
|
||||||
|
action = this._origAction;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", { value: action });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _extraFieldsChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.action,
|
||||||
|
...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.actions.type.device.extra_fields.${schema.name}`
|
||||||
|
) || schema.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-device_id": HaDeviceAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "../../../../../components/ha-service-picker";
|
||||||
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../../../components/ha-yaml-editor";
|
||||||
|
|
||||||
|
import { LitElement, property, customElement } from "lit-element";
|
||||||
|
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { html } from "lit-html";
|
||||||
|
import { EventAction } from "../../../../../data/script";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-event")
|
||||||
|
export class HaEventAction extends LitElement implements ActionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: EventAction;
|
||||||
|
|
||||||
|
public static get defaultConfig(): EventAction {
|
||||||
|
return { event: "", event_data: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { event, event_data } = this.action;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.event.event"
|
||||||
|
)}
|
||||||
|
name="event"
|
||||||
|
.value=${event}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
<ha-yaml-editor
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.event.service_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-action-event": HaEventAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
|
|
||||||
|
import { LitElement, property, customElement, html } from "lit-element";
|
||||||
|
import { ActionElement } from "../ha-automation-action-row";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { SceneAction } from "../../../../../data/script";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-scene")
|
||||||
|
export class HaSceneAction extends LitElement implements ActionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: SceneAction;
|
||||||
|
|
||||||
|
public static get defaultConfig(): SceneAction {
|
||||||
|
return { scene: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { scene } = this.action;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-entity-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${scene}
|
||||||
|
@value-changed=${this._entityPicked}
|
||||||
|
.includeDomains=${["scene"]}
|
||||||
|
allow-custom-entity
|
||||||
|
></ha-entity-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityPicked(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, scene: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-scene": HaSceneAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "../../../../../components/ha-service-picker";
|
||||||
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../../../components/ha-yaml-editor";
|
||||||
|
|
||||||
|
import { LitElement, property, customElement } from "lit-element";
|
||||||
|
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { html } from "lit-html";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||||
|
import { computeObjectId } from "../../../../../common/entity/compute_object_id";
|
||||||
|
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { ServiceAction } from "../../../../../data/script";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-service")
|
||||||
|
export class HaServiceAction extends LitElement implements ActionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: ServiceAction;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { service: "", data: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getServiceData = memoizeOne((service: string) => {
|
||||||
|
if (!service) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const domain = computeDomain(service);
|
||||||
|
const serviceName = computeObjectId(service);
|
||||||
|
const serviceDomains = this.hass.services;
|
||||||
|
if (!(domain in serviceDomains)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!(serviceName in serviceDomains[domain])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = serviceDomains[domain][serviceName].fields;
|
||||||
|
return Object.keys(fields).map((field) => {
|
||||||
|
return { key: field, ...fields[field] };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { service, data, entity_id } = this.action;
|
||||||
|
|
||||||
|
const serviceData = this._getServiceData(service);
|
||||||
|
const entity = serviceData.find((attr) => attr.key === "entity_id");
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-service-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${service}
|
||||||
|
@value-changed=${this._serviceChanged}
|
||||||
|
></ha-service-picker>
|
||||||
|
${entity
|
||||||
|
? html`
|
||||||
|
<ha-entity-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${entity_id}
|
||||||
|
.label=${entity.description}
|
||||||
|
@value-changed=${this._entityPicked}
|
||||||
|
.includeDomains=${[computeDomain(service)]}
|
||||||
|
allow-custom-entity
|
||||||
|
></ha-entity-picker>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-yaml-editor
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.service.service_data"
|
||||||
|
)}
|
||||||
|
.name=${"data"}
|
||||||
|
.value=${data}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-yaml-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
handleChangeEvent(this, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _serviceChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (ev.detail.value === this.action.service) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, service: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityPicked(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, entity_id: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-service": HaServiceAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
import { LitElement, property, customElement } from "lit-element";
|
||||||
|
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { html } from "lit-html";
|
||||||
|
import { WaitAction } from "../../../../../data/script";
|
||||||
|
|
||||||
|
@customElement("ha-automation-action-wait_template")
|
||||||
|
export class HaWaitAction extends LitElement implements ActionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public action!: WaitAction;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { wait_template: "", timeout: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { wait_template, timeout } = this.action;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-textarea
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
|
||||||
|
)}
|
||||||
|
name="wait_template"
|
||||||
|
.value=${wait_template}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
dir="ltr"
|
||||||
|
></ha-textarea>
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.wait_template.timeout"
|
||||||
|
)}
|
||||||
|
.name=${"timeout"}
|
||||||
|
.value=${timeout}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
handleChangeEvent(this, ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-action-wait_template": HaWaitAction;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
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 { customElement, html, LitElement, property } from "lit-element";
|
||||||
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
import "./types/ha-automation-condition-device";
|
||||||
|
import "./types/ha-automation-condition-state";
|
||||||
|
import "./types/ha-automation-condition-numeric_state";
|
||||||
|
import "./types/ha-automation-condition-sun";
|
||||||
|
import "./types/ha-automation-condition-template";
|
||||||
|
import "./types/ha-automation-condition-time";
|
||||||
|
import "./types/ha-automation-condition-zone";
|
||||||
|
import "./types/ha-automation-condition-and";
|
||||||
|
import "./types/ha-automation-condition-or";
|
||||||
|
import { Condition } from "../../../../data/automation";
|
||||||
|
|
||||||
|
const OPTIONS = [
|
||||||
|
"device",
|
||||||
|
"and",
|
||||||
|
"or",
|
||||||
|
"state",
|
||||||
|
"numeric_state",
|
||||||
|
"sun",
|
||||||
|
"template",
|
||||||
|
"time",
|
||||||
|
"zone",
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-editor")
|
||||||
|
export default class HaAutomationConditionEditor extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: Condition;
|
||||||
|
@property() public yamlMode = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const selected = OPTIONS.indexOf(this.condition.condition);
|
||||||
|
const yamlMode = this.yamlMode || selected === -1;
|
||||||
|
return html`
|
||||||
|
${yamlMode
|
||||||
|
? html`
|
||||||
|
<div style="margin-right: 24px;">
|
||||||
|
${selected === -1
|
||||||
|
? html`
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.unsupported_condition",
|
||||||
|
"condition",
|
||||||
|
this.condition.condition
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-yaml-editor
|
||||||
|
.value=${this.condition}
|
||||||
|
@value-changed=${this._onYamlChange}
|
||||||
|
></ha-yaml-editor>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<paper-dropdown-menu-light
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type_select"
|
||||||
|
)}
|
||||||
|
no-animations
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected=${selected}
|
||||||
|
@iron-select=${this._typeChanged}
|
||||||
|
>
|
||||||
|
${OPTIONS.map(
|
||||||
|
(opt) => html`
|
||||||
|
<paper-item .condition=${opt}>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.conditions.type.${opt}.label`
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu-light>
|
||||||
|
<div>
|
||||||
|
${dynamicElement(
|
||||||
|
`ha-automation-condition-${this.condition.condition}`,
|
||||||
|
{ hass: this.hass, condition: this.condition }
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _typeChanged(ev: CustomEvent) {
|
||||||
|
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||||
|
?.condition;
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elClass = customElements.get(`ha-automation-condition-${type}`);
|
||||||
|
|
||||||
|
if (type !== this.condition.condition) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
condition: type,
|
||||||
|
...elClass.defaultConfig,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onYamlChange(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-editor": HaAutomationConditionEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-menu-button/paper-menu-button";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
import "./ha-automation-condition-editor";
|
||||||
|
import { Condition } from "../../../../data/automation";
|
||||||
|
|
||||||
|
export interface ConditionElement extends LitElement {
|
||||||
|
condition: Condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleChangeEvent = (
|
||||||
|
element: ConditionElement,
|
||||||
|
ev: CustomEvent
|
||||||
|
) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const name = (ev.target as any)?.name;
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
|
if ((element.condition[name] || "") === newVal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newCondition: Condition;
|
||||||
|
if (!newVal) {
|
||||||
|
newCondition = { ...element.condition };
|
||||||
|
delete newCondition[name];
|
||||||
|
} else {
|
||||||
|
newCondition = { ...element.condition, [name]: newVal };
|
||||||
|
}
|
||||||
|
fireEvent(element, "value-changed", { value: newCondition });
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-row")
|
||||||
|
export default class HaAutomationConditionRow extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: Condition;
|
||||||
|
@property() private _yamlMode = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.condition) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
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.conditions.duplicate"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
<paper-item @click=${this._onDelete}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.delete"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-menu-button>
|
||||||
|
</div>
|
||||||
|
<ha-automation-condition-editor
|
||||||
|
.yamlMode=${this._yamlMode}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.condition=${this.condition}
|
||||||
|
></ha-automation-condition-editor>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDelete() {
|
||||||
|
if (
|
||||||
|
confirm(
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.delete_confirm"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fireEvent(this, "value-changed", { value: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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-condition-row": HaAutomationConditionRow;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
|
||||||
|
import { HaStateCondition } from "./types/ha-automation-condition-state";
|
||||||
|
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
import "./ha-automation-condition-row";
|
||||||
|
import { Condition } from "../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition")
|
||||||
|
export default class HaAutomationCondition extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public conditions!: Condition[];
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
${this.conditions.map(
|
||||||
|
(cond, idx) => html`
|
||||||
|
<ha-automation-condition-row
|
||||||
|
.index=${idx}
|
||||||
|
.condition=${cond}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-condition-row>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-actions add-card">
|
||||||
|
<mwc-button @click=${this._addCondition}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.add"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addCondition() {
|
||||||
|
const conditions = this.conditions.concat({
|
||||||
|
condition: "state",
|
||||||
|
...HaStateCondition.defaultConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: conditions });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const conditions = [...this.conditions];
|
||||||
|
const newValue = ev.detail.value;
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
|
||||||
|
if (newValue === null) {
|
||||||
|
conditions.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
conditions[index] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: conditions });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-automation-condition-row,
|
||||||
|
ha-card {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.add-card mwc-button {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition": HaAutomationCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { HaLogicalCondition } from "./ha-automation-condition-logical";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-and")
|
||||||
|
export class HaAndCondition extends HaLogicalCondition {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-and": HaAndCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
import "../../../../../components/device/ha-device-picker";
|
||||||
|
import "../../../../../components/device/ha-device-condition-picker";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchDeviceConditionCapabilities,
|
||||||
|
deviceAutomationsEqual,
|
||||||
|
DeviceCondition,
|
||||||
|
} 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-condition-device")
|
||||||
|
export class HaDeviceCondition extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: DeviceCondition;
|
||||||
|
@property() private _deviceId?: string;
|
||||||
|
@property() private _capabilities?;
|
||||||
|
private _origCondition?: DeviceCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return {
|
||||||
|
device_id: "",
|
||||||
|
domain: "",
|
||||||
|
entity_id: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const deviceId = this._deviceId || this.condition.device_id;
|
||||||
|
|
||||||
|
const extraFieldsData =
|
||||||
|
this._capabilities && this._capabilities.extra_fields
|
||||||
|
? this._capabilities.extra_fields.map((item) => {
|
||||||
|
return { [item.name]: this.condition[item.name] };
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-device-picker
|
||||||
|
.value=${deviceId}
|
||||||
|
@value-changed=${this._devicePicked}
|
||||||
|
.hass=${this.hass}
|
||||||
|
label="Device"
|
||||||
|
></ha-device-picker>
|
||||||
|
<ha-device-condition-picker
|
||||||
|
.value=${this.condition}
|
||||||
|
.deviceId=${deviceId}
|
||||||
|
@value-changed=${this._deviceConditionPicked}
|
||||||
|
.hass=${this.hass}
|
||||||
|
label="Condition"
|
||||||
|
></ha-device-condition-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.condition) {
|
||||||
|
this._origCondition = this.condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedPros) {
|
||||||
|
const prevCondition = changedPros.get("condition");
|
||||||
|
if (
|
||||||
|
prevCondition &&
|
||||||
|
!deviceAutomationsEqual(prevCondition, this.condition)
|
||||||
|
) {
|
||||||
|
this._getCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getCapabilities() {
|
||||||
|
const condition = this.condition;
|
||||||
|
|
||||||
|
this._capabilities = condition.domain
|
||||||
|
? await fetchDeviceConditionCapabilities(this.hass, condition)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _devicePicked(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._deviceId = ev.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _deviceConditionPicked(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
let condition = ev.detail.value;
|
||||||
|
if (
|
||||||
|
this._origCondition &&
|
||||||
|
deviceAutomationsEqual(this._origCondition, condition)
|
||||||
|
) {
|
||||||
|
condition = this._origCondition;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _extraFieldsChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
...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.conditions.type.device.extra_fields.${schema.name}`
|
||||||
|
) || schema.name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import { customElement, html, LitElement, property } from "lit-element";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { ConditionElement } from "../ha-automation-condition-row";
|
||||||
|
import "../ha-automation-condition";
|
||||||
|
import { LogicalCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-logical")
|
||||||
|
export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: LogicalCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { conditions: [{ condition: "state" }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-automation-condition
|
||||||
|
.conditions=${this.condition.conditions || []}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-condition>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.condition, conditions: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-logical": HaLogicalCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
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 { handleChangeEvent } from "../ha-automation-condition-row";
|
||||||
|
import { NumericStateCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-numeric_state")
|
||||||
|
export default class HaNumericStateCondition extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: NumericStateCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return {
|
||||||
|
entity_id: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { value_template, entity_id, below, above } = this.condition;
|
||||||
|
|
||||||
|
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.conditions.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.conditions.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.conditions.type.numeric_state.value_template"
|
||||||
|
)}
|
||||||
|
name="value_template"
|
||||||
|
.value=${value_template}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
dir="ltr"
|
||||||
|
></ha-textarea>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
handleChangeEvent(this, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityPicked(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.condition, entity_id: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-numeric_state": HaNumericStateCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { HaLogicalCondition } from "./ha-automation-condition-logical";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-or")
|
||||||
|
export class HaOrCondition extends HaLogicalCondition {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-or": HaOrCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
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,
|
||||||
|
ConditionElement,
|
||||||
|
} from "../ha-automation-condition-row";
|
||||||
|
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
|
import { StateCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-state")
|
||||||
|
export class HaStateCondition extends LitElement implements ConditionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: StateCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { entity_id: "", state: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { entity_id, state } = this.condition;
|
||||||
|
|
||||||
|
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.conditions.type.state.state"
|
||||||
|
)}
|
||||||
|
.name=${"state"}
|
||||||
|
.value=${state}
|
||||||
|
@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.condition, entity_id: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-state": HaStateCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
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 {
|
||||||
|
handleChangeEvent,
|
||||||
|
ConditionElement,
|
||||||
|
} from "../ha-automation-condition-row";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { SunCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-sun")
|
||||||
|
export class HaSunCondition extends LitElement implements ConditionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: SunCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { after, after_offset, before, before_offset } = this.condition;
|
||||||
|
return html`
|
||||||
|
<label id="beforelabel">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.before"
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<paper-radio-group
|
||||||
|
.selected=${before}
|
||||||
|
.name=${"before"}
|
||||||
|
aria-labelledby="beforelabel"
|
||||||
|
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||||
|
>
|
||||||
|
<paper-radio-button name="sunrise">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||||
|
)}
|
||||||
|
</paper-radio-button>
|
||||||
|
<paper-radio-button name="sunset">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||||
|
)}
|
||||||
|
</paper-radio-button>
|
||||||
|
</paper-radio-group>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.before_offset"
|
||||||
|
)}
|
||||||
|
name="before_offset"
|
||||||
|
.value=${before_offset}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<label id="afterlabel">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.after"
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<paper-radio-group
|
||||||
|
.selected=${after}
|
||||||
|
.name=${"after"}
|
||||||
|
aria-labelledby="afterlabel"
|
||||||
|
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||||
|
>
|
||||||
|
<paper-radio-button name="sunrise">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||||
|
)}
|
||||||
|
</paper-radio-button>
|
||||||
|
<paper-radio-button name="sunset">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||||
|
)}
|
||||||
|
</paper-radio-button>
|
||||||
|
</paper-radio-group>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.sun.after_offset"
|
||||||
|
)}
|
||||||
|
name="after_offset"
|
||||||
|
.value=${after_offset}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
handleChangeEvent(this, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _radioGroupPicked(ev) {
|
||||||
|
const key = ev.target.name;
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
[key]: (ev.target as PaperRadioGroupElement).selected,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import "../../../../../components/ha-textarea";
|
||||||
|
import { LitElement, property, html, customElement } from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { handleChangeEvent } from "../ha-automation-condition-row";
|
||||||
|
import { TemplateCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-template")
|
||||||
|
export class HaTemplateCondition extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: TemplateCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return { value_template: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { value_template } = this.condition;
|
||||||
|
return html`
|
||||||
|
<ha-textarea
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.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,44 @@
|
|||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import { LitElement, html, property, customElement } from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import {
|
||||||
|
handleChangeEvent,
|
||||||
|
ConditionElement,
|
||||||
|
} from "../ha-automation-condition-row";
|
||||||
|
import { TimeCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-time")
|
||||||
|
export class HaTimeCondition extends LitElement implements ConditionElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: TimeCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { after, before } = this.condition;
|
||||||
|
return html`
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.time.after"
|
||||||
|
)}
|
||||||
|
name="after"
|
||||||
|
.value=${after}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.type.time.before"
|
||||||
|
)}
|
||||||
|
name="before"
|
||||||
|
.value=${before}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
handleChangeEvent(this, ev);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import "@polymer/paper-radio-button/paper-radio-button";
|
||||||
|
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 { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { ZoneCondition } from "../../../../../data/automation";
|
||||||
|
|
||||||
|
function zoneAndLocationFilter(stateObj) {
|
||||||
|
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-automation-condition-zone")
|
||||||
|
export class HaZoneCondition extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public condition!: ZoneCondition;
|
||||||
|
|
||||||
|
public static get defaultConfig() {
|
||||||
|
return {
|
||||||
|
entity_id: "",
|
||||||
|
zone: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { entity_id, zone } = this.condition;
|
||||||
|
return html`
|
||||||
|
<ha-entity-picker
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.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.conditions.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.conditions.type.zone.event"
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityPicked(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.condition, entity_id: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _zonePicked(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.condition, zone: ev.detail.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-condition-zone": HaZoneCondition;
|
||||||
|
}
|
||||||
|
}
|
@ -1,42 +1,37 @@
|
|||||||
import {
|
|
||||||
LitElement,
|
|
||||||
TemplateResult,
|
|
||||||
html,
|
|
||||||
CSSResult,
|
|
||||||
css,
|
|
||||||
PropertyValues,
|
|
||||||
property,
|
|
||||||
} from "lit-element";
|
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
|
||||||
|
|
||||||
import { h, render } from "preact";
|
|
||||||
|
|
||||||
import "../../../components/ha-fab";
|
|
||||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
|
||||||
import "../../../layouts/ha-app-layout";
|
|
||||||
|
|
||||||
import Automation from "../js/automation";
|
|
||||||
import unmountPreact from "../../../common/preact/unmount";
|
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
|
||||||
|
|
||||||
import { haStyle } from "../../../resources/styles";
|
|
||||||
import { HomeAssistant } from "../../../types";
|
|
||||||
import {
|
import {
|
||||||
AutomationEntity,
|
css,
|
||||||
AutomationConfig,
|
CSSResult,
|
||||||
deleteAutomation,
|
html,
|
||||||
getAutomationEditorInitData,
|
LitElement,
|
||||||
} from "../../../data/automation";
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
|
import "../../../components/ha-fab";
|
||||||
|
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||||
|
import {
|
||||||
|
AutomationConfig,
|
||||||
|
AutomationEntity,
|
||||||
|
Condition,
|
||||||
|
deleteAutomation,
|
||||||
|
getAutomationEditorInitData,
|
||||||
|
Trigger,
|
||||||
|
} from "../../../data/automation";
|
||||||
|
import { Action } from "../../../data/script";
|
||||||
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
|
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
|
||||||
|
import "../../../layouts/ha-app-layout";
|
||||||
function AutomationEditor(mountEl, props, mergeEl) {
|
import { haStyle } from "../../../resources/styles";
|
||||||
return render(h(Automation, props), mountEl, mergeEl);
|
import { HomeAssistant } from "../../../types";
|
||||||
}
|
import "./action/ha-automation-action";
|
||||||
|
import "./condition/ha-automation-condition";
|
||||||
|
import "./trigger/ha-automation-trigger";
|
||||||
|
|
||||||
export class HaAutomationEditor extends LitElement {
|
export class HaAutomationEditor extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -45,26 +40,9 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
@property() public creatingNew?: boolean;
|
@property() public creatingNew?: boolean;
|
||||||
@property() private _config?: AutomationConfig;
|
@property() private _config?: AutomationConfig;
|
||||||
@property() private _dirty?: boolean;
|
@property() private _dirty?: boolean;
|
||||||
private _rendered?: unknown;
|
|
||||||
@property() private _errors?: string;
|
@property() private _errors?: string;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._configChanged = this._configChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
if (this._rendered) {
|
|
||||||
unmountPreact(this._rendered);
|
|
||||||
this._rendered = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
<ha-app-layout has-scrolling-region>
|
<ha-app-layout has-scrolling-region>
|
||||||
<app-header slot="header" fixed>
|
<app-header slot="header" fixed>
|
||||||
@ -100,11 +78,131 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div
|
<div
|
||||||
id="root"
|
|
||||||
class="${classMap({
|
class="${classMap({
|
||||||
rtl: computeRTL(this.hass),
|
rtl: computeRTL(this.hass),
|
||||||
})}"
|
})}"
|
||||||
></div>
|
>
|
||||||
|
${this._config
|
||||||
|
? html`
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">${this._config.alias}</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.introduction"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.alias"
|
||||||
|
)}
|
||||||
|
name="alias"
|
||||||
|
.value=${this._config.alias}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
>
|
||||||
|
</paper-input>
|
||||||
|
<ha-textarea
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.description.label"
|
||||||
|
)}
|
||||||
|
.placeholder=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.description.placeholder"
|
||||||
|
)}
|
||||||
|
name="description"
|
||||||
|
.value=${this._config.description}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-textarea>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.header"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.introduction"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://home-assistant.io/docs/automation/trigger/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ha-automation-trigger
|
||||||
|
.triggers=${this._config.trigger}
|
||||||
|
@value-changed=${this._triggerChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-trigger>
|
||||||
|
</ha-config-section>
|
||||||
|
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.header"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.introduction"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://home-assistant.io/docs/scripts/conditions/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ha-automation-condition
|
||||||
|
.conditions=${this._config.condition || []}
|
||||||
|
@value-changed=${this._conditionChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-condition>
|
||||||
|
</ha-config-section>
|
||||||
|
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.header"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.introduction"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://home-assistant.io/docs/automation/action/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.learn_more"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ha-automation-action
|
||||||
|
.actions=${this._config.action}
|
||||||
|
@value-changed=${this._actionChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-action>
|
||||||
|
</ha-config-section>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
@ -184,28 +282,40 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
...initData,
|
...initData,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has("_config") && this.hass) {
|
|
||||||
this._rendered = AutomationEditor(
|
|
||||||
this.shadowRoot!.querySelector("#root"),
|
|
||||||
{
|
|
||||||
automation: this._config,
|
|
||||||
onChange: this._configChanged,
|
|
||||||
isWide: this.isWide,
|
|
||||||
hass: this.hass,
|
|
||||||
localize: this.hass.localize,
|
|
||||||
},
|
|
||||||
this._rendered
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configChanged(config: AutomationConfig): void {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
// onChange gets called a lot during initial rendering causing recursing calls.
|
ev.stopPropagation();
|
||||||
if (!this._rendered) {
|
const name = (ev.target as any)?.name;
|
||||||
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._config = config;
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
|
if ((this._config![name] || "") === newVal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._config = { ...this._config!, [name]: newVal };
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _triggerChanged(ev: CustomEvent): void {
|
||||||
|
this._config = { ...this._config!, trigger: ev.detail.value as Trigger[] };
|
||||||
|
this._errors = undefined;
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _conditionChanged(ev: CustomEvent): void {
|
||||||
|
this._config = {
|
||||||
|
...this._config!,
|
||||||
|
condition: ev.detail.value as Condition[],
|
||||||
|
};
|
||||||
|
this._errors = undefined;
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _actionChanged(ev: CustomEvent): void {
|
||||||
|
this._config = { ...this._config!, action: ev.detail.value as Action[] };
|
||||||
this._errors = undefined;
|
this._errors = undefined;
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
@ -275,32 +385,6 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
.content {
|
.content {
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
.triggers,
|
|
||||||
.script {
|
|
||||||
margin-top: -16px;
|
|
||||||
}
|
|
||||||
.triggers ha-card,
|
|
||||||
.script ha-card {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
.add-card mwc-button {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.card-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 1;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
.rtl .card-menu {
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.card-menu paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
span[slot="introduction"] a {
|
span[slot="introduction"] a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { dynamicContentDirective } from "../../../../common/dom/dynamic-content-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
@ -30,7 +30,8 @@ import "./types/ha-automation-trigger-time";
|
|||||||
import "./types/ha-automation-trigger-time_pattern";
|
import "./types/ha-automation-trigger-time_pattern";
|
||||||
import "./types/ha-automation-trigger-webhook";
|
import "./types/ha-automation-trigger-webhook";
|
||||||
import "./types/ha-automation-trigger-zone";
|
import "./types/ha-automation-trigger-zone";
|
||||||
import { DeviceTrigger } from "../../../../data/device_automation";
|
|
||||||
|
import { Trigger } from "../../../../data/automation";
|
||||||
|
|
||||||
const OPTIONS = [
|
const OPTIONS = [
|
||||||
"device",
|
"device",
|
||||||
@ -48,103 +49,6 @@ const OPTIONS = [
|
|||||||
"zone",
|
"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 {
|
export interface TriggerElement extends LitElement {
|
||||||
trigger: Trigger;
|
trigger: Trigger;
|
||||||
}
|
}
|
||||||
@ -178,14 +82,9 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
@property() private _yamlMode = false;
|
@property() private _yamlMode = false;
|
||||||
|
|
||||||
protected render() {
|
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);
|
const selected = OPTIONS.indexOf(this.trigger.platform);
|
||||||
|
const yamlMode = this._yamlMode || selected === -1;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@ -202,8 +101,11 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
slot="dropdown-trigger"
|
slot="dropdown-trigger"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
<paper-listbox slot="dropdown-content">
|
<paper-listbox slot="dropdown-content">
|
||||||
<paper-item @click=${this._switchYamlMode}>
|
<paper-item
|
||||||
${this._yamlMode
|
@click=${this._switchYamlMode}
|
||||||
|
.disabled=${selected === -1}
|
||||||
|
>
|
||||||
|
${yamlMode
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.edit_ui"
|
"ui.panel.config.automation.editor.edit_ui"
|
||||||
)
|
)
|
||||||
@ -224,10 +126,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-menu-button>
|
</paper-menu-button>
|
||||||
</div>
|
</div>
|
||||||
${this._yamlMode
|
${yamlMode
|
||||||
? html`
|
? html`
|
||||||
<div style="margin-right: 24px;">
|
<div style="margin-right: 24px;">
|
||||||
${!hasEditor
|
${selected === -1
|
||||||
? html`
|
? html`
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.triggers.unsupported_platform",
|
"ui.panel.config.automation.editor.triggers.unsupported_platform",
|
||||||
@ -266,7 +168,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu-light>
|
</paper-dropdown-menu-light>
|
||||||
<div>
|
<div>
|
||||||
${dynamicContentDirective(
|
${dynamicElement(
|
||||||
`ha-automation-trigger-${this.trigger.platform}`,
|
`ha-automation-trigger-${this.trigger.platform}`,
|
||||||
{ hass: this.hass, trigger: this.trigger }
|
{ hass: this.hass, trigger: this.trigger }
|
||||||
)}
|
)}
|
||||||
|
@ -13,41 +13,42 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
|||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
import "./ha-automation-trigger-row";
|
import "./ha-automation-trigger-row";
|
||||||
|
import { HaStateTrigger } from "./types/ha-automation-trigger-state";
|
||||||
|
import { Trigger } from "../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger")
|
@customElement("ha-automation-trigger")
|
||||||
export default class HaAutomationTrigger extends LitElement {
|
export default class HaAutomationTrigger extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public triggers;
|
@property() public triggers!: Trigger[];
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="triggers">
|
${this.triggers.map(
|
||||||
${this.triggers.map(
|
(trg, idx) => html`
|
||||||
(trg, idx) => html`
|
<ha-automation-trigger-row
|
||||||
<ha-automation-trigger-row
|
.index=${idx}
|
||||||
.index=${idx}
|
.trigger=${trg}
|
||||||
.trigger=${trg}
|
@value-changed=${this._triggerChanged}
|
||||||
@value-changed=${this._triggerChanged}
|
.hass=${this.hass}
|
||||||
.hass=${this.hass}
|
></ha-automation-trigger-row>
|
||||||
></ha-automation-trigger-row>
|
`
|
||||||
`
|
)}
|
||||||
)}
|
<ha-card>
|
||||||
<ha-card>
|
<div class="card-actions add-card">
|
||||||
<div class="card-actions add-card">
|
<mwc-button @click=${this._addTrigger}>
|
||||||
<mwc-button @click=${this._addTrigger}>
|
${this.hass.localize(
|
||||||
${this.hass.localize(
|
"ui.panel.config.automation.editor.triggers.add"
|
||||||
"ui.panel.config.automation.editor.triggers.add"
|
)}
|
||||||
)}
|
</mwc-button>
|
||||||
</mwc-button>
|
</div>
|
||||||
</div>
|
</ha-card>
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addTrigger() {
|
private _addTrigger() {
|
||||||
const triggers = this.triggers.concat({
|
const triggers = this.triggers.concat({
|
||||||
platform: "state",
|
platform: "state",
|
||||||
|
...HaStateTrigger.defaultConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: triggers });
|
fireEvent(this, "value-changed", { value: triggers });
|
||||||
@ -70,12 +71,9 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
.triggers,
|
ha-automation-trigger-row,
|
||||||
.script {
|
ha-card {
|
||||||
margin-top: -16px;
|
display: block;
|
||||||
}
|
|
||||||
.triggers ha-card,
|
|
||||||
.script ha-card {
|
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.add-card mwc-button {
|
.add-card mwc-button {
|
||||||
|
@ -28,9 +28,8 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (this._deviceId === undefined) {
|
const deviceId = this._deviceId || this.trigger.device_id;
|
||||||
this._deviceId = this.trigger.device_id;
|
|
||||||
}
|
|
||||||
const extraFieldsData =
|
const extraFieldsData =
|
||||||
this._capabilities && this._capabilities.extra_fields
|
this._capabilities && this._capabilities.extra_fields
|
||||||
? this._capabilities.extra_fields.map((item) => {
|
? this._capabilities.extra_fields.map((item) => {
|
||||||
@ -40,14 +39,14 @@ export class HaDeviceTrigger extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
.value=${this._deviceId}
|
.value=${deviceId}
|
||||||
@value-changed=${this._devicePicked}
|
@value-changed=${this._devicePicked}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
label="Device"
|
label="Device"
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
<ha-device-trigger-picker
|
<ha-device-trigger-picker
|
||||||
.value=${this.trigger}
|
.value=${this.trigger}
|
||||||
.deviceId=${this._deviceId}
|
.deviceId=${deviceId}
|
||||||
@value-changed=${this._deviceTriggerPicked}
|
@value-changed=${this._deviceTriggerPicked}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
label="Trigger"
|
label="Trigger"
|
||||||
|
@ -4,11 +4,11 @@ import "../../../../../components/ha-yaml-editor";
|
|||||||
import { LitElement, property, customElement } from "lit-element";
|
import { LitElement, property, customElement } from "lit-element";
|
||||||
import {
|
import {
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
EventTrigger,
|
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { html } from "lit-html";
|
import { html } from "lit-html";
|
||||||
|
import { EventTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-event")
|
@customElement("ha-automation-trigger-event")
|
||||||
export class HaEventTrigger extends LitElement implements TriggerElement {
|
export class HaEventTrigger extends LitElement implements TriggerElement {
|
||||||
|
@ -5,11 +5,9 @@ import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-g
|
|||||||
import "../../../../../components/entity/ha-entity-picker";
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
import { LitElement, customElement, property, html } from "lit-element";
|
import { LitElement, customElement, property, html } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import {
|
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||||
GeoLocationTrigger,
|
|
||||||
handleChangeEvent,
|
|
||||||
} from "../ha-automation-trigger-row";
|
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { GeoLocationTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-geo_location")
|
@customElement("ha-automation-trigger-geo_location")
|
||||||
export default class HaGeolocationTrigger extends LitElement {
|
export default class HaGeolocationTrigger extends LitElement {
|
||||||
|
@ -5,7 +5,7 @@ import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-g
|
|||||||
import { LitElement, html, property, customElement } from "lit-element";
|
import { LitElement, html, property, customElement } from "lit-element";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { HassTrigger } from "../ha-automation-trigger-row";
|
import { HassTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-homeassistant")
|
@customElement("ha-automation-trigger-homeassistant")
|
||||||
export default class HaHassTrigger extends LitElement {
|
export default class HaHassTrigger extends LitElement {
|
||||||
|
@ -4,8 +4,8 @@ import { HomeAssistant } from "../../../../../types";
|
|||||||
import {
|
import {
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
MqttTrigger,
|
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
|
import { MqttTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-mqtt")
|
@customElement("ha-automation-trigger-mqtt")
|
||||||
export class HaMQTTTrigger extends LitElement implements TriggerElement {
|
export class HaMQTTTrigger extends LitElement implements TriggerElement {
|
||||||
|
@ -5,11 +5,8 @@ import "../../../../../components/entity/ha-entity-picker";
|
|||||||
import { LitElement, html, customElement, property } from "lit-element";
|
import { LitElement, html, customElement, property } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import {
|
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||||
NumericStateTrigger,
|
import { NumericStateTrigger, ForDict } from "../../../../../data/automation";
|
||||||
ForDict,
|
|
||||||
handleChangeEvent,
|
|
||||||
} from "../ha-automation-trigger-row";
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-numeric_state")
|
@customElement("ha-automation-trigger-numeric_state")
|
||||||
export default class HaNumericStateTrigger extends LitElement {
|
export default class HaNumericStateTrigger extends LitElement {
|
||||||
|
@ -6,10 +6,9 @@ import { HomeAssistant } from "../../../../../types";
|
|||||||
import {
|
import {
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
StateTrigger,
|
|
||||||
ForDict,
|
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
|
import { StateTrigger, ForDict } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-state")
|
@customElement("ha-automation-trigger-state")
|
||||||
export class HaStateTrigger extends LitElement implements TriggerElement {
|
export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||||
|
@ -6,11 +6,11 @@ import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-g
|
|||||||
import { LitElement, customElement, property, html } from "lit-element";
|
import { LitElement, customElement, property, html } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import {
|
import {
|
||||||
SunTrigger,
|
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { SunTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-sun")
|
@customElement("ha-automation-trigger-sun")
|
||||||
export class HaSunTrigger extends LitElement implements TriggerElement {
|
export class HaSunTrigger extends LitElement implements TriggerElement {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import "../../../../../components/ha-textarea";
|
import "../../../../../components/ha-textarea";
|
||||||
import { LitElement, property, html, customElement } from "lit-element";
|
import { LitElement, property, html, customElement } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import {
|
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||||
TemplateTrigger,
|
import { TemplateTrigger } from "../../../../../data/automation";
|
||||||
handleChangeEvent,
|
|
||||||
} from "../ha-automation-trigger-row";
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-template")
|
@customElement("ha-automation-trigger-template")
|
||||||
export class HaTemplateTrigger extends LitElement {
|
export class HaTemplateTrigger extends LitElement {
|
||||||
|
@ -2,10 +2,10 @@ import "@polymer/paper-input/paper-input";
|
|||||||
import { LitElement, html, property, customElement } from "lit-element";
|
import { LitElement, html, property, customElement } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import {
|
import {
|
||||||
TimeTrigger,
|
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
|
import { TimeTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-time")
|
@customElement("ha-automation-trigger-time")
|
||||||
export class HaTimeTrigger extends LitElement implements TriggerElement {
|
export class HaTimeTrigger extends LitElement implements TriggerElement {
|
||||||
|
@ -3,9 +3,9 @@ import { LitElement, property, html, customElement } from "lit-element";
|
|||||||
import {
|
import {
|
||||||
TriggerElement,
|
TriggerElement,
|
||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
TimePatternTrigger,
|
|
||||||
} from "../ha-automation-trigger-row";
|
} from "../ha-automation-trigger-row";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { TimePatternTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-time_pattern")
|
@customElement("ha-automation-trigger-time_pattern")
|
||||||
export class HaTimePatternTrigger extends LitElement implements TriggerElement {
|
export class HaTimePatternTrigger extends LitElement implements TriggerElement {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import { LitElement, customElement, property, html } from "lit-element";
|
import { LitElement, customElement, property, html } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import {
|
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||||
WebhookTrigger,
|
import { WebhookTrigger } from "../../../../../data/automation";
|
||||||
handleChangeEvent,
|
|
||||||
} from "../ha-automation-trigger-row";
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-webhook")
|
@customElement("ha-automation-trigger-webhook")
|
||||||
export class HaWebhookTrigger extends LitElement {
|
export class HaWebhookTrigger extends LitElement {
|
||||||
|
@ -8,9 +8,9 @@ import { hasLocation } from "../../../../../common/entity/has_location";
|
|||||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||||
import { LitElement, property, html, customElement } from "lit-element";
|
import { LitElement, property, html, customElement } from "lit-element";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { ZoneTrigger } from "../ha-automation-trigger-row";
|
|
||||||
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { ZoneTrigger } from "../../../../../data/automation";
|
||||||
|
|
||||||
function zoneAndLocationFilter(stateObj) {
|
function zoneAndLocationFilter(stateObj) {
|
||||||
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
|
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
|
||||||
|
@ -27,6 +27,7 @@ import { domainIcon } from "../../../../common/entity/domain_icon";
|
|||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { HaSwitch } from "../../../../components/ha-switch";
|
import { HaSwitch } from "../../../../components/ha-switch";
|
||||||
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
||||||
|
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
|
||||||
|
|
||||||
@customElement("ha-device-entities-card")
|
@customElement("ha-device-entities-card")
|
||||||
export class HaDeviceEntitiesCard extends LitElement {
|
export class HaDeviceEntitiesCard extends LitElement {
|
||||||
@ -49,51 +50,60 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
</ha-switch>
|
</ha-switch>
|
||||||
</paper-item>
|
</paper-item>
|
||||||
${this.entities.length
|
${this.entities.length
|
||||||
? this.entities.map((entry: EntityRegistryStateEntry) => {
|
? html`
|
||||||
if (!this._showDisabled && entry.disabled_by) {
|
${this.entities.map((entry: EntityRegistryStateEntry) => {
|
||||||
return "";
|
if (!this._showDisabled && entry.disabled_by) {
|
||||||
}
|
return "";
|
||||||
const stateObj = this.hass.states[entry.entity_id];
|
}
|
||||||
return html`
|
const stateObj = this.hass.states[entry.entity_id];
|
||||||
<paper-icon-item
|
return html`
|
||||||
.entry=${entry}
|
<paper-icon-item
|
||||||
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
|
.entry=${entry}
|
||||||
>
|
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
|
||||||
${stateObj
|
>
|
||||||
? html`
|
|
||||||
<state-badge
|
|
||||||
@click=${this._openMoreInfo}
|
|
||||||
.stateObj=${stateObj}
|
|
||||||
slot="item-icon"
|
|
||||||
></state-badge>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<ha-icon
|
|
||||||
slot="item-icon"
|
|
||||||
.icon=${domainIcon(computeDomain(entry.entity_id))}
|
|
||||||
></ha-icon>
|
|
||||||
`}
|
|
||||||
<paper-item-body two-line @click=${this._openMoreInfo}>
|
|
||||||
<div class="name">${entry.stateName}</div>
|
|
||||||
<div class="secondary entity-id">${entry.entity_id}</div>
|
|
||||||
</paper-item-body>
|
|
||||||
<div class="buttons">
|
|
||||||
${stateObj
|
${stateObj
|
||||||
? html`
|
? html`
|
||||||
<paper-icon-button
|
<state-badge
|
||||||
@click=${this._openMoreInfo}
|
@click=${this._openMoreInfo}
|
||||||
icon="hass:information-outline"
|
.stateObj=${stateObj}
|
||||||
></paper-icon-button>
|
slot="item-icon"
|
||||||
|
></state-badge>
|
||||||
`
|
`
|
||||||
: ""}
|
: html`
|
||||||
<paper-icon-button
|
<ha-icon
|
||||||
@click=${this._openEditEntry}
|
slot="item-icon"
|
||||||
icon="hass:settings"
|
.icon=${domainIcon(computeDomain(entry.entity_id))}
|
||||||
></paper-icon-button>
|
></ha-icon>
|
||||||
</div>
|
`}
|
||||||
</paper-icon-item>
|
<paper-item-body two-line @click=${this._openMoreInfo}>
|
||||||
`;
|
<div class="name">${entry.stateName}</div>
|
||||||
})
|
<div class="secondary entity-id">${entry.entity_id}</div>
|
||||||
|
</paper-item-body>
|
||||||
|
<div class="buttons">
|
||||||
|
${stateObj
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
@click=${this._openMoreInfo}
|
||||||
|
icon="hass:information-outline"
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<paper-icon-button
|
||||||
|
@click=${this._openEditEntry}
|
||||||
|
icon="hass:settings"
|
||||||
|
></paper-icon-button>
|
||||||
|
</div>
|
||||||
|
</paper-icon-item>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button @click=${this._addToLovelaceView}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.devices.entities.add_entities_lovelace"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
: html`
|
: html`
|
||||||
<div class="config-entry-row">
|
<div class="config-entry-row">
|
||||||
<paper-item-body two-line>
|
<paper-item-body two-line>
|
||||||
@ -125,6 +135,14 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
fireEvent(this, "hass-more-info", { entityId: entry.entity_id });
|
fireEvent(this, "hass-more-info", { entityId: entry.entity_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addToLovelaceView(): void {
|
||||||
|
addEntitiesToLovelaceView(
|
||||||
|
this,
|
||||||
|
this.hass,
|
||||||
|
this.entities.map((entity) => entity.entity_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
ha-icon {
|
ha-icon {
|
||||||
|
@ -156,7 +156,9 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
${entities.length
|
${entities.length
|
||||||
? html`
|
? html`
|
||||||
<div class="header">
|
<div class="header">
|
||||||
${this.hass.localize("ui.panel.config.devices.entities")}
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.devices.entities.entities"
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ha-device-entities-card
|
<ha-device-entities-card
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -31,7 +31,6 @@ import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialo
|
|||||||
class DialogEntityRegistryDetail extends LitElement {
|
class DialogEntityRegistryDetail extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() private _name!: string;
|
@property() private _name!: string;
|
||||||
@property() private _platform!: string;
|
|
||||||
@property() private _entityId!: string;
|
@property() private _entityId!: string;
|
||||||
@property() private _disabledBy!: string | null;
|
@property() private _disabledBy!: string | null;
|
||||||
@property() private _error?: string;
|
@property() private _error?: string;
|
||||||
@ -45,7 +44,6 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
this._params = params;
|
this._params = params;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._name = this._params.entry.name || "";
|
this._name = this._params.entry.name || "";
|
||||||
this._platform = this._params.entry.platform;
|
|
||||||
this._origEntityId = this._params.entry.entity_id;
|
this._origEntityId = this._params.entry.entity_id;
|
||||||
this._entityId = this._params.entry.entity_id;
|
this._entityId = this._params.entry.entity_id;
|
||||||
this._disabledBy = this._params.entry.disabled_by;
|
this._disabledBy = this._params.entry.disabled_by;
|
||||||
@ -143,7 +141,8 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
<mwc-button
|
<mwc-button
|
||||||
class="warning"
|
class="warning"
|
||||||
@click="${this._confirmDeleteEntry}"
|
@click="${this._confirmDeleteEntry}"
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting ||
|
||||||
|
!(stateObj && stateObj.attributes.restored)}
|
||||||
>
|
>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.entity_registry.editor.delete"
|
"ui.panel.config.entity_registry.editor.delete"
|
||||||
@ -201,13 +200,8 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
|
|
||||||
private _confirmDeleteEntry(): void {
|
private _confirmDeleteEntry(): void {
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.entity_registry.editor.confirm_delete"
|
|
||||||
),
|
|
||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.entity_registry.editor.confirm_delete2",
|
"ui.panel.config.entity_registry.editor.confirm_delete"
|
||||||
"platform",
|
|
||||||
this._platform
|
|
||||||
),
|
),
|
||||||
confirm: () => this._deleteEntry(),
|
confirm: () => this._deleteEntry(),
|
||||||
});
|
});
|
||||||
|
@ -5,19 +5,28 @@ import {
|
|||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
property,
|
property,
|
||||||
|
query,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
|
|
||||||
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
computeEntityRegistryName,
|
computeEntityRegistryName,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
|
removeEntityRegistryEntry,
|
||||||
|
updateEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import "../../../layouts/hass-loading-screen";
|
import "../../../layouts/hass-loading-screen";
|
||||||
import "../../../components/data-table/ha-data-table";
|
import "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import "../../../components/ha-switch";
|
|
||||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
import { stateIcon } from "../../../common/entity/state_icon";
|
import { stateIcon } from "../../../common/entity/state_icon";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
@ -26,25 +35,33 @@ import {
|
|||||||
loadEntityRegistryDetailDialog,
|
loadEntityRegistryDetailDialog,
|
||||||
} from "./show-dialog-entity-registry-detail";
|
} from "./show-dialog-entity-registry-detail";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
// tslint:disable-next-line
|
|
||||||
import { HaSwitch } from "../../../components/ha-switch";
|
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import {
|
import {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
RowClickedEvent,
|
RowClickedEvent,
|
||||||
|
SelectionChangedEvent,
|
||||||
|
HaDataTable,
|
||||||
|
DataTableColumnData,
|
||||||
} from "../../../components/data-table/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
|
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
|
||||||
|
|
||||||
class HaConfigEntityRegistry extends LitElement {
|
class HaConfigEntityRegistry extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public isWide?: boolean;
|
@property() public isWide!: boolean;
|
||||||
|
@property() public narrow!: boolean;
|
||||||
@property() private _entities?: EntityRegistryEntry[];
|
@property() private _entities?: EntityRegistryEntry[];
|
||||||
@property() private _showDisabled = false;
|
@property() private _showDisabled = false;
|
||||||
|
@property() private _showUnavailable = true;
|
||||||
|
@property() private _filter = "";
|
||||||
|
@property() private _selectedEntities: string[] = [];
|
||||||
|
@query("ha-data-table") private _dataTable!: HaDataTable;
|
||||||
|
|
||||||
private _unsubEntities?: UnsubscribeFunc;
|
private _unsubEntities?: UnsubscribeFunc;
|
||||||
|
|
||||||
private _columns = memoize(
|
private _columns = memoize(
|
||||||
(_language): DataTableColumnContainer => {
|
(narrow, _language): DataTableColumnContainer => {
|
||||||
return {
|
const columns: DataTableColumnContainer = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
type: "icon",
|
type: "icon",
|
||||||
@ -60,58 +77,123 @@ class HaConfigEntityRegistry extends LitElement {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
},
|
},
|
||||||
entity_id: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.entity_registry.picker.headers.entity_id"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
},
|
|
||||||
platform: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.entity_registry.picker.headers.integration"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
template: (platform) =>
|
|
||||||
html`
|
|
||||||
${this.hass.localize(`component.${platform}.config.title`) ||
|
|
||||||
platform}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
disabled_by: {
|
|
||||||
title: this.hass.localize(
|
|
||||||
"ui.panel.config.entity_registry.picker.headers.enabled"
|
|
||||||
),
|
|
||||||
type: "icon",
|
|
||||||
template: (disabledBy) => html`
|
|
||||||
<ha-icon
|
|
||||||
slot="item-icon"
|
|
||||||
.icon=${disabledBy ? "hass:cancel" : "hass:check-circle"}
|
|
||||||
></ha-icon>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const statusColumn: DataTableColumnData = {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.headers.status"
|
||||||
|
),
|
||||||
|
type: "icon",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
template: (_status, entity: any) =>
|
||||||
|
entity.unavailable || entity.disabled_by
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
style="display:inline-block; position: relative;"
|
||||||
|
>
|
||||||
|
<ha-icon
|
||||||
|
style=${styleMap({
|
||||||
|
color: entity.unavailable ? "var(--google-red-500)" : "",
|
||||||
|
})}
|
||||||
|
.icon=${entity.unavailable
|
||||||
|
? "hass:alert-circle"
|
||||||
|
: "hass:cancel"}
|
||||||
|
></ha-icon>
|
||||||
|
<paper-tooltip position="left">
|
||||||
|
${entity.unavailable
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.status.unavailable"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.status.disabled"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (narrow) {
|
||||||
|
columns.name.template = (name, entity: any) => {
|
||||||
|
return html`
|
||||||
|
${name}<br />
|
||||||
|
${entity.entity_id} |
|
||||||
|
${this.hass.localize(`component.${entity.platform}.config.title`) ||
|
||||||
|
entity.platform}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
columns.status = statusColumn;
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.entity_id = {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.headers.entity_id"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
};
|
||||||
|
columns.platform = {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.headers.integration"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
template: (platform) =>
|
||||||
|
this.hass.localize(`component.${platform}.config.title`) || platform,
|
||||||
|
};
|
||||||
|
columns.status = statusColumn;
|
||||||
|
|
||||||
|
return columns;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private _filteredEntities = memoize(
|
private _filteredEntities = memoize(
|
||||||
(entities: EntityRegistryEntry[], showDisabled: boolean) =>
|
(
|
||||||
(showDisabled
|
entities: EntityRegistryEntry[],
|
||||||
? entities
|
showDisabled: boolean,
|
||||||
: entities.filter((entity) => !Boolean(entity.disabled_by))
|
showUnavailable: boolean
|
||||||
).map((entry) => {
|
) => {
|
||||||
|
if (!showDisabled) {
|
||||||
|
entities = entities.filter((entity) => !Boolean(entity.disabled_by));
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities.reduce((result, entry) => {
|
||||||
const state = this.hass!.states[entry.entity_id];
|
const state = this.hass!.states[entry.entity_id];
|
||||||
return {
|
|
||||||
|
const unavailable =
|
||||||
|
state && (state.state === "unavailable" || state.attributes.restored); // if there is not state it is disabled
|
||||||
|
|
||||||
|
if (!showUnavailable && unavailable) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
...entry,
|
...entry,
|
||||||
icon: state
|
icon: state
|
||||||
? stateIcon(state)
|
? stateIcon(state)
|
||||||
: domainIcon(computeDomain(entry.entity_id)),
|
: domainIcon(computeDomain(entry.entity_id)),
|
||||||
name:
|
name:
|
||||||
computeEntityRegistryName(this.hass!, entry) ||
|
computeEntityRegistryName(this.hass!, entry) ||
|
||||||
this.hass!.localize("state.default.unavailable"),
|
this.hass.localize("state.default.unavailable"),
|
||||||
};
|
unavailable,
|
||||||
})
|
status: unavailable
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.status.unavailable"
|
||||||
|
)
|
||||||
|
: entry.disabled_by
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.status.disabled"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.status.ok"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, [] as any);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
@ -133,17 +215,19 @@ class HaConfigEntityRegistry extends LitElement {
|
|||||||
"ui.panel.config.entity_registry.caption"
|
"ui.panel.config.entity_registry.caption"
|
||||||
)}"
|
)}"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="intro">
|
<div class="intro">
|
||||||
<h2>
|
<h2>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.entity_registry.picker.header"
|
"ui.panel.config.entity_registry.picker.header"
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.entity_registry.picker.introduction"
|
"ui.panel.config.entity_registry.picker.introduction"
|
||||||
)}
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.entity_registry.picker.introduction2"
|
"ui.panel.config.entity_registry.picker.introduction2"
|
||||||
@ -154,22 +238,123 @@ class HaConfigEntityRegistry extends LitElement {
|
|||||||
"ui.panel.config.entity_registry.picker.integrations_page"
|
"ui.panel.config.entity_registry.picker.integrations_page"
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
<ha-switch
|
|
||||||
?checked=${this._showDisabled}
|
|
||||||
@change=${this._showDisabledChanged}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.entity_registry.picker.show_disabled"
|
|
||||||
)}
|
|
||||||
</ha-switch>
|
|
||||||
</div>
|
</div>
|
||||||
</p>
|
<ha-data-table
|
||||||
<ha-data-table
|
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||||
.columns=${this._columns(this.hass.language)}
|
.data=${this._filteredEntities(
|
||||||
.data=${this._filteredEntities(this._entities, this._showDisabled)}
|
this._entities,
|
||||||
@row-click=${this._openEditEntry}
|
this._showDisabled,
|
||||||
id="entity_id"
|
this._showUnavailable
|
||||||
>
|
)}
|
||||||
</ha-data-table>
|
.filter=${this._filter}
|
||||||
|
selectable
|
||||||
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
|
@row-click=${this._openEditEntry}
|
||||||
|
id="entity_id"
|
||||||
|
>
|
||||||
|
<div class="table-header" slot="header">
|
||||||
|
${this._selectedEntities.length
|
||||||
|
? html`
|
||||||
|
<p class="selected-txt">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.selected",
|
||||||
|
"number",
|
||||||
|
this._selectedEntities.length
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="header-btns">
|
||||||
|
${!this.narrow
|
||||||
|
? html`
|
||||||
|
<mwc-button @click=${this._enableSelected}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.enable_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
<mwc-button @click=${this._disableSelected}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.disable_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
<mwc-button @click=${this._removeSelected}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.remove_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<paper-icon-button
|
||||||
|
id="enable-btn"
|
||||||
|
icon="hass:undo"
|
||||||
|
@click=${this._enableSelected}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip for="enable-btn">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.enable_selected.button"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
<paper-icon-button
|
||||||
|
id="disable-btn"
|
||||||
|
icon="hass:cancel"
|
||||||
|
@click=${this._disableSelected}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip for="disable-btn">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.disable_selected.button"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
<paper-icon-button
|
||||||
|
id="remove-btn"
|
||||||
|
icon="hass:delete"
|
||||||
|
@click=${this._disableSelected}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip for="remove-btn">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.remove_selected.button"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<search-input
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
.filter=${this._filter}
|
||||||
|
></search-input>
|
||||||
|
<paper-menu-button no-animations horizontal-align="right">
|
||||||
|
<paper-icon-button
|
||||||
|
aria-label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.filter.filter"
|
||||||
|
)}
|
||||||
|
title="${this.hass!.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.filter.filter"
|
||||||
|
)}"
|
||||||
|
icon="hass:filter-variant"
|
||||||
|
slot="dropdown-trigger"
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-listbox slot="dropdown-content">
|
||||||
|
<paper-icon-item @click="${this._showDisabledChanged}">
|
||||||
|
<paper-checkbox
|
||||||
|
.checked=${this._showDisabled}
|
||||||
|
slot="item-icon"
|
||||||
|
></paper-checkbox>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.filter.show_disabled"
|
||||||
|
)}
|
||||||
|
</paper-icon-item>
|
||||||
|
<paper-icon-item @click="${this._showRestoredChanged}">
|
||||||
|
<paper-checkbox
|
||||||
|
.checked=${this._showUnavailable}
|
||||||
|
slot="item-icon"
|
||||||
|
></paper-checkbox>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.filter.show_unavailable"
|
||||||
|
)}
|
||||||
|
</paper-icon-item>
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-menu-button>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</ha-data-table>
|
||||||
</div>
|
</div>
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
`;
|
`;
|
||||||
@ -192,8 +377,103 @@ class HaConfigEntityRegistry extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showDisabledChanged(ev: Event) {
|
private _showDisabledChanged() {
|
||||||
this._showDisabled = (ev.target as HaSwitch).checked;
|
this._showDisabled = !this._showDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showRestoredChanged() {
|
||||||
|
this._showUnavailable = !this._showUnavailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
|
this._filter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSelectionChanged(ev: CustomEvent): void {
|
||||||
|
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||||
|
const entity = changedSelection.id;
|
||||||
|
if (changedSelection.selected) {
|
||||||
|
this._selectedEntities = [...this._selectedEntities, entity];
|
||||||
|
} else {
|
||||||
|
this._selectedEntities = this._selectedEntities.filter(
|
||||||
|
(entityId) => entityId !== entity
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _enableSelected() {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.enable_selected.confirm_title",
|
||||||
|
"number",
|
||||||
|
this._selectedEntities.length
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.enable_selected.confirm_text"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.hass.localize("ui.common.yes"),
|
||||||
|
cancelBtnText: this.hass.localize("ui.common.no"),
|
||||||
|
confirm: () => {
|
||||||
|
this._selectedEntities.forEach((entity) =>
|
||||||
|
updateEntityRegistryEntry(this.hass, entity, {
|
||||||
|
disabled_by: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this._clearSelection();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disableSelected() {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.disable_selected.confirm_title",
|
||||||
|
"number",
|
||||||
|
this._selectedEntities.length
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.disable_selected.confirm_text"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.hass.localize("ui.common.yes"),
|
||||||
|
cancelBtnText: this.hass.localize("ui.common.no"),
|
||||||
|
confirm: () => {
|
||||||
|
this._selectedEntities.forEach((entity) =>
|
||||||
|
updateEntityRegistryEntry(this.hass, entity, {
|
||||||
|
disabled_by: "user",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this._clearSelection();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeSelected() {
|
||||||
|
const removeableEntities = this._selectedEntities.filter((entity) => {
|
||||||
|
const stateObj = this.hass.states[entity];
|
||||||
|
return stateObj?.attributes.restored;
|
||||||
|
});
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.remove_selected.confirm_title",
|
||||||
|
"number",
|
||||||
|
removeableEntities.length
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.picker.remove_selected.confirm_text"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.hass.localize("ui.common.yes"),
|
||||||
|
cancelBtnText: this.hass.localize("ui.common.no"),
|
||||||
|
confirm: () => {
|
||||||
|
removeableEntities.forEach((entity) =>
|
||||||
|
removeEntityRegistryEntry(this.hass, entity)
|
||||||
|
);
|
||||||
|
this._clearSelection();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _clearSelection() {
|
||||||
|
this._dataTable.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openEditEntry(ev: CustomEvent): void {
|
private _openEditEntry(ev: CustomEvent): void {
|
||||||
@ -237,18 +517,35 @@ class HaConfigEntityRegistry extends LitElement {
|
|||||||
opacity: var(--dark-primary-opacity);
|
opacity: var(--dark-primary-opacity);
|
||||||
}
|
}
|
||||||
.intro {
|
.intro {
|
||||||
padding: 24px 16px 0;
|
padding: 24px 16px;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
ha-data-table {
|
ha-data-table {
|
||||||
margin-bottom: 24px;
|
width: 100%;
|
||||||
margin-top: 0px;
|
|
||||||
}
|
}
|
||||||
ha-switch {
|
ha-switch {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||||
|
}
|
||||||
|
search-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.selected-txt {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 38px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
.header-btns > mwc-button,
|
||||||
|
.header-btns > paper-icon-button {
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/iron-icon/iron-icon";
|
import "@polymer/iron-icon/iron-icon";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
|
||||||
@ -23,7 +24,11 @@ import {
|
|||||||
loadConfigFlowDialog,
|
loadConfigFlowDialog,
|
||||||
showConfigFlowDialog,
|
showConfigFlowDialog,
|
||||||
} from "../../../dialogs/config-flow/show-dialog-config-flow";
|
} from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { localizeConfigFlowTitle } from "../../../data/config_flow";
|
import {
|
||||||
|
localizeConfigFlowTitle,
|
||||||
|
ignoreConfigFlow,
|
||||||
|
DISCOVERY_SOURCES,
|
||||||
|
} from "../../../data/config_flow";
|
||||||
import {
|
import {
|
||||||
LitElement,
|
LitElement,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
@ -34,10 +39,11 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ConfigEntry } from "../../../data/config_entries";
|
import { ConfigEntry, deleteConfigEntry } from "../../../data/config_entries";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||||
|
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
|
||||||
|
|
||||||
@customElement("ha-config-entries-dashboard")
|
@customElement("ha-config-entries-dashboard")
|
||||||
export class HaConfigManagerDashboard extends LitElement {
|
export class HaConfigManagerDashboard extends LitElement {
|
||||||
@ -56,6 +62,7 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
* For example, can be discovered devices that require more config.
|
* For example, can be discovered devices that require more config.
|
||||||
*/
|
*/
|
||||||
@property() private configEntriesInProgress!: DataEntryFlowProgress[];
|
@property() private configEntriesInProgress!: DataEntryFlowProgress[];
|
||||||
|
@property() private _showIgnored = false;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@ -67,6 +74,67 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
<hass-subpage
|
<hass-subpage
|
||||||
header=${this.hass.localize("ui.panel.config.integrations.caption")}
|
header=${this.hass.localize("ui.panel.config.integrations.caption")}
|
||||||
>
|
>
|
||||||
|
<paper-menu-button
|
||||||
|
close-on-activate
|
||||||
|
no-animations
|
||||||
|
horizontal-align="right"
|
||||||
|
horizontal-offset="-5"
|
||||||
|
slot="toolbar-icon"
|
||||||
|
>
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:dots-vertical"
|
||||||
|
slot="dropdown-trigger"
|
||||||
|
alt="menu"
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
role="listbox"
|
||||||
|
selected="{{selectedItem}}"
|
||||||
|
>
|
||||||
|
<paper-item @click=${this._toggleShowIgnored}>
|
||||||
|
${this.hass.localize(
|
||||||
|
this._showIgnored
|
||||||
|
? "ui.panel.config.integrations.ignore.hide_ignored"
|
||||||
|
: "ui.panel.config.integrations.ignore.show_ignored"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-menu-button>
|
||||||
|
|
||||||
|
${this._showIgnored
|
||||||
|
? html`
|
||||||
|
<ha-config-section>
|
||||||
|
<span slot="header"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.ignored"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-card>
|
||||||
|
${this.configEntries
|
||||||
|
.filter((item) => item.source === "ignore")
|
||||||
|
.map(
|
||||||
|
(item: ConfigEntry) => html`
|
||||||
|
<paper-item>
|
||||||
|
<paper-item-body>
|
||||||
|
${this.hass.localize(
|
||||||
|
`component.${item.domain}.config.title`
|
||||||
|
)}
|
||||||
|
</paper-item-body>
|
||||||
|
<paper-icon-button
|
||||||
|
@click=${this._removeIgnoredIntegration}
|
||||||
|
.entry=${item}
|
||||||
|
icon="hass:delete"
|
||||||
|
aria-label=${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.details"
|
||||||
|
)}
|
||||||
|
></paper-icon-button>
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
${this.configEntriesInProgress.length
|
${this.configEntriesInProgress.length
|
||||||
? html`
|
? html`
|
||||||
<ha-config-section>
|
<ha-config-section>
|
||||||
@ -82,9 +150,22 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
<paper-item-body>
|
<paper-item-body>
|
||||||
${localizeConfigFlowTitle(this.hass.localize, flow)}
|
${localizeConfigFlowTitle(this.hass.localize, flow)}
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
|
${DISCOVERY_SOURCES.includes(flow.context.source) &&
|
||||||
|
flow.context.unique_id
|
||||||
|
? html`
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._ignoreFlow}
|
||||||
|
.flow=${flow}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.ignore"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._continueFlow}
|
@click=${this._continueFlow}
|
||||||
data-id="${flow.flow_id}"
|
.flowId=${flow.flow_id}
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.panel.config.integrations.configure"
|
"ui.panel.config.integrations.configure"
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
@ -98,49 +179,51 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<ha-config-section class="configured">
|
<ha-config-section class="configured">
|
||||||
<span slot="header"
|
<span slot="header">
|
||||||
>${this.hass.localize(
|
${this.hass.localize("ui.panel.config.integrations.configured")}
|
||||||
"ui.panel.config.integrations.configured"
|
</span>
|
||||||
)}</span
|
|
||||||
>
|
|
||||||
<ha-card>
|
<ha-card>
|
||||||
${this.entityRegistryEntries.length
|
${this.entityRegistryEntries.length
|
||||||
? this.configEntries.map(
|
? this.configEntries.map((item: any, idx) =>
|
||||||
(item: any, idx) => html`
|
item.source === "ignore"
|
||||||
<a
|
? ""
|
||||||
href="/config/integrations/config_entry/${item.entry_id}"
|
: html`
|
||||||
>
|
<a
|
||||||
<paper-item data-index=${idx}>
|
href="/config/integrations/config_entry/${item.entry_id}"
|
||||||
<paper-item-body two-line>
|
>
|
||||||
<div>
|
<paper-item data-index=${idx}>
|
||||||
${this.hass.localize(
|
<paper-item-body two-line>
|
||||||
`component.${item.domain}.config.title`
|
<div>
|
||||||
)}:
|
${this.hass.localize(
|
||||||
${item.title}
|
`component.${item.domain}.config.title`
|
||||||
</div>
|
)}:
|
||||||
<div secondary>
|
${item.title}
|
||||||
${this._getEntities(item).map(
|
</div>
|
||||||
(entity) => html`
|
<div secondary>
|
||||||
<span>
|
${this._getEntities(item).map(
|
||||||
<ha-state-icon
|
(entity) => html`
|
||||||
.stateObj=${entity}
|
<span>
|
||||||
></ha-state-icon>
|
<ha-state-icon
|
||||||
<paper-tooltip position="bottom"
|
.stateObj=${entity}
|
||||||
>${computeStateName(entity)}</paper-tooltip
|
></ha-state-icon>
|
||||||
>
|
<paper-tooltip position="bottom"
|
||||||
</span>
|
>${computeStateName(
|
||||||
`
|
entity
|
||||||
)}
|
)}</paper-tooltip
|
||||||
</div>
|
>
|
||||||
</paper-item-body>
|
</span>
|
||||||
<ha-icon-next
|
`
|
||||||
aria-label=${this.hass.localize(
|
)}
|
||||||
"ui.panel.config.integrations.details"
|
</div>
|
||||||
)}
|
</paper-item-body>
|
||||||
></ha-icon-next>
|
<ha-icon-next
|
||||||
</paper-item>
|
aria-label=${this.hass.localize(
|
||||||
</a>
|
"ui.panel.config.integrations.details"
|
||||||
`
|
)}
|
||||||
|
></ha-icon-next>
|
||||||
|
</paper-item>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
)
|
)
|
||||||
: html`
|
: html`
|
||||||
<div class="config-entry-row">
|
<div class="config-entry-row">
|
||||||
@ -176,12 +259,64 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
|
|
||||||
private _continueFlow(ev: Event) {
|
private _continueFlow(ev: Event) {
|
||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
continueFlowId:
|
continueFlowId: (ev.target! as any).flowId,
|
||||||
(ev.target as HTMLElement).getAttribute("data-id") || undefined,
|
|
||||||
dialogClosedCallback: () => fireEvent(this, "hass-reload-entries"),
|
dialogClosedCallback: () => fireEvent(this, "hass-reload-entries"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _ignoreFlow(ev: Event) {
|
||||||
|
const flow = (ev.target! as any).flow;
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass!.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.confirm_ignore_title",
|
||||||
|
"name",
|
||||||
|
localizeConfigFlowTitle(this.hass.localize, flow)
|
||||||
|
),
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.confirm_ignore"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.hass!.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.ignore"
|
||||||
|
),
|
||||||
|
confirm: () => {
|
||||||
|
ignoreConfigFlow(this.hass, flow.flow_id);
|
||||||
|
fireEvent(this, "hass-reload-entries");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleShowIgnored() {
|
||||||
|
this._showIgnored = !this._showIgnored;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeIgnoredIntegration(ev: Event) {
|
||||||
|
const entry = (ev.target! as any).entry;
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass!.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.confirm_delete_ignore_title",
|
||||||
|
"name",
|
||||||
|
this.hass.localize(`component.${entry.domain}.config.title`)
|
||||||
|
),
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.confirm_delete_ignore"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.hass!.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||||
|
),
|
||||||
|
confirm: async () => {
|
||||||
|
const result = await deleteConfigEntry(this.hass, entry.entry_id);
|
||||||
|
if (result.require_restart) {
|
||||||
|
alert(
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fireEvent(this, "hass-reload-entries");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _getEntities(configEntry: ConfigEntry): HassEntity[] {
|
private _getEntities(configEntry: ConfigEntry): HassEntity[] {
|
||||||
if (!this.entityRegistryEntries) {
|
if (!this.entityRegistryEntries) {
|
||||||
return [];
|
return [];
|
||||||
@ -203,8 +338,7 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
mwc-button {
|
mwc-button {
|
||||||
top: 3px;
|
align-self: center;
|
||||||
margin-right: -0.57em;
|
|
||||||
}
|
}
|
||||||
.config-entry-row {
|
.config-entry-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -229,6 +363,9 @@ export class HaConfigManagerDashboard extends LitElement {
|
|||||||
right: auto;
|
right: auto;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
}
|
}
|
||||||
|
.overflow {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ class HaConfigIntegrations extends HassRouterPage {
|
|||||||
private _loadData() {
|
private _loadData() {
|
||||||
getConfigEntries(this.hass).then((configEntries) => {
|
getConfigEntries(this.hass).then((configEntries) => {
|
||||||
this._configEntries = configEntries.sort((conf1, conf2) =>
|
this._configEntries = configEntries.sort((conf1, conf2) =>
|
||||||
compare(conf1.title, conf2.title)
|
compare(conf1.domain + conf1.title, conf2.domain + conf2.title)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (this._unsubs) {
|
if (this._unsubs) {
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
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 />;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "../ha-config-section";
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-textarea";
|
|
||||||
|
|
||||||
import "../automation/trigger/ha-automation-trigger";
|
|
||||||
|
|
||||||
import Condition from "./condition/index";
|
|
||||||
import Script from "./script/index";
|
|
||||||
|
|
||||||
export default class Automation extends Component<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
this.triggerChanged = this.triggerChanged.bind(this);
|
|
||||||
this.conditionChanged = this.conditionChanged.bind(this);
|
|
||||||
this.actionChanged = this.actionChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChange(ev) {
|
|
||||||
this.props.onChange({
|
|
||||||
...this.props.automation,
|
|
||||||
[ev.target.name]: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerChanged(ev: CustomEvent) {
|
|
||||||
this.props.onChange({ ...this.props.automation, trigger: ev.detail.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
public conditionChanged(condition) {
|
|
||||||
this.props.onChange({ ...this.props.automation, condition });
|
|
||||||
}
|
|
||||||
|
|
||||||
public actionChanged(action) {
|
|
||||||
this.props.onChange({ ...this.props.automation, action });
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ automation, isWide, hass, localize }) {
|
|
||||||
const { alias, description, trigger, condition, action } = automation;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-config-section is-wide={isWide}>
|
|
||||||
<span slot="header">{alias}</span>
|
|
||||||
<span slot="introduction">
|
|
||||||
{localize("ui.panel.config.automation.editor.introduction")}
|
|
||||||
</span>
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<paper-input
|
|
||||||
label={localize("ui.panel.config.automation.editor.alias")}
|
|
||||||
name="alias"
|
|
||||||
value={alias}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
<ha-textarea
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.description.label"
|
|
||||||
)}
|
|
||||||
placeholder={localize(
|
|
||||||
"ui.panel.config.automation.editor.description.placeholder"
|
|
||||||
)}
|
|
||||||
name="description"
|
|
||||||
value={description}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section is-wide={isWide}>
|
|
||||||
<span slot="header">
|
|
||||||
{localize("ui.panel.config.automation.editor.triggers.header")}
|
|
||||||
</span>
|
|
||||||
<span slot="introduction">
|
|
||||||
<p>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.introduction"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href="https://home-assistant.io/docs/automation/trigger/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<ha-automation-trigger
|
|
||||||
triggers={trigger}
|
|
||||||
onvalue-changed={this.triggerChanged}
|
|
||||||
hass={hass}
|
|
||||||
/>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section is-wide={isWide}>
|
|
||||||
<span slot="header">
|
|
||||||
{localize("ui.panel.config.automation.editor.conditions.header")}
|
|
||||||
</span>
|
|
||||||
<span slot="introduction">
|
|
||||||
<p>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.introduction"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href="https://home-assistant.io/docs/scripts/conditions/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<Condition
|
|
||||||
condition={condition || []}
|
|
||||||
onChange={this.conditionChanged}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section is-wide={isWide}>
|
|
||||||
<span slot="header">
|
|
||||||
{localize("ui.panel.config.automation.editor.actions.header")}
|
|
||||||
</span>
|
|
||||||
<span slot="introduction">
|
|
||||||
<p>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.introduction"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href="https://home-assistant.io/docs/automation/action/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{localize("ui.panel.config.automation.editor.actions.learn_more")}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<Script
|
|
||||||
script={action}
|
|
||||||
onChange={this.actionChanged}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
</ha-config-section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
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";
|
|
||||||
import StateCondition from "./state";
|
|
||||||
import SunCondition from "./sun";
|
|
||||||
import TemplateCondition from "./template";
|
|
||||||
import TimeCondition from "./time";
|
|
||||||
import ZoneCondition from "./zone";
|
|
||||||
|
|
||||||
const TYPES = {
|
|
||||||
and: LogicalCondition,
|
|
||||||
device: DeviceCondition,
|
|
||||||
numeric_state: NumericStateCondition,
|
|
||||||
or: LogicalCondition,
|
|
||||||
state: StateCondition,
|
|
||||||
sun: SunCondition,
|
|
||||||
template: TemplateCondition,
|
|
||||||
time: TimeCondition,
|
|
||||||
zone: ZoneCondition,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OPTIONS = Object.keys(TYPES).sort();
|
|
||||||
|
|
||||||
export default class ConditionEdit extends Component<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.typeChanged = this.typeChanged.bind(this);
|
|
||||||
this.onYamlChange = this.onYamlChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public typeChanged(ev) {
|
|
||||||
const type = ev.target.selectedItem.attributes.condition.value;
|
|
||||||
|
|
||||||
if (type !== this.props.condition.condition) {
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
condition: type,
|
|
||||||
...TYPES[type].defaultConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (yamlMode || !Comp) {
|
|
||||||
return (
|
|
||||||
<div style="margin-right: 24px;">
|
|
||||||
{!Comp && (
|
|
||||||
<div>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.unsupported_condition",
|
|
||||||
"condition",
|
|
||||||
condition.condition
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<YAMLTextArea value={condition} onChange={this.onYamlChange} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<paper-dropdown-menu-light
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type_select"
|
|
||||||
)}
|
|
||||||
no-animations
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
selected={selected}
|
|
||||||
oniron-select={this.typeChanged}
|
|
||||||
>
|
|
||||||
{OPTIONS.map((opt) => (
|
|
||||||
<paper-item condition={opt}>
|
|
||||||
{localize(
|
|
||||||
`ui.panel.config.automation.editor.conditions.type.${opt}.label`
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
))}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu-light>
|
|
||||||
<Comp
|
|
||||||
index={index}
|
|
||||||
condition={condition}
|
|
||||||
onChange={onChange}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onYamlChange(condition) {
|
|
||||||
this.props.onChange(this.props.index, condition);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
import "@polymer/paper-menu-button/paper-menu-button";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
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() {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (
|
|
||||||
confirm(
|
|
||||||
this.props.localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.delete_confirm"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.props.onChange(this.props.index, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(props, { yamlMode }) {
|
|
||||||
return (
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<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"
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
<paper-item onTap={this.onDelete}>
|
|
||||||
{props.localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.delete"
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-menu-button>
|
|
||||||
</div>
|
|
||||||
<ConditionEdit {...props} yamlMode={yamlMode} />
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private switchYamlMode() {
|
|
||||||
this.setState({
|
|
||||||
yamlMode: !this.state.yamlMode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
import "../../../../components/device/ha-device-picker";
|
|
||||||
import "../../../../components/device/ha-device-condition-picker";
|
|
||||||
import "../../../../components/ha-form/ha-form";
|
|
||||||
|
|
||||||
import {
|
|
||||||
fetchDeviceConditionCapabilities,
|
|
||||||
deviceAutomationsEqual,
|
|
||||||
} from "../../../../data/device_automation";
|
|
||||||
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class DeviceCondition extends AutomationComponent<any, any> {
|
|
||||||
private _origCondition;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.devicePicked = this.devicePicked.bind(this);
|
|
||||||
this.deviceConditionPicked = this.deviceConditionPicked.bind(this);
|
|
||||||
this._extraFieldsChanged = this._extraFieldsChanged.bind(this);
|
|
||||||
this.state = { device_id: undefined, capabilities: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
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 &&
|
|
||||||
deviceAutomationsEqual(this._origCondition, condition)
|
|
||||||
) {
|
|
||||||
condition = this._origCondition;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
public render({ condition, hass }, { device_id, capabilities }) {
|
|
||||||
if (device_id === undefined) {
|
|
||||||
device_id = condition.device_id;
|
|
||||||
}
|
|
||||||
const extraFieldsData =
|
|
||||||
capabilities && capabilities.extra_fields
|
|
||||||
? capabilities.extra_fields.map((item) => {
|
|
||||||
return { [item.name]: this.props.condition[item.name] };
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-device-picker
|
|
||||||
value={device_id}
|
|
||||||
onChange={this.devicePicked}
|
|
||||||
hass={hass}
|
|
||||||
label="Device"
|
|
||||||
/>
|
|
||||||
<ha-device-condition-picker
|
|
||||||
value={condition}
|
|
||||||
deviceId={device_id}
|
|
||||||
onChange={this.deviceConditionPicked}
|
|
||||||
hass={hass}
|
|
||||||
label="Condition"
|
|
||||||
/>
|
|
||||||
{extraFieldsData && (
|
|
||||||
<ha-form
|
|
||||||
data={Object.assign({}, ...extraFieldsData)}
|
|
||||||
schema={this.state.capabilities.extra_fields}
|
|
||||||
computeLabel={this._extraFieldsComputeLabelCallback(hass.localize)}
|
|
||||||
onvalue-changed={this._extraFieldsChanged}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount() {
|
|
||||||
this.initialized = true;
|
|
||||||
if (!this.state.capabilities) {
|
|
||||||
this._getCapabilities();
|
|
||||||
}
|
|
||||||
if (this.props.condition) {
|
|
||||||
this._origCondition = this.props.condition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidUpdate(prevProps) {
|
|
||||||
if (!deviceAutomationsEqual(prevProps.condition, this.props.condition)) {
|
|
||||||
this._getCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getCapabilities() {
|
|
||||||
const condition = this.props.condition;
|
|
||||||
|
|
||||||
const capabilities = condition.domain
|
|
||||||
? await fetchDeviceConditionCapabilities(this.props.hass, condition)
|
|
||||||
: null;
|
|
||||||
this.setState({ ...this.state, capabilities });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _extraFieldsChanged(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.condition,
|
|
||||||
...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.condition.type.device.extra_fields.${schema.name}`
|
|
||||||
) || schema.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(DeviceCondition as any).defaultConfig = {
|
|
||||||
device_id: "",
|
|
||||||
domain: "",
|
|
||||||
entity_id: "",
|
|
||||||
};
|
|
@ -1,57 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
|
|
||||||
import ConditionRow from "./condition_row";
|
|
||||||
|
|
||||||
export default class Condition extends Component<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.addCondition = this.addCondition.bind(this);
|
|
||||||
this.conditionChanged = this.conditionChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addCondition() {
|
|
||||||
const condition = this.props.condition.concat({
|
|
||||||
condition: "state",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onChange(condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public conditionChanged(index, newValue) {
|
|
||||||
const condition = this.props.condition.concat();
|
|
||||||
|
|
||||||
if (newValue === null) {
|
|
||||||
condition.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
condition[index] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ condition, hass, localize }) {
|
|
||||||
return (
|
|
||||||
<div class="triggers">
|
|
||||||
{condition.map((cnd, idx) => (
|
|
||||||
<ConditionRow
|
|
||||||
index={idx}
|
|
||||||
condition={cnd}
|
|
||||||
onChange={this.conditionChanged}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-actions add-card">
|
|
||||||
<mwc-button onTap={this.addCondition}>
|
|
||||||
{localize("ui.panel.config.automation.editor.conditions.add")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
import Condition from "./index";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class LogicalCondition extends AutomationComponent<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.conditionChanged = this.conditionChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public conditionChanged(conditions) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.condition,
|
|
||||||
conditions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
public render({ condition, hass, localize }) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Condition
|
|
||||||
condition={condition.conditions || []}
|
|
||||||
onChange={this.conditionChanged}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(LogicalCondition as any).defaultConfig = {
|
|
||||||
conditions: [{ condition: "state" }],
|
|
||||||
};
|
|
@ -1,71 +0,0 @@
|
|||||||
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 AutomationComponent<any> {
|
|
||||||
private onChange: (obj: any) => void;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "condition");
|
|
||||||
this.entityPicked = this.entityPicked.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public entityPicked(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.condition,
|
|
||||||
entity_id: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
public render({ condition, hass, localize }) {
|
|
||||||
const { value_template, entity_id, below, above } = condition;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-entity-picker
|
|
||||||
value={entity_id}
|
|
||||||
onChange={this.entityPicked}
|
|
||||||
hass={hass}
|
|
||||||
allowCustomEntity
|
|
||||||
/>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.above"
|
|
||||||
)}
|
|
||||||
name="above"
|
|
||||||
value={above}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.below"
|
|
||||||
)}
|
|
||||||
name="below"
|
|
||||||
value={below}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
<ha-textarea
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.value_template"
|
|
||||||
)}
|
|
||||||
name="value_template"
|
|
||||||
value={value_template}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(NumericStateCondition as any).defaultConfig = {
|
|
||||||
entity_id: "",
|
|
||||||
};
|
|
@ -1,56 +0,0 @@
|
|||||||
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 AutomationComponent<any> {
|
|
||||||
private onChange: (obj: any) => void;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "condition");
|
|
||||||
this.entityPicked = this.entityPicked.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public entityPicked(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.condition,
|
|
||||||
entity_id: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
public render({ condition, hass, localize }) {
|
|
||||||
const { entity_id, state } = condition;
|
|
||||||
const cndFor = condition.for;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-entity-picker
|
|
||||||
value={entity_id}
|
|
||||||
onChange={this.entityPicked}
|
|
||||||
hass={hass}
|
|
||||||
allowCustomEntity
|
|
||||||
/>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.state.state"
|
|
||||||
)}
|
|
||||||
name="state"
|
|
||||||
value={state}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
{cndFor && <pre>For: {JSON.stringify(cndFor, null, 2)}</pre>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(StateCondition as any).defaultConfig = {
|
|
||||||
entity_id: "",
|
|
||||||
state: "",
|
|
||||||
};
|
|
@ -1,112 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-radio-button/paper-radio-button";
|
|
||||||
import "@polymer/paper-radio-group/paper-radio-group";
|
|
||||||
|
|
||||||
import { onChangeEvent } from "../../../../common/preact/event";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class SunCondition extends AutomationComponent<any> {
|
|
||||||
private onChange: (obj: any) => void;
|
|
||||||
private afterPicked: (obj: any) => void;
|
|
||||||
private beforePicked: (obj: any) => void;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "condition");
|
|
||||||
this.afterPicked = this.radioGroupPicked.bind(this, "after");
|
|
||||||
this.beforePicked = this.radioGroupPicked.bind(this, "before");
|
|
||||||
}
|
|
||||||
|
|
||||||
public radioGroupPicked(key, ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const condition = { ...this.props.condition };
|
|
||||||
|
|
||||||
if (ev.target.selected) {
|
|
||||||
condition[key] = ev.target.selected;
|
|
||||||
} else {
|
|
||||||
delete condition[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(this.props.index, condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ condition, localize }) {
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
const { after, after_offset, before, before_offset } = condition;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label id="beforelabel">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.before"
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
<paper-radio-group
|
|
||||||
allow-empty-selection
|
|
||||||
selected={before}
|
|
||||||
aria-labelledby="beforelabel"
|
|
||||||
onpaper-radio-group-changed={this.beforePicked}
|
|
||||||
>
|
|
||||||
<paper-radio-button name="sunrise">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
|
||||||
)}
|
|
||||||
</paper-radio-button>
|
|
||||||
<paper-radio-button name="sunset">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
|
||||||
)}
|
|
||||||
</paper-radio-button>
|
|
||||||
</paper-radio-group>
|
|
||||||
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.before_offset"
|
|
||||||
)}
|
|
||||||
name="before_offset"
|
|
||||||
value={before_offset}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
disabled={before === undefined}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label id="afterlabel">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.after"
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
<paper-radio-group
|
|
||||||
allow-empty-selection
|
|
||||||
selected={after}
|
|
||||||
aria-labelledby="afterlabel"
|
|
||||||
onpaper-radio-group-changed={this.afterPicked}
|
|
||||||
>
|
|
||||||
<paper-radio-button name="sunrise">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
|
||||||
)}
|
|
||||||
</paper-radio-button>
|
|
||||||
<paper-radio-button name="sunset">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
|
||||||
)}
|
|
||||||
</paper-radio-button>
|
|
||||||
</paper-radio-group>
|
|
||||||
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.sun.after_offset"
|
|
||||||
)}
|
|
||||||
name="after_offset"
|
|
||||||
value={after_offset}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
disabled={after === undefined}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(SunCondition as any).defaultConfig = {};
|
|
@ -1,36 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "../../../../components/ha-textarea";
|
|
||||||
|
|
||||||
import { onChangeEvent } from "../../../../common/preact/event";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class TemplateCondition extends AutomationComponent<any> {
|
|
||||||
private onChange: (obj: any) => void;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "condition");
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ condition, localize }) {
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
const { value_template } = condition;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-textarea
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.template.value_template"
|
|
||||||
)}
|
|
||||||
name="value_template"
|
|
||||||
value={value_template}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(TemplateCondition as any).defaultConfig = {
|
|
||||||
value_template: "",
|
|
||||||
};
|
|
@ -1,41 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
|
|
||||||
import { onChangeEvent } from "../../../../common/preact/event";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class TimeCondition extends AutomationComponent<any> {
|
|
||||||
private onChange: (obj: any) => void;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "condition");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
public render({ condition, localize }) {
|
|
||||||
const { after, before } = condition;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.time.after"
|
|
||||||
)}
|
|
||||||
name="after"
|
|
||||||
value={after}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.time.before"
|
|
||||||
)}
|
|
||||||
name="before"
|
|
||||||
value={before}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(TimeCondition as any).defaultConfig = {};
|
|
@ -1,73 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
|
||||||
import { hasLocation } from "../../../../common/entity/has_location";
|
|
||||||
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
|
|
||||||
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
function zoneAndLocationFilter(stateObj) {
|
|
||||||
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ZoneCondition extends AutomationComponent<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.entityPicked = this.entityPicked.bind(this);
|
|
||||||
this.zonePicked = this.zonePicked.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public entityPicked(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.condition,
|
|
||||||
entity_id: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public zonePicked(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.condition,
|
|
||||||
zone: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
public render({ condition, hass, localize }) {
|
|
||||||
const { entity_id, zone } = condition;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-entity-picker
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.zone.entity"
|
|
||||||
)}
|
|
||||||
value={entity_id}
|
|
||||||
onChange={this.entityPicked}
|
|
||||||
hass={hass}
|
|
||||||
allowCustomEntity
|
|
||||||
entityFilter={zoneAndLocationFilter}
|
|
||||||
/>
|
|
||||||
<ha-entity-picker
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.zone.zone"
|
|
||||||
)}
|
|
||||||
value={zone}
|
|
||||||
onChange={this.zonePicked}
|
|
||||||
hass={hass}
|
|
||||||
allowCustomEntity
|
|
||||||
includeDomains={["zone"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(ZoneCondition as any).defaultConfig = {
|
|
||||||
entity_id: "",
|
|
||||||
zone: "",
|
|
||||||
};
|
|
@ -1,65 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
import "../../../components/ha-textarea";
|
|
||||||
|
|
||||||
export default class JSONTextArea extends Component<any, any> {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
isvalid: true,
|
|
||||||
value: JSON.stringify(props.value || {}, null, 2),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChange(ev) {
|
|
||||||
const value = ev.target.value;
|
|
||||||
let parsed;
|
|
||||||
let isValid;
|
|
||||||
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(value);
|
|
||||||
isValid = true;
|
|
||||||
} catch (err) {
|
|
||||||
// Invalid JSON
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
value,
|
|
||||||
isValid,
|
|
||||||
});
|
|
||||||
if (isValid) {
|
|
||||||
this.props.onChange(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillReceiveProps({ value }) {
|
|
||||||
if (value === this.props.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
value: JSON.stringify(value, null, 2),
|
|
||||||
isValid: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ label }, { value, isValid }) {
|
|
||||||
const style: any = {
|
|
||||||
minWidth: 300,
|
|
||||||
width: "100%",
|
|
||||||
};
|
|
||||||
if (!isValid) {
|
|
||||||
style.border = "1px solid red";
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ha-textarea
|
|
||||||
label={label}
|
|
||||||
value={value}
|
|
||||||
style={style}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
|
||||||
|
|
||||||
// Force file to be a module to augment global scope.
|
|
||||||
export {};
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
namespace JSX {
|
|
||||||
interface IntrinsicElements {
|
|
||||||
"paper-input": Partial<PaperInputElement>;
|
|
||||||
"ha-config-section": any;
|
|
||||||
"ha-card": any;
|
|
||||||
"paper-radio-button": any;
|
|
||||||
"paper-radio-group": any;
|
|
||||||
"ha-entity-picker": any;
|
|
||||||
"paper-listbox": any;
|
|
||||||
"paper-item": any;
|
|
||||||
"paper-menu-button": any;
|
|
||||||
"paper-dropdown-menu-light": any;
|
|
||||||
"paper-icon-button": any;
|
|
||||||
"ha-device-picker": any;
|
|
||||||
"ha-device-condition-picker": any;
|
|
||||||
"ha-textarea": any;
|
|
||||||
"ha-code-editor": any;
|
|
||||||
"ha-service-picker": any;
|
|
||||||
"mwc-button": any;
|
|
||||||
"ha-automation-trigger": any;
|
|
||||||
"ha-device-trigger-picker": any;
|
|
||||||
"ha-device-action-picker": any;
|
|
||||||
"ha-form": any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "../ha-config-section";
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
|
|
||||||
import Script from "./script/index";
|
|
||||||
|
|
||||||
export default class ScriptEditor extends Component<{
|
|
||||||
onChange: (...args: any[]) => any;
|
|
||||||
script: any;
|
|
||||||
isWide: any;
|
|
||||||
hass: any;
|
|
||||||
localize: any;
|
|
||||||
}> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
this.sequenceChanged = this.sequenceChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChange(ev) {
|
|
||||||
this.props.onChange({
|
|
||||||
...this.props.script,
|
|
||||||
[ev.target.name]: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public sequenceChanged(sequence) {
|
|
||||||
this.props.onChange({ ...this.props.script, sequence });
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
public render({ script, isWide, hass, localize }) {
|
|
||||||
const { alias, sequence } = script;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-config-section is-wide={isWide}>
|
|
||||||
<span slot="header">{alias}</span>
|
|
||||||
<span slot="introduction">
|
|
||||||
{localize("ui.panel.config.script.editor.introduction")}
|
|
||||||
</span>
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<paper-input
|
|
||||||
label="Name"
|
|
||||||
name="alias"
|
|
||||||
value={alias}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section is-wide={isWide}>
|
|
||||||
<span slot="header">
|
|
||||||
{localize("ui.panel.config.script.editor.sequence")}
|
|
||||||
</span>
|
|
||||||
<span slot="introduction">
|
|
||||||
{localize("ui.panel.config.script.editor.sequence_sentence")}
|
|
||||||
<p>
|
|
||||||
<a href="https://home-assistant.io/docs/scripts/" target="_blank">
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.script.editor.link_available_actions"
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
<Script
|
|
||||||
script={sequence}
|
|
||||||
onChange={this.sequenceChanged}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
</ha-config-section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
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 CallServiceAction from "./call_service";
|
|
||||||
import ConditionAction from "./condition";
|
|
||||||
import DelayAction from "./delay";
|
|
||||||
import DeviceAction from "./device";
|
|
||||||
import EventAction from "./event";
|
|
||||||
import SceneAction from "./scene";
|
|
||||||
import WaitAction from "./wait";
|
|
||||||
|
|
||||||
const TYPES = {
|
|
||||||
service: CallServiceAction,
|
|
||||||
delay: DelayAction,
|
|
||||||
wait_template: WaitAction,
|
|
||||||
condition: ConditionAction,
|
|
||||||
event: EventAction,
|
|
||||||
device_id: DeviceAction,
|
|
||||||
scene: SceneAction,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OPTIONS = Object.keys(TYPES).sort();
|
|
||||||
|
|
||||||
function getType(action) {
|
|
||||||
const keys = Object.keys(TYPES);
|
|
||||||
// tslint:disable-next-line: prefer-for-of
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
if (keys[i] in action) {
|
|
||||||
return keys[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Action extends Component<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.typeChanged = this.typeChanged.bind(this);
|
|
||||||
this.onYamlChange = this.onYamlChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public typeChanged(ev) {
|
|
||||||
const newType = ev.target.selectedItem.attributes.action.value;
|
|
||||||
const oldType = getType(this.props.action);
|
|
||||||
|
|
||||||
if (oldType !== newType) {
|
|
||||||
this.props.onChange(this.props.index, TYPES[newType].defaultConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ index, action, onChange, hass, localize, yamlMode }) {
|
|
||||||
const type = getType(action);
|
|
||||||
// tslint:disable-next-line: variable-name
|
|
||||||
const Comp = type && TYPES[type];
|
|
||||||
// @ts-ignore
|
|
||||||
const selected = OPTIONS.indexOf(type);
|
|
||||||
|
|
||||||
if (yamlMode || !Comp) {
|
|
||||||
return (
|
|
||||||
<div style="margin-right: 24px;">
|
|
||||||
{!Comp && (
|
|
||||||
<div>
|
|
||||||
{localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.unsupported_action",
|
|
||||||
"action",
|
|
||||||
type
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<YAMLTextArea value={action} onChange={this.onYamlChange} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<paper-dropdown-menu-light
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type_select"
|
|
||||||
)}
|
|
||||||
no-animations
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
selected={selected}
|
|
||||||
oniron-select={this.typeChanged}
|
|
||||||
>
|
|
||||||
{OPTIONS.map((opt) => (
|
|
||||||
<paper-item action={opt}>
|
|
||||||
{localize(
|
|
||||||
`ui.panel.config.automation.editor.actions.type.${opt}.label`
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
))}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu-light>
|
|
||||||
<Comp
|
|
||||||
index={index}
|
|
||||||
action={action}
|
|
||||||
onChange={onChange}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onYamlChange(condition) {
|
|
||||||
this.props.onChange(this.props.index, condition);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
import "@polymer/paper-menu-button/paper-menu-button";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
|
|
||||||
import ActionEdit from "./action_edit";
|
|
||||||
|
|
||||||
export default class Action extends Component<any> {
|
|
||||||
public state: { yamlMode: boolean };
|
|
||||||
private moveUp: (event: Event) => void;
|
|
||||||
private moveDown: (event: Event) => void;
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
yamlMode: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onDelete = this.onDelete.bind(this);
|
|
||||||
this.switchYamlMode = this.switchYamlMode.bind(this);
|
|
||||||
this.moveUp = props.moveUp.bind(this, props.index);
|
|
||||||
this.moveDown = props.moveDown.bind(this, props.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onDelete() {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (
|
|
||||||
confirm(
|
|
||||||
this.props.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.delete_confirm"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.props.onChange(this.props.index, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(props, { yamlMode }) {
|
|
||||||
return (
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="card-menu" style="z-index: 3">
|
|
||||||
{props.index !== 0 && (
|
|
||||||
<paper-icon-button icon="hass:arrow-up" onTap={this.moveUp} />
|
|
||||||
)}
|
|
||||||
{props.index !== props.length - 1 && (
|
|
||||||
<paper-icon-button icon="hass:arrow-down" onTap={this.moveDown} />
|
|
||||||
)}
|
|
||||||
<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.actions.duplicate"
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
<paper-item onTap={this.onDelete}>
|
|
||||||
{props.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.delete"
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-menu-button>
|
|
||||||
</div>
|
|
||||||
<ActionEdit {...props} yamlMode={yamlMode} />
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private switchYamlMode() {
|
|
||||||
this.setState({
|
|
||||||
yamlMode: !this.state.yamlMode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "../../../../components/ha-service-picker";
|
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
|
||||||
|
|
||||||
import YAMLTextArea from "../yaml_textarea";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
|
||||||
import { computeObjectId } from "../../../../common/entity/compute_object_id";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
|
|
||||||
export default class CallServiceAction extends AutomationComponent<any> {
|
|
||||||
private _getServiceData = memoizeOne((service: string) => {
|
|
||||||
if (!service) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const domain = computeDomain(service);
|
|
||||||
const serviceName = computeObjectId(service);
|
|
||||||
const serviceDomains = this.props.hass.services;
|
|
||||||
if (!(domain in serviceDomains)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (!(serviceName in serviceDomains[domain])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = serviceDomains[domain][serviceName].fields;
|
|
||||||
return Object.keys(fields).map((field) => {
|
|
||||||
return { key: field, ...fields[field] };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.serviceChanged = this.serviceChanged.bind(this);
|
|
||||||
this.entityChanged = this.entityChanged.bind(this);
|
|
||||||
this.serviceDataChanged = this.serviceDataChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public serviceChanged(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newAction = {
|
|
||||||
...this.props.action,
|
|
||||||
service: ev.target.value,
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
computeDomain(this.props.action.service) !==
|
|
||||||
computeDomain(ev.target.value)
|
|
||||||
) {
|
|
||||||
delete newAction.entity_id;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, newAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public entityChanged(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.action,
|
|
||||||
entity_id: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public serviceDataChanged(data) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, { ...this.props.action, data });
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ action, hass, localize }) {
|
|
||||||
const { service, data, entity_id } = action;
|
|
||||||
const serviceData = this._getServiceData(service);
|
|
||||||
const entity = serviceData.find((attr) => attr.key === "entity_id");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-service-picker
|
|
||||||
hass={hass}
|
|
||||||
value={service}
|
|
||||||
onChange={this.serviceChanged}
|
|
||||||
/>
|
|
||||||
{entity && (
|
|
||||||
<ha-entity-picker
|
|
||||||
hass={hass}
|
|
||||||
value={entity_id}
|
|
||||||
label={entity.description}
|
|
||||||
onChange={this.entityChanged}
|
|
||||||
includeDomains={[computeDomain(service)]}
|
|
||||||
allow-custom-entity
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<YAMLTextArea
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.service.service_data"
|
|
||||||
)}
|
|
||||||
value={data}
|
|
||||||
onChange={this.serviceDataChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(CallServiceAction as any).defaultConfig = {
|
|
||||||
alias: "",
|
|
||||||
service: "",
|
|
||||||
data: {},
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
|
|
||||||
import StateCondition from "../condition/state";
|
|
||||||
import ConditionEdit from "../condition/condition_edit";
|
|
||||||
|
|
||||||
export default class ConditionAction extends Component<any> {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
public render({ action, index, onChange, hass, localize }) {
|
|
||||||
return (
|
|
||||||
<ConditionEdit
|
|
||||||
condition={action}
|
|
||||||
onChange={onChange}
|
|
||||||
index={index}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(ConditionAction as any).defaultConfig = {
|
|
||||||
condition: "state",
|
|
||||||
...(StateCondition as any).defaultConfig,
|
|
||||||
};
|
|
@ -1,45 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class DelayAction extends AutomationComponent<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ action, localize }) {
|
|
||||||
const { delay } = action;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.delay.delay"
|
|
||||||
)}
|
|
||||||
name="delay"
|
|
||||||
value={delay}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onChange(ev) {
|
|
||||||
if (
|
|
||||||
!this.initialized ||
|
|
||||||
ev.target.value === this.props.action[ev.target.name]
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.action,
|
|
||||||
[ev.target.name]: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(DelayAction as any).defaultConfig = {
|
|
||||||
delay: "",
|
|
||||||
};
|
|
@ -1,144 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
import "../../../../components/device/ha-device-picker";
|
|
||||||
import "../../../../components/device/ha-device-action-picker";
|
|
||||||
import "../../../../components/ha-form/ha-form";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
import {
|
|
||||||
fetchDeviceActionCapabilities,
|
|
||||||
deviceAutomationsEqual,
|
|
||||||
} from "../../../../data/device_automation";
|
|
||||||
import { DeviceAction } from "../../../../data/script";
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
|
||||||
|
|
||||||
export default class DeviceActionEditor extends AutomationComponent<
|
|
||||||
{
|
|
||||||
index: number;
|
|
||||||
action: DeviceAction;
|
|
||||||
hass: HomeAssistant;
|
|
||||||
onChange(index: number, action: DeviceAction);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
device_id: string | undefined;
|
|
||||||
capabilities: any | undefined;
|
|
||||||
}
|
|
||||||
> {
|
|
||||||
public static defaultConfig: DeviceAction = {
|
|
||||||
device_id: "",
|
|
||||||
domain: "",
|
|
||||||
entity_id: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
private _origAction;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.devicePicked = this.devicePicked.bind(this);
|
|
||||||
this.deviceActionPicked = this.deviceActionPicked.bind(this);
|
|
||||||
this._extraFieldsChanged = this._extraFieldsChanged.bind(this);
|
|
||||||
this.state = { device_id: undefined, capabilities: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { action, hass } = this.props;
|
|
||||||
const deviceId = this.state.device_id || action.device_id;
|
|
||||||
const capabilities = this.state.capabilities;
|
|
||||||
const extraFieldsData =
|
|
||||||
capabilities && capabilities.extra_fields
|
|
||||||
? capabilities.extra_fields.map((item) => {
|
|
||||||
return { [item.name]: this.props.action[item.name] };
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-device-picker
|
|
||||||
value={deviceId}
|
|
||||||
onChange={this.devicePicked}
|
|
||||||
hass={hass}
|
|
||||||
label="Device"
|
|
||||||
/>
|
|
||||||
<ha-device-action-picker
|
|
||||||
value={action}
|
|
||||||
deviceId={deviceId}
|
|
||||||
onChange={this.deviceActionPicked}
|
|
||||||
hass={hass}
|
|
||||||
label="Action"
|
|
||||||
/>
|
|
||||||
{extraFieldsData && (
|
|
||||||
<ha-form
|
|
||||||
data={Object.assign({}, ...extraFieldsData)}
|
|
||||||
onvalue-changed={this._extraFieldsChanged}
|
|
||||||
schema={this.state.capabilities.extra_fields}
|
|
||||||
computeLabel={this._extraFieldsComputeLabelCallback(hass.localize)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount() {
|
|
||||||
this.initialized = true;
|
|
||||||
if (!this.state.capabilities) {
|
|
||||||
this._getCapabilities();
|
|
||||||
}
|
|
||||||
if (this.props.action) {
|
|
||||||
this._origAction = this.props.action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidUpdate(prevProps) {
|
|
||||||
if (!deviceAutomationsEqual(prevProps.action, this.props.action)) {
|
|
||||||
this._getCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private devicePicked(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({ ...this.state, device_id: ev.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
private deviceActionPicked(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let deviceAction = ev.target.value;
|
|
||||||
if (
|
|
||||||
this._origAction &&
|
|
||||||
deviceAutomationsEqual(this._origAction, deviceAction)
|
|
||||||
) {
|
|
||||||
deviceAction = this._origAction;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, deviceAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getCapabilities() {
|
|
||||||
const action = this.props.action;
|
|
||||||
|
|
||||||
const capabilities = action.domain
|
|
||||||
? await fetchDeviceActionCapabilities(this.props.hass, action)
|
|
||||||
: null;
|
|
||||||
this.setState({ ...this.state, capabilities });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _extraFieldsChanged(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.action,
|
|
||||||
...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.actions.type.device_id.extra_fields.${schema.name}`
|
|
||||||
) || schema.name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
|
|
||||||
import YAMLTextArea from "../yaml_textarea";
|
|
||||||
import { onChangeEvent } from "../../../../common/preact/event";
|
|
||||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
|
||||||
import { EventAction } from "../../../../data/script";
|
|
||||||
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
index: number;
|
|
||||||
action: EventAction;
|
|
||||||
localize: LocalizeFunc;
|
|
||||||
onChange: (index: number, action: EventAction) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class EventActionForm extends AutomationComponent<Props> {
|
|
||||||
private onChange: (event: Event) => void;
|
|
||||||
|
|
||||||
static get defaultConfig(): EventAction {
|
|
||||||
return {
|
|
||||||
event: "",
|
|
||||||
event_data: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "action");
|
|
||||||
this.serviceDataChanged = this.serviceDataChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const {
|
|
||||||
action: { event, event_data },
|
|
||||||
localize,
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.event.event"
|
|
||||||
)}
|
|
||||||
name="event"
|
|
||||||
value={event}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
<YAMLTextArea
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.event.service_data"
|
|
||||||
)}
|
|
||||||
value={event_data}
|
|
||||||
onChange={this.serviceDataChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private serviceDataChanged(eventData) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.action,
|
|
||||||
event_data: eventData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
|
|
||||||
import ActionRow from "./action_row";
|
|
||||||
|
|
||||||
export default class Script extends Component<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.addAction = this.addAction.bind(this);
|
|
||||||
this.actionChanged = this.actionChanged.bind(this);
|
|
||||||
this.moveUp = this.moveUp.bind(this);
|
|
||||||
this.moveDown = this.moveDown.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addAction() {
|
|
||||||
const script = this.props.script.concat({
|
|
||||||
service: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onChange(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
public actionChanged(index, newValue) {
|
|
||||||
const script = this.props.script.concat();
|
|
||||||
|
|
||||||
if (newValue === null) {
|
|
||||||
script.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
script[index] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
public moveUp(index: number) {
|
|
||||||
this.move(index, index - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public moveDown(index: number) {
|
|
||||||
this.move(index, index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ script, hass, localize }) {
|
|
||||||
return (
|
|
||||||
<div class="script">
|
|
||||||
{script.map((act, idx) => (
|
|
||||||
<ActionRow
|
|
||||||
index={idx}
|
|
||||||
length={script.length}
|
|
||||||
action={act}
|
|
||||||
onChange={this.actionChanged}
|
|
||||||
moveUp={this.moveUp}
|
|
||||||
moveDown={this.moveDown}
|
|
||||||
hass={hass}
|
|
||||||
localize={localize}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-actions add-card">
|
|
||||||
<mwc-button onTap={this.addAction}>
|
|
||||||
{localize("ui.panel.config.automation.editor.actions.add")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private move(index: number, newIndex: number) {
|
|
||||||
const script = this.props.script.concat();
|
|
||||||
const action = script.splice(index, 1)[0];
|
|
||||||
script.splice(newIndex, 0, action);
|
|
||||||
this.props.onChange(script);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class SceneAction extends AutomationComponent<any> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.sceneChanged = this.sceneChanged.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sceneChanged(ev: any) {
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.action,
|
|
||||||
scene: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ action, hass }) {
|
|
||||||
const { scene } = action;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-entity-picker
|
|
||||||
value={scene}
|
|
||||||
onChange={this.sceneChanged}
|
|
||||||
hass={hass}
|
|
||||||
includeDomains={["scene"]}
|
|
||||||
allowCustomEntity
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(SceneAction as any).defaultConfig = {
|
|
||||||
scene: "",
|
|
||||||
};
|
|
@ -1,60 +0,0 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
|
|
||||||
import "../../../../components/ha-textarea";
|
|
||||||
|
|
||||||
import { onChangeEvent } from "../../../../common/preact/event";
|
|
||||||
import { AutomationComponent } from "../automation-component";
|
|
||||||
|
|
||||||
export default class WaitAction extends AutomationComponent<any> {
|
|
||||||
private onChange: (obj: any) => void;
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.onChange = onChangeEvent.bind(this, "action");
|
|
||||||
this.onTemplateChange = this.onTemplateChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets fired on mount. If empty, onChangeEvent removes attribute.
|
|
||||||
// Without the attribute this action is no longer matched to this component.
|
|
||||||
public onTemplateChange(ev) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onChange(this.props.index, {
|
|
||||||
...this.props.action,
|
|
||||||
[ev.target.getAttribute("name")]: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ action, localize }) {
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
const { wait_template, timeout } = action;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ha-textarea
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
|
|
||||||
)}
|
|
||||||
name="wait_template"
|
|
||||||
value={wait_template}
|
|
||||||
onvalue-changed={this.onTemplateChange}
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
<paper-input
|
|
||||||
label={localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.wait_template.timeout"
|
|
||||||
)}
|
|
||||||
name="timeout"
|
|
||||||
value={timeout}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(WaitAction as any).defaultConfig = {
|
|
||||||
wait_template: "",
|
|
||||||
timeout: "",
|
|
||||||
};
|
|
@ -1,80 +0,0 @@
|
|||||||
import { h, Component } from "preact";
|
|
||||||
import { safeDump, safeLoad } from "js-yaml";
|
|
||||||
import "../../../components/ha-code-editor";
|
|
||||||
|
|
||||||
const isEmpty = (obj: object) => {
|
|
||||||
for (const key in obj) {
|
|
||||||
if (obj.hasOwnProperty(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class YAMLTextArea extends Component<any, any> {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
let value: string | undefined;
|
|
||||||
try {
|
|
||||||
value =
|
|
||||||
props.value && !isEmpty(props.value)
|
|
||||||
? safeDump(props.value)
|
|
||||||
: undefined;
|
|
||||||
} catch (err) {
|
|
||||||
alert(`There was an error converting to YAML: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isvalid: true,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChange(ev) {
|
|
||||||
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.setState({
|
|
||||||
value,
|
|
||||||
isValid,
|
|
||||||
});
|
|
||||||
if (isValid) {
|
|
||||||
this.props.onChange(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render({ label }, { value, isValid }) {
|
|
||||||
const style: any = {
|
|
||||||
minWidth: 300,
|
|
||||||
width: "100%",
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{label && <p>{label}</p>}
|
|
||||||
<ha-code-editor
|
|
||||||
mode="yaml"
|
|
||||||
style={style}
|
|
||||||
value={value}
|
|
||||||
error={isValid === false}
|
|
||||||
onvalue-changed={this.onChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,336 +0,0 @@
|
|||||||
import "@polymer/app-layout/app-header/app-header";
|
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { h, render } from "preact";
|
|
||||||
|
|
||||||
import "../../../layouts/ha-app-layout";
|
|
||||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
|
||||||
import "../../../components/ha-fab";
|
|
||||||
|
|
||||||
import Script from "../js/script";
|
|
||||||
import unmountPreact from "../../../common/preact/unmount";
|
|
||||||
|
|
||||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
|
||||||
import NavigateMixin from "../../../mixins/navigate-mixin";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
import { deleteScript } from "../../../data/script";
|
|
||||||
|
|
||||||
function ScriptEditor(mountEl, props, mergeEl) {
|
|
||||||
return render(h(Script, props), mountEl, mergeEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin NavigateMixin
|
|
||||||
*/
|
|
||||||
class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style">
|
|
||||||
.errors {
|
|
||||||
padding: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--google-red-500);
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
.triggers,
|
|
||||||
.script {
|
|
||||||
margin-top: -16px;
|
|
||||||
}
|
|
||||||
.triggers ha-card,
|
|
||||||
.script ha-card {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
.add-card mwc-button {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.card-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 1;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
.card-menu paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
span[slot="introduction"] a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
ha-fab {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
z-index: 1;
|
|
||||||
margin-bottom: -80px;
|
|
||||||
transition: margin-bottom 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-fab[is-wide] {
|
|
||||||
bottom: 24px;
|
|
||||||
right: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-fab[dirty] {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-fab[rtl] {
|
|
||||||
right: auto;
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-fab[rtl][is-wide] {
|
|
||||||
bottom: 24px;
|
|
||||||
right: auto;
|
|
||||||
left: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-app-layout has-scrolling-region="">
|
|
||||||
<app-header slot="header" fixed="">
|
|
||||||
<app-toolbar>
|
|
||||||
<ha-paper-icon-button-arrow-prev
|
|
||||||
on-click="backTapped"
|
|
||||||
></ha-paper-icon-button-arrow-prev>
|
|
||||||
<div main-title>[[computeHeader(script)]]</div>
|
|
||||||
<template is="dom-if" if="[[!creatingNew]]">
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:delete"
|
|
||||||
title="[[localize('ui.panel.config.script.editor.delete_script')]]"
|
|
||||||
on-click="_delete"
|
|
||||||
></paper-icon-button>
|
|
||||||
</template>
|
|
||||||
</app-toolbar>
|
|
||||||
</app-header>
|
|
||||||
<div class="content">
|
|
||||||
<template is="dom-if" if="[[errors]]">
|
|
||||||
<div class="errors">[[errors]]</div>
|
|
||||||
</template>
|
|
||||||
<div id="root"></div>
|
|
||||||
</div>
|
|
||||||
<ha-fab
|
|
||||||
slot="fab"
|
|
||||||
is-wide$="[[isWide]]"
|
|
||||||
dirty$="[[dirty]]"
|
|
||||||
icon="hass:content-save"
|
|
||||||
title="[[localize('ui.common.save')]]"
|
|
||||||
on-click="saveScript"
|
|
||||||
rtl$="[[rtl]]"
|
|
||||||
></ha-fab>
|
|
||||||
</ha-app-layout>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
errors: {
|
|
||||||
type: Object,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
dirty: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
config: {
|
|
||||||
type: Object,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
script: {
|
|
||||||
type: Object,
|
|
||||||
observer: "scriptChanged",
|
|
||||||
},
|
|
||||||
|
|
||||||
creatingNew: {
|
|
||||||
type: Boolean,
|
|
||||||
observer: "creatingNewChanged",
|
|
||||||
},
|
|
||||||
|
|
||||||
isWide: {
|
|
||||||
type: Boolean,
|
|
||||||
observer: "_updateComponent",
|
|
||||||
},
|
|
||||||
|
|
||||||
_rendered: {
|
|
||||||
type: Object,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderScheduled: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
rtl: {
|
|
||||||
type: Boolean,
|
|
||||||
reflectToAttribute: true,
|
|
||||||
computed: "_computeRTL(hass)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
this.configChanged = this.configChanged.bind(this);
|
|
||||||
super.ready(); // This call will initialize preact.
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
if (this._rendered) {
|
|
||||||
unmountPreact(this._rendered);
|
|
||||||
this._rendered = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configChanged(config) {
|
|
||||||
// onChange gets called a lot during initial rendering causing recursing calls.
|
|
||||||
if (this._rendered === null) return;
|
|
||||||
this.config = config;
|
|
||||||
this.errors = null;
|
|
||||||
this.dirty = true;
|
|
||||||
this._updateComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptChanged(newVal, oldVal) {
|
|
||||||
if (!newVal) return;
|
|
||||||
if (!this.hass) {
|
|
||||||
setTimeout(() => this.scriptChanged(newVal, oldVal), 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (oldVal && oldVal.entity_id === newVal.entity_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hass
|
|
||||||
.callApi(
|
|
||||||
"get",
|
|
||||||
"config/script/config/" + computeObjectId(newVal.entity_id)
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
(config) => {
|
|
||||||
// Normalize data: ensure sequence is a list
|
|
||||||
// Happens when people copy paste their scripts into the config
|
|
||||||
var value = config.sequence;
|
|
||||||
if (value && !Array.isArray(value)) {
|
|
||||||
config.sequence = [value];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dirty = false;
|
|
||||||
this.config = config;
|
|
||||||
this._updateComponent();
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
alert(
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.load_error_not_editable"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
creatingNewChanged(newVal) {
|
|
||||||
if (!newVal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.dirty = false;
|
|
||||||
this.config = {
|
|
||||||
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
|
|
||||||
sequence: [{ service: "", data: {} }],
|
|
||||||
};
|
|
||||||
this._updateComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
backTapped() {
|
|
||||||
if (
|
|
||||||
this.dirty &&
|
|
||||||
// eslint-disable-next-line
|
|
||||||
!confirm(
|
|
||||||
this.hass.localize("ui.panel.config.common.editor.confirm_unsaved")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateComponent() {
|
|
||||||
if (this._renderScheduled || !this.hass || !this.config) return;
|
|
||||||
this._renderScheduled = true;
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
this._rendered = ScriptEditor(
|
|
||||||
this.$.root,
|
|
||||||
{
|
|
||||||
script: this.config,
|
|
||||||
onChange: this.configChanged,
|
|
||||||
isWide: this.isWide,
|
|
||||||
hass: this.hass,
|
|
||||||
localize: this.localize,
|
|
||||||
},
|
|
||||||
this._rendered
|
|
||||||
);
|
|
||||||
this._renderScheduled = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _delete() {
|
|
||||||
if (
|
|
||||||
!confirm(
|
|
||||||
this.hass.localize("ui.panel.config.script.editor.delete_confirm")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await deleteScript(this.hass, computeObjectId(this.script.entity_id));
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
saveScript() {
|
|
||||||
var id = this.creatingNew
|
|
||||||
? "" + Date.now()
|
|
||||||
: computeObjectId(this.script.entity_id);
|
|
||||||
this.hass.callApi("post", "config/script/config/" + id, this.config).then(
|
|
||||||
() => {
|
|
||||||
this.dirty = false;
|
|
||||||
|
|
||||||
if (this.creatingNew) {
|
|
||||||
this.navigate(`/config/script/edit/${id}`, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(errors) => {
|
|
||||||
this.errors = errors.body.message;
|
|
||||||
throw errors;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeHeader(script) {
|
|
||||||
return script
|
|
||||||
? this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.header",
|
|
||||||
"name",
|
|
||||||
computeStateName(script)
|
|
||||||
)
|
|
||||||
: this.hass.localize("ui.panel.config.script.editor.default_name");
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRTL(hass) {
|
|
||||||
return computeRTL(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-script-editor", HaScriptEditor);
|
|
322
src/panels/config/script/ha-script-editor.ts
Normal file
322
src/panels/config/script/ha-script-editor.ts
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
|
import "../../../components/ha-fab";
|
||||||
|
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
ScriptEntity,
|
||||||
|
ScriptConfig,
|
||||||
|
deleteScript,
|
||||||
|
} from "../../../data/script";
|
||||||
|
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
|
||||||
|
import "../../../layouts/ha-app-layout";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import "../automation/action/ha-automation-action";
|
||||||
|
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||||
|
|
||||||
|
export class HaScriptEditor extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public script!: ScriptEntity;
|
||||||
|
@property() public isWide?: boolean;
|
||||||
|
@property() public creatingNew?: boolean;
|
||||||
|
@property() private _config?: ScriptConfig;
|
||||||
|
@property() private _dirty?: boolean;
|
||||||
|
@property() private _errors?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<ha-app-layout has-scrolling-region>
|
||||||
|
<app-header slot="header" fixed>
|
||||||
|
<app-toolbar>
|
||||||
|
<ha-paper-icon-button-arrow-prev
|
||||||
|
@click=${this._backTapped}
|
||||||
|
></ha-paper-icon-button-arrow-prev>
|
||||||
|
<div main-title>
|
||||||
|
${this.script
|
||||||
|
? computeStateName(this.script)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.default_name"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
${this.creatingNew
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<paper-icon-button
|
||||||
|
title="${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.delete_script"
|
||||||
|
)}"
|
||||||
|
icon="hass:delete"
|
||||||
|
@click=${this._delete}
|
||||||
|
></paper-icon-button>
|
||||||
|
`}
|
||||||
|
</app-toolbar>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
${this._errors
|
||||||
|
? html`
|
||||||
|
<div class="errors">${this._errors}</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div
|
||||||
|
class="${classMap({
|
||||||
|
rtl: computeRTL(this.hass),
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
${this._config
|
||||||
|
? html`
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">${this._config.alias}</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.introduction"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<paper-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.alias"
|
||||||
|
)}
|
||||||
|
name="alias"
|
||||||
|
.value=${this._config.alias}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
>
|
||||||
|
</paper-input>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.sequence"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.sequence_sentence"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://home-assistant.io/docs/scripts/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.link_available_actions"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ha-automation-action
|
||||||
|
.actions=${this._config.sequence}
|
||||||
|
@value-changed=${this._sequenceChanged}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-automation-action>
|
||||||
|
</ha-config-section>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ha-fab
|
||||||
|
slot="fab"
|
||||||
|
?is-wide="${this.isWide}"
|
||||||
|
?dirty="${this._dirty}"
|
||||||
|
icon="hass:content-save"
|
||||||
|
.title="${this.hass.localize("ui.common.save")}"
|
||||||
|
@click=${this._saveScript}
|
||||||
|
class="${classMap({
|
||||||
|
rtl: computeRTL(this.hass),
|
||||||
|
})}"
|
||||||
|
></ha-fab>
|
||||||
|
</ha-app-layout>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
|
const oldScript = changedProps.get("script") as ScriptEntity;
|
||||||
|
if (
|
||||||
|
changedProps.has("script") &&
|
||||||
|
this.script &&
|
||||||
|
this.hass &&
|
||||||
|
// Only refresh config if we picked a new script. If same ID, don't fetch it.
|
||||||
|
(!oldScript || oldScript.entity_id !== this.script.entity_id)
|
||||||
|
) {
|
||||||
|
this.hass
|
||||||
|
.callApi<ScriptConfig>(
|
||||||
|
"GET",
|
||||||
|
`config/script/config/${computeObjectId(this.script.entity_id)}`
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(config) => {
|
||||||
|
// Normalize data: ensure sequence is a list
|
||||||
|
// Happens when people copy paste their scripts into the config
|
||||||
|
const value = config.sequence;
|
||||||
|
if (value && !Array.isArray(value)) {
|
||||||
|
config.sequence = [value];
|
||||||
|
}
|
||||||
|
this._dirty = false;
|
||||||
|
this._config = config;
|
||||||
|
},
|
||||||
|
(resp) => {
|
||||||
|
alert(
|
||||||
|
resp.status_code === 404
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.load_error_not_editable"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.load_error_unknown",
|
||||||
|
"err_no",
|
||||||
|
resp.status_code
|
||||||
|
)
|
||||||
|
);
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
|
||||||
|
this._dirty = false;
|
||||||
|
this._config = {
|
||||||
|
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
|
||||||
|
sequence: [{ service: "" }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const name = (ev.target as any)?.name;
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newVal = ev.detail.value;
|
||||||
|
|
||||||
|
if ((this._config![name] || "") === newVal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._config = { ...this._config!, [name]: newVal };
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sequenceChanged(ev: CustomEvent): void {
|
||||||
|
this._config = { ...this._config!, sequence: ev.detail.value as Action[] };
|
||||||
|
this._errors = undefined;
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _backTapped(): void {
|
||||||
|
if (this._dirty) {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.common.editor.confirm_unsaved"
|
||||||
|
),
|
||||||
|
confirmBtnText: this.hass!.localize("ui.common.yes"),
|
||||||
|
cancelBtnText: this.hass!.localize("ui.common.no"),
|
||||||
|
confirm: () => history.back(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _delete() {
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
this.hass.localize("ui.panel.config.script.editor.delete_confirm")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await deleteScript(this.hass, computeObjectId(this.script.entity_id));
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _saveScript(): void {
|
||||||
|
const id = this.creatingNew
|
||||||
|
? "" + Date.now()
|
||||||
|
: computeObjectId(this.script.entity_id);
|
||||||
|
this.hass!.callApi("POST", "config/script/config/" + id, this._config).then(
|
||||||
|
() => {
|
||||||
|
this._dirty = false;
|
||||||
|
|
||||||
|
if (this.creatingNew) {
|
||||||
|
navigate(this, `/config/script/edit/${id}`, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(errors) => {
|
||||||
|
this._errors = errors.body.message;
|
||||||
|
throw errors;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
ha-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.errors {
|
||||||
|
padding: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
span[slot="introduction"] a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
ha-fab {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
margin-bottom: -80px;
|
||||||
|
transition: margin-bottom 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-fab[is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-fab[dirty] {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-fab.rtl {
|
||||||
|
right: auto;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-fab[is-wide].rtl {
|
||||||
|
bottom: 24px;
|
||||||
|
right: auto;
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("ha-script-editor", HaScriptEditor);
|
@ -1,4 +1,4 @@
|
|||||||
import { ZHADevice } from "../../../data/zha";
|
import { ZHADevice, ZHAGroup } from "../../../data/zha";
|
||||||
|
|
||||||
export const formatAsPaddedHex = (value: string | number): string => {
|
export const formatAsPaddedHex = (value: string | number): string => {
|
||||||
let hex = value;
|
let hex = value;
|
||||||
@ -13,3 +13,9 @@ export const sortZHADevices = (a: ZHADevice, b: ZHADevice): number => {
|
|||||||
const nameb = b.user_given_name ? b.user_given_name : b.name;
|
const nameb = b.user_given_name ? b.user_given_name : b.name;
|
||||||
return nameA.localeCompare(nameb);
|
return nameA.localeCompare(nameb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sortZHAGroups = (a: ZHAGroup, b: ZHAGroup): number => {
|
||||||
|
const nameA = a.name;
|
||||||
|
const nameb = b.name;
|
||||||
|
return nameA.localeCompare(nameb);
|
||||||
|
};
|
||||||
|
@ -5,6 +5,7 @@ import "./zha-cluster-attributes";
|
|||||||
import "./zha-cluster-commands";
|
import "./zha-cluster-commands";
|
||||||
import "./zha-network";
|
import "./zha-network";
|
||||||
import "./zha-node";
|
import "./zha-node";
|
||||||
|
import "./zha-groups-tile";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -45,6 +46,11 @@ export class HaConfigZha extends LitElement {
|
|||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
></zha-network>
|
></zha-network>
|
||||||
|
|
||||||
|
<zha-groups-tile
|
||||||
|
.isWide="${this.isWide}"
|
||||||
|
.hass="${this.hass}"
|
||||||
|
></zha-groups-tile>
|
||||||
|
|
||||||
<zha-node
|
<zha-node
|
||||||
.isWide="${this.isWide}"
|
.isWide="${this.isWide}"
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
|
197
src/panels/config/zha/zha-add-group-page.ts
Normal file
197
src/panels/config/zha/zha-add-group-page.ts
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import {
|
||||||
|
property,
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
customElement,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
import "../../../layouts/hass-subpage";
|
||||||
|
import "../../../layouts/hass-error-screen";
|
||||||
|
import "../ha-config-section";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import {
|
||||||
|
ZHADevice,
|
||||||
|
fetchGroupableDevices,
|
||||||
|
addGroup,
|
||||||
|
ZHAGroup,
|
||||||
|
} from "../../../data/zha";
|
||||||
|
import "./zha-devices-data-table";
|
||||||
|
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
@customElement("zha-add-group-page")
|
||||||
|
export class ZHAAddGroupPage extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
@property() public devices: ZHADevice[] = [];
|
||||||
|
@property() private _processingAdd: boolean = false;
|
||||||
|
@property() private _groupName: string = "";
|
||||||
|
|
||||||
|
private _firstUpdatedCalled: boolean = false;
|
||||||
|
private _selectedDevicesToAdd: string[] = [];
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this.hass && this._firstUpdatedCalled) {
|
||||||
|
this._fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
if (this.hass) {
|
||||||
|
this._fetchData();
|
||||||
|
}
|
||||||
|
this._firstUpdatedCalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.header=${this.hass.localize("ui.panel.config.zha.groups.create_group")}
|
||||||
|
>
|
||||||
|
<ha-config-section .isWide=${!this.narrow}>
|
||||||
|
<p slot="introduction">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zha.groups.create_group_details"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<paper-input
|
||||||
|
type="string"
|
||||||
|
.value="${this._groupName}"
|
||||||
|
@value-changed=${this._handleNameChange}
|
||||||
|
placeholder="${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.groups.group_name_placeholder"
|
||||||
|
)}"
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<zha-devices-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.devices=${this.devices}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
selectable
|
||||||
|
@selection-changed=${this._handleAddSelectionChanged}
|
||||||
|
class="table"
|
||||||
|
>
|
||||||
|
</zha-devices-data-table>
|
||||||
|
|
||||||
|
<div class="paper-dialog-buttons">
|
||||||
|
<mwc-button
|
||||||
|
.disabled="${!this._groupName ||
|
||||||
|
this._groupName === "" ||
|
||||||
|
this._processingAdd}"
|
||||||
|
@click="${this._createGroup}"
|
||||||
|
class="button"
|
||||||
|
>
|
||||||
|
<paper-spinner
|
||||||
|
?active="${this._processingAdd}"
|
||||||
|
alt="${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.groups.creating_group"
|
||||||
|
)}"
|
||||||
|
></paper-spinner>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.groups.create"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</ha-config-section>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData() {
|
||||||
|
this.devices = await fetchGroupableDevices(this.hass!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleAddSelectionChanged(ev: CustomEvent): void {
|
||||||
|
const changedSelection = ev.detail as SelectionChangedEvent;
|
||||||
|
const entity = changedSelection.id;
|
||||||
|
if (changedSelection.selected) {
|
||||||
|
this._selectedDevicesToAdd.push(entity);
|
||||||
|
} else {
|
||||||
|
const index = this._selectedDevicesToAdd.indexOf(entity);
|
||||||
|
if (index !== -1) {
|
||||||
|
this._selectedDevicesToAdd.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._selectedDevicesToAdd = [...this._selectedDevicesToAdd];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createGroup(): Promise<void> {
|
||||||
|
this._processingAdd = true;
|
||||||
|
const group: ZHAGroup = await addGroup(
|
||||||
|
this.hass,
|
||||||
|
this._groupName,
|
||||||
|
this._selectedDevicesToAdd
|
||||||
|
);
|
||||||
|
this._selectedDevicesToAdd = [];
|
||||||
|
this._processingAdd = false;
|
||||||
|
this._groupName = "";
|
||||||
|
navigate(this, `/config/zha/group/${group.group_id}`, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleNameChange(ev: PolymerChangedEvent<string>) {
|
||||||
|
const target = ev.currentTarget as PaperInputElement;
|
||||||
|
this._groupName = target.value || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
css`
|
||||||
|
.header {
|
||||||
|
font-family: var(--paper-font-display1_-_font-family);
|
||||||
|
-webkit-font-smoothing: var(
|
||||||
|
--paper-font-display1_-_-webkit-font-smoothing
|
||||||
|
);
|
||||||
|
font-size: var(--paper-font-display1_-_font-size);
|
||||||
|
font-weight: var(--paper-font-display1_-_font-weight);
|
||||||
|
letter-spacing: var(--paper-font-display1_-_letter-spacing);
|
||||||
|
line-height: var(--paper-font-display1_-_line-height);
|
||||||
|
opacity: var(--dark-primary-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
height: 400px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-config-section *:last-child {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
mwc-button paper-spinner {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
paper-spinner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
paper-spinner[active] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.paper-dialog-buttons {
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.paper-dialog-buttons .warning {
|
||||||
|
--mdc-theme-primary: var(--google-red-500);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
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