diff --git a/config/babel.js b/config/babel.js
index 1c073245dc..67021b92c4 100644
--- a/config/babel.js
+++ b/config/babel.js
@@ -3,7 +3,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
throw Error("latestBuild not defined for babel loader config");
}
return {
- test: /\.m?js$|\.ts$/,
+ test: /\.m?js$|\.tsx?$/,
use: {
loader: "babel-loader",
options: {
@@ -12,7 +12,12 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
require("@babel/preset-env").default,
{ modules: false },
],
- require("@babel/preset-typescript").default,
+ [
+ require("@babel/preset-typescript").default,
+ {
+ jsxPragma: "h",
+ },
+ ],
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
@@ -28,6 +33,14 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
pragma: "h",
},
],
+ [
+ require("@babel/plugin-proposal-decorators").default,
+ { decoratorsBeforeExport: true },
+ ],
+ [
+ require("@babel/plugin-proposal-class-properties").default,
+ { loose: true },
+ ],
],
},
},
diff --git a/config/minimizer.js b/config/minimizer.js
deleted file mode 100644
index ce16504520..0000000000
--- a/config/minimizer.js
+++ /dev/null
@@ -1,29 +0,0 @@
-const BabelMinifyPlugin = require("babel-minify-webpack-plugin");
-
-module.exports.minimizer = [
- // Took options from Polymer build tool
- // https://github.com/Polymer/tools/blob/master/packages/build/src/js-transform.ts
- new BabelMinifyPlugin(
- {
- // Disable the minify-constant-folding plugin because it has a bug relating
- // to invalid substitution of constant values into export specifiers:
- // https://github.com/babel/minify/issues/820
- evaluate: false,
-
- // TODO(aomarks) Find out why we disabled this plugin.
- simplifyComparisons: false,
-
- // Prevent removal of things that babel thinks are unreachable, but sometimes
- // gets wrong: https://github.com/Polymer/tools/issues/724
- deadcode: false,
-
- // Disable the simplify plugin because it can eat some statements preceeding
- // loops. https://github.com/babel/minify/issues/824
- simplify: false,
-
- // This is breaking ES6 output. https://github.com/Polymer/tools/issues/261
- mangle: false,
- },
- {}
- ),
-];
diff --git a/config/webpack.js b/config/webpack.js
new file mode 100644
index 0000000000..118fb0e23f
--- /dev/null
+++ b/config/webpack.js
@@ -0,0 +1,60 @@
+const webpack = require("webpack");
+const path = require("path");
+const BabelMinifyPlugin = require("babel-minify-webpack-plugin");
+
+module.exports.resolve = {
+ extensions: [".ts", ".js", ".json", ".tsx"],
+ alias: {
+ react: "preact-compat",
+ "react-dom": "preact-compat",
+ // Not necessary unless you consume a module using `createClass`
+ "create-react-class": "preact-compat/lib/create-react-class",
+ // Not necessary unless you consume a module requiring `react-dom-factories`
+ "react-dom-factories": "preact-compat/lib/react-dom-factories",
+ },
+};
+
+module.exports.plugins = [
+ // Ignore moment.js locales
+ new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
+ // Color.js is bloated, it contains all color definitions for all material color sets.
+ new webpack.NormalModuleReplacementPlugin(
+ /@polymer\/paper-styles\/color\.js$/,
+ path.resolve(__dirname, "../src/util/empty.js")
+ ),
+ // Ignore roboto pointing at CDN. We use local font-roboto-local.
+ new webpack.NormalModuleReplacementPlugin(
+ /@polymer\/font-roboto\/roboto\.js$/,
+ path.resolve(__dirname, "../src/util/empty.js")
+ ),
+];
+
+module.exports.optimization = {
+ minimizer: [
+ // Took options from Polymer build tool
+ // https://github.com/Polymer/tools/blob/master/packages/build/src/js-transform.ts
+ new BabelMinifyPlugin(
+ {
+ // Disable the minify-constant-folding plugin because it has a bug relating
+ // to invalid substitution of constant values into export specifiers:
+ // https://github.com/babel/minify/issues/820
+ evaluate: false,
+
+ // TODO(aomarks) Find out why we disabled this plugin.
+ simplifyComparisons: false,
+
+ // Prevent removal of things that babel thinks are unreachable, but sometimes
+ // gets wrong: https://github.com/Polymer/tools/issues/724
+ deadcode: false,
+
+ // Disable the simplify plugin because it can eat some statements preceeding
+ // loops. https://github.com/babel/minify/issues/824
+ simplify: false,
+
+ // This is breaking ES6 output. https://github.com/Polymer/tools/issues/261
+ mangle: false,
+ },
+ {}
+ ),
+ ],
+};
diff --git a/demo/script/size_stats b/demo/script/size_stats
new file mode 100755
index 0000000000..3afbe6c9a0
--- /dev/null
+++ b/demo/script/size_stats
@@ -0,0 +1,11 @@
+#!/bin/sh
+# Analyze stats
+
+# Stop on errors
+set -e
+
+cd "$(dirname "$0")/.."
+
+STATS=1 NODE_ENV=production ../node_modules/.bin/webpack --profile --json > compilation-stats.json
+npx webpack-bundle-analyzer compilation-stats.json dist
+rm compilation-stats.json
diff --git a/demo/src/stubs/lovelace.ts b/demo/src/stubs/lovelace.ts
index f3d4b300da..d1d6c2296b 100644
--- a/demo/src/stubs/lovelace.ts
+++ b/demo/src/stubs/lovelace.ts
@@ -3,7 +3,6 @@ import "../custom-cards/ha-demo-card";
// tslint:disable-next-line
import { HADemoCard } from "../custom-cards/ha-demo-card";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
-import { HUIView } from "../../../src/panels/lovelace/hui-view";
import { selectedDemoConfig } from "../configs/demo-configs";
export const mockLovelace = (hass: MockHomeAssistant) => {
@@ -16,13 +15,17 @@ export const mockLovelace = (hass: MockHomeAssistant) => {
hass.mockWS("lovelace/config/save", () => Promise.resolve());
};
-// Patch HUI-VIEW to make the lovelace object available to the demo card
-const oldCreateCard = HUIView.prototype.createCardElement;
+customElements.whenDefined("hui-view").then(() => {
+ // tslint:disable-next-line
+ const HUIView = customElements.get("hui-view");
+ // Patch HUI-VIEW to make the lovelace object available to the demo card
+ const oldCreateCard = HUIView.prototype.createCardElement;
-HUIView.prototype.createCardElement = function(config) {
- const el = oldCreateCard.call(this, config);
- if (el.tagName === "HA-DEMO-CARD") {
- (el as HADemoCard).lovelace = this.lovelace;
- }
- return el;
-};
+ HUIView.prototype.createCardElement = function(config) {
+ const el = oldCreateCard.call(this, config);
+ if (el.tagName === "HA-DEMO-CARD") {
+ (el as HADemoCard).lovelace = this.lovelace;
+ }
+ return el;
+ };
+});
diff --git a/demo/webpack.config.js b/demo/webpack.config.js
index 851a983f30..3ba0f389df 100644
--- a/demo/webpack.config.js
+++ b/demo/webpack.config.js
@@ -3,10 +3,12 @@ const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const { babelLoaderConfig } = require("../config/babel.js");
-const { minimizer } = require("../config/babel.js");
+const webpackBase = require("../config/webpack.js");
const isProd = process.env.NODE_ENV === "production";
-const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
+const isStatsBuild = process.env.STATS === "1";
+const chunkFilename =
+ isProd && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
const buildPath = path.resolve(__dirname, "dist");
const publicPath = "/";
@@ -14,9 +16,7 @@ const latestBuild = false;
module.exports = {
mode: isProd ? "production" : "development",
- // Disabled in prod while we make Home Assistant able to serve the right files.
- // Was source-map
- devtool: isProd ? "none" : "inline-source-map",
+ devtool: isProd ? "cheap-source-map" : "inline-source-map",
entry: {
main: "./src/entrypoint.ts",
compatibility: "../src/entrypoints/compatibility.js",
@@ -39,9 +39,7 @@ module.exports = {
},
],
},
- optimization: {
- minimizer,
- },
+ optimization: webpackBase.optimization,
plugins: [
new webpack.DefinePlugin({
__DEV__: false,
@@ -71,23 +69,14 @@ module.exports = {
to: "static/images/leaflet/",
},
]),
+ ...webpackBase.plugins,
isProd &&
new WorkboxPlugin.GenerateSW({
swDest: "service_worker_es5.js",
importWorkboxFrom: "local",
}),
].filter(Boolean),
- resolve: {
- extensions: [".ts", ".js", ".json"],
- alias: {
- react: "preact-compat",
- "react-dom": "preact-compat",
- // Not necessary unless you consume a module using `createClass`
- "create-react-class": "preact-compat/lib/create-react-class",
- // Not necessary unless you consume a module requiring `react-dom-factories`
- "react-dom-factories": "preact-compat/lib/react-dom-factories",
- },
- },
+ resolve: webpackBase.resolve,
output: {
filename: "[name].js",
chunkFilename: chunkFilename,
diff --git a/gallery/src/demos/demo-hui-gauge-card.ts b/gallery/src/demos/demo-hui-gauge-card.ts
index 6f9b4c4ac4..5f5e43644f 100644
--- a/gallery/src/demos/demo-hui-gauge-card.ts
+++ b/gallery/src/demos/demo-hui-gauge-card.ts
@@ -2,6 +2,19 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
+import { getEntity } from "../../../src/fake_data/entity";
+import { provideHass } from "../../../src/fake_data/provide_hass";
+
+const ENTITIES = [
+ getEntity("sensor", "brightness", "12", {}),
+ getEntity("plant", "bonsai", "ok", {}),
+ getEntity("sensor", "outside_humidity", "54", {
+ unit_of_measurement: "%",
+ }),
+ getEntity("sensor", "outside_temperature", "15.6", {
+ unit_of_measurement: "°C",
+ }),
+];
const CONFIGS = [
{
@@ -66,7 +79,7 @@ const CONFIGS = [
class DemoGaugeEntity extends PolymerElement {
static get template() {
return html`
-
+
`;
}
@@ -78,6 +91,12 @@ class DemoGaugeEntity extends PolymerElement {
},
};
}
+
+ public ready() {
+ super.ready();
+ const hass = provideHass(this.$.demos);
+ hass.addEntities(ENTITIES);
+ }
}
customElements.define("demo-hui-gauge-card", DemoGaugeEntity);
diff --git a/gallery/src/demos/demo-hui-picture-entity-card.ts b/gallery/src/demos/demo-hui-picture-entity-card.ts
index 7940a5ad00..1378707e45 100644
--- a/gallery/src/demos/demo-hui-picture-entity-card.ts
+++ b/gallery/src/demos/demo-hui-picture-entity-card.ts
@@ -2,6 +2,17 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
+import { provideHass } from "../../../src/fake_data/provide_hass";
+import { getEntity } from "../../../src/fake_data/entity";
+
+const ENTITIES = [
+ getEntity("light", "kitchen_lights", "on", {
+ friendly_name: "Kitchen Lights",
+ }),
+ getEntity("light", "bed_light", "off", {
+ friendly_name: "Bed Light",
+ }),
+];
const CONFIGS = [
{
@@ -10,6 +21,8 @@ const CONFIGS = [
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
+ tap_action:
+ action: toggle
`,
},
{
@@ -18,6 +31,8 @@ const CONFIGS = [
- type: picture-entity
image: /images/bed.png
entity: light.bed_light
+ tap_action:
+ action: toggle
`,
},
{
@@ -68,7 +83,7 @@ const CONFIGS = [
class DemoPicEntity extends PolymerElement {
static get template() {
return html`
-
+
`;
}
@@ -80,6 +95,12 @@ class DemoPicEntity extends PolymerElement {
},
};
}
+
+ public ready() {
+ super.ready();
+ const hass = provideHass(this.$.demos);
+ hass.addEntities(ENTITIES);
+ }
}
customElements.define("demo-hui-picture-entity-card", DemoPicEntity);
diff --git a/gallery/src/demos/demo-hui-picture-glance-card.ts b/gallery/src/demos/demo-hui-picture-glance-card.ts
index 2ab8d7c32c..faae58bd7b 100644
--- a/gallery/src/demos/demo-hui-picture-glance-card.ts
+++ b/gallery/src/demos/demo-hui-picture-glance-card.ts
@@ -2,6 +2,25 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
+import { getEntity } from "../../../src/fake_data/entity";
+import { provideHass } from "../../../src/fake_data/provide_hass";
+
+const ENTITIES = [
+ getEntity("switch", "decorative_lights", "on", {
+ friendly_name: "Decorative Lights",
+ }),
+ getEntity("light", "ceiling_lights", "on", {
+ friendly_name: "Ceiling Lights",
+ }),
+ getEntity("binary_sensor", "movement_backyard", "on", {
+ friendly_name: "Movement Backyard",
+ device_class: "moving",
+ }),
+ getEntity("binary_sensor", "basement_floor_wet", "off", {
+ friendly_name: "Basement Floor Wet",
+ device_class: "moisture",
+ }),
+];
const CONFIGS = [
{
@@ -105,7 +124,7 @@ const CONFIGS = [
class DemoPicGlance extends PolymerElement {
static get template() {
return html`
-
+
`;
}
@@ -117,6 +136,12 @@ class DemoPicGlance extends PolymerElement {
},
};
}
+
+ public ready() {
+ super.ready();
+ const hass = provideHass(this.$.demos);
+ hass.addEntities(ENTITIES);
+ }
}
customElements.define("demo-hui-picture-glance-card", DemoPicGlance);
diff --git a/gallery/webpack.config.js b/gallery/webpack.config.js
index 032efa198d..82a7008709 100644
--- a/gallery/webpack.config.js
+++ b/gallery/webpack.config.js
@@ -1,7 +1,7 @@
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { babelLoaderConfig } = require("../config/babel.js");
-const { minimizer } = require("../config/babel.js");
+const webpackBase = require("../config/webpack.js");
const isProd = process.env.NODE_ENV === "production";
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
@@ -32,9 +32,7 @@ module.exports = {
},
],
},
- optimization: {
- minimizer,
- },
+ optimization: webpackBase.optimization,
plugins: [
new CopyWebpackPlugin([
"public",
@@ -63,9 +61,7 @@ module.exports = {
},
}),
].filter(Boolean),
- resolve: {
- extensions: [".ts", ".js", ".json"],
- },
+ resolve: webpackBase.resolve,
output: {
filename: "[name].js",
chunkFilename: chunkFilename,
diff --git a/package.json b/package.json
index 17c029fdaf..111db689f4 100644
--- a/package.json
+++ b/package.json
@@ -64,25 +64,24 @@
"@polymer/polymer": "^3.0.5",
"@vaadin/vaadin-combo-box": "^4.2.0",
"@vaadin/vaadin-date-picker": "^3.3.1",
- "@webcomponents/shadycss": "^1.6.0",
- "@webcomponents/webcomponentsjs": "^2.2.0",
+ "@webcomponents/shadycss": "^1.9.0",
+ "@webcomponents/webcomponentsjs": "^2.2.6",
"chart.js": "~2.7.2",
"chartjs-chart-timeline": "^0.2.1",
"codemirror": "^5.43.0",
"deep-clone-simple": "^1.1.1",
"es6-object-assign": "^1.1.0",
- "eslint-import-resolver-webpack": "^0.10.1",
"fecha": "^3.0.0",
- "gulp-hash-filename": "^2.0.1",
"home-assistant-js-websocket": "^3.2.4",
"intl-messageformat": "^2.2.0",
"jquery": "^3.3.1",
"js-yaml": "^3.12.0",
"leaflet": "^1.3.4",
- "lit-element": "2.0.0-rc.5",
- "lit-html": "1.0.0-rc.2",
+ "lit-element": "^2.0.0",
+ "lit-html": "^1.0.0",
"marked": "^0.6.0",
"mdn-polyfills": "^5.12.0",
+ "memoize-one": "^5.0.0",
"moment": "^2.22.2",
"preact": "^8.3.1",
"preact-compat": "^3.18.4",
@@ -97,6 +96,8 @@
"devDependencies": {
"@babel/core": "^7.1.2",
"@babel/plugin-external-helpers": "^7.0.0",
+ "@babel/plugin-proposal-class-properties": "^7.3.0",
+ "@babel/plugin-proposal-decorators": "^7.3.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
@@ -105,6 +106,7 @@
"@gfx/zopfli": "^1.0.9",
"@types/chai": "^4.1.7",
"@types/codemirror": "^0.0.71",
+ "@types/memoize-one": "^4.1.0",
"@types/mocha": "^5.2.5",
"babel-eslint": "^10",
"babel-loader": "^8.0.4",
@@ -116,12 +118,14 @@
"eslint": "^5.6.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^4.0.0",
+ "eslint-import-resolver-webpack": "^0.10.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.11.1",
"gulp": "^3.9.1",
"gulp-foreach": "^0.1.0",
"gulp-hash": "^4.2.2",
+ "gulp-hash-filename": "^2.0.1",
"gulp-insert": "^0.5.0",
"gulp-json-transform": "^0.4.5",
"gulp-jsonminify": "^1.1.0",
@@ -156,8 +160,8 @@
},
"resolutions": {
"@polymer/polymer": "3.1.0",
- "@webcomponents/webcomponentsjs": "2.2.1",
- "@webcomponents/shadycss": "^1.6.0",
+ "@webcomponents/webcomponentsjs": "^2.2.6",
+ "@webcomponents/shadycss": "^1.9.0",
"@vaadin/vaadin-overlay": "3.2.2",
"@vaadin/vaadin-lumo-styles": "1.3.0"
},
diff --git a/setup.py b/setup.py
index 19c01b4d00..85ac515d8c 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
- version="20190203.0",
+ version="20190212.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
diff --git a/src/common/file/b64-to-blob.ts b/src/common/file/b64-to-blob.ts
new file mode 100644
index 0000000000..969cfcae1a
--- /dev/null
+++ b/src/common/file/b64-to-blob.ts
@@ -0,0 +1,20 @@
+// https://stackoverflow.com/a/16245768
+export const b64toBlob = (b64Data, contentType = "", sliceSize = 512) => {
+ const byteCharacters = atob(b64Data);
+ const byteArrays: Uint8Array[] = [];
+
+ for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
+ const slice = byteCharacters.slice(offset, offset + sliceSize);
+
+ const byteNumbers = new Array(slice.length);
+ for (let i = 0; i < slice.length; i++) {
+ byteNumbers[i] = slice.charCodeAt(i);
+ }
+
+ const byteArray = new Uint8Array(byteNumbers);
+
+ byteArrays.push(byteArray);
+ }
+
+ return new Blob(byteArrays, { type: contentType });
+};
diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts
new file mode 100644
index 0000000000..5fe0720c95
--- /dev/null
+++ b/src/components/entity/ha-entities-picker.ts
@@ -0,0 +1,120 @@
+import {
+ LitElement,
+ TemplateResult,
+ property,
+ html,
+ customElement,
+} from "lit-element";
+import "@polymer/paper-icon-button/paper-icon-button-light";
+
+import { HomeAssistant } from "../../types";
+import { PolymerChangedEvent } from "../../polymer-types";
+import { fireEvent } from "../../common/dom/fire_event";
+import isValidEntityId from "../../common/entity/valid_entity_id";
+
+import "./ha-entity-picker";
+// Not a duplicate, type import
+// tslint:disable-next-line
+import { HaEntityPickerEntityFilterFunc } from "./ha-entity-picker";
+import { HassEntity } from "home-assistant-js-websocket";
+
+@customElement("ha-entities-picker")
+class HaEntitiesPickerLight extends LitElement {
+ @property() public hass?: HomeAssistant;
+ @property() public value?: string[];
+ @property() public domainFilter?: string;
+ @property() public pickedEntityLabel?: string;
+ @property() public pickEntityLabel?: string;
+
+ protected render(): TemplateResult | void {
+ if (!this.hass) {
+ return;
+ }
+ const currentEntities = this._currentEntities;
+ return html`
+ ${currentEntities.map(
+ (entityId) => html`
+
+
+
+ `
+ )}
+
+
+
+ `;
+ }
+
+ private _entityFilter: HaEntityPickerEntityFilterFunc = (
+ stateObj: HassEntity
+ ) => !this.value || !this.value.includes(stateObj.entity_id);
+
+ private get _currentEntities() {
+ return this.value || [];
+ }
+
+ private async _updateEntities(entities) {
+ fireEvent(this, "value-changed", {
+ value: entities,
+ });
+
+ this.value = entities;
+ }
+
+ private _entityChanged(event: PolymerChangedEvent) {
+ event.stopPropagation();
+ const curValue = (event.currentTarget as any).curValue;
+ const newValue = event.detail.value;
+ if (
+ newValue === curValue ||
+ (newValue !== "" && !isValidEntityId(newValue))
+ ) {
+ return;
+ }
+ if (newValue === "") {
+ this._updateEntities(
+ this._currentEntities.filter((ent) => ent !== curValue)
+ );
+ } else {
+ this._updateEntities(
+ this._currentEntities.map((ent) => (ent === curValue ? newValue : ent))
+ );
+ }
+ }
+
+ private async _addEntity(event: PolymerChangedEvent) {
+ event.stopPropagation();
+ const toAdd = event.detail.value;
+ (event.currentTarget as any).value = "";
+ if (!toAdd) {
+ return;
+ }
+ const currentEntities = this._currentEntities;
+ if (currentEntities.includes(toAdd)) {
+ return;
+ }
+
+ this._updateEntities([...currentEntities, toAdd]);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-entities-picker": HaEntitiesPickerLight;
+ }
+}
diff --git a/src/components/entity/ha-entity-picker.js b/src/components/entity/ha-entity-picker.js
deleted file mode 100644
index bc4765d8d7..0000000000
--- a/src/components/entity/ha-entity-picker.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import "@polymer/paper-icon-button/paper-icon-button";
-import "@polymer/paper-input/paper-input";
-import "@polymer/paper-item/paper-icon-item";
-import "@polymer/paper-item/paper-item-body";
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
-
-import "./state-badge";
-
-import computeStateName from "../../common/entity/compute_state_name";
-import LocalizeMixin from "../../mixins/localize-mixin";
-import EventsMixin from "../../mixins/events-mixin";
-
-/*
- * @appliesMixin LocalizeMixin
- */
-class HaEntityPicker extends EventsMixin(LocalizeMixin(PolymerElement)) {
- static get template() {
- return html`
-
-
-
- Clear
- Toggle
-
-
-
-
-
-
- [[_computeStateName(item)]]
- [[item.entity_id]]
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- allowCustomEntity: {
- type: Boolean,
- value: false,
- },
- hass: {
- type: Object,
- observer: "_hassChanged",
- },
- _hass: Object,
- _states: {
- type: Array,
- computed: "_computeStates(_hass, domainFilter, entityFilter)",
- },
- autofocus: Boolean,
- label: {
- type: String,
- },
- value: {
- type: String,
- notify: true,
- },
- opened: {
- type: Boolean,
- value: false,
- observer: "_openedChanged",
- },
- domainFilter: {
- type: String,
- value: null,
- },
- entityFilter: {
- type: Function,
- value: null,
- },
- disabled: Boolean,
- };
- }
-
- _computeLabel(label, localize) {
- return label === undefined
- ? localize("ui.components.entity.entity-picker.entity")
- : label;
- }
-
- _computeStates(hass, domainFilter, entityFilter) {
- if (!hass) return [];
-
- let entityIds = Object.keys(hass.states);
-
- if (domainFilter) {
- entityIds = entityIds.filter(
- (eid) => eid.substr(0, eid.indexOf(".")) === domainFilter
- );
- }
-
- let entities = entityIds.sort().map((key) => hass.states[key]);
-
- if (entityFilter) {
- entities = entities.filter(entityFilter);
- }
-
- return entities;
- }
-
- _computeStateName(state) {
- return computeStateName(state);
- }
-
- _openedChanged(newVal) {
- if (!newVal) {
- this._hass = this.hass;
- }
- }
-
- _hassChanged(newVal) {
- if (!this.opened) {
- this._hass = newVal;
- }
- }
-
- _computeToggleIcon(opened) {
- return opened ? "hass:menu-up" : "hass:menu-down";
- }
-
- _fireChanged(ev) {
- ev.stopPropagation();
- this.fire("change");
- }
-}
-
-customElements.define("ha-entity-picker", HaEntityPicker);
diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts
new file mode 100644
index 0000000000..3b5bf7ed88
--- /dev/null
+++ b/src/components/entity/ha-entity-picker.ts
@@ -0,0 +1,200 @@
+import "@polymer/paper-icon-button/paper-icon-button";
+import "@polymer/paper-input/paper-input";
+import "@polymer/paper-item/paper-icon-item";
+import "@polymer/paper-item/paper-item-body";
+import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
+import memoizeOne from "memoize-one";
+
+import "./state-badge";
+
+import computeStateName from "../../common/entity/compute_state_name";
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ css,
+ CSSResult,
+ property,
+ PropertyValues,
+} from "lit-element";
+import { HomeAssistant } from "../../types";
+import { HassEntity } from "home-assistant-js-websocket";
+import { PolymerChangedEvent } from "../../polymer-types";
+import { fireEvent } from "../../common/dom/fire_event";
+
+export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
+
+const rowRenderer = (
+ root: HTMLElement,
+ _owner,
+ model: { item: HassEntity }
+) => {
+ if (!root.firstElementChild) {
+ root.innerHTML = `
+
+
+
+
+ [[_computeStateName(item)]]
+ [[item.entity_id]]
+
+
+ `;
+ }
+
+ root.querySelector("state-badge")!.stateObj = model.item;
+ root.querySelector(".name")!.textContent = computeStateName(model.item);
+ root.querySelector("[secondary]")!.textContent = model.item.entity_id;
+};
+
+class HaEntityPicker extends LitElement {
+ @property({ type: Boolean }) public autofocus?: boolean;
+ @property({ type: Boolean }) public disabled?: boolean;
+ @property({ type: Boolean }) public allowCustomEntity;
+ @property() public hass?: HomeAssistant;
+ @property() public label?: string;
+ @property() public value?: string;
+ @property() public domainFilter?: string;
+ @property() public entityFilter?: HaEntityPickerEntityFilterFunc;
+ @property({ type: Boolean }) private _opened?: boolean;
+ @property() private _hass?: HomeAssistant;
+
+ private _getStates = memoizeOne(
+ (
+ hass: this["hass"],
+ domainFilter: this["domainFilter"],
+ entityFilter: this["entityFilter"]
+ ) => {
+ let states: HassEntity[] = [];
+
+ if (!hass) {
+ return [];
+ }
+ let entityIds = Object.keys(hass.states);
+
+ if (domainFilter) {
+ entityIds = entityIds.filter(
+ (eid) => eid.substr(0, eid.indexOf(".")) === domainFilter
+ );
+ }
+
+ states = entityIds.sort().map((key) => hass!.states[key]);
+
+ if (entityFilter) {
+ states = states.filter(
+ (stateObj) =>
+ // We always want to include the entity of the current value
+ stateObj.entity_id === this.value || entityFilter!(stateObj)
+ );
+ }
+ return states;
+ }
+ );
+
+ protected updated(changedProps: PropertyValues) {
+ super.updated(changedProps);
+
+ if (changedProps.has("hass") && !this._opened) {
+ this._hass = this.hass;
+ }
+ }
+
+ protected render(): TemplateResult | void {
+ const states = this._getStates(
+ this._hass,
+ this.domainFilter,
+ this.entityFilter
+ );
+
+ return html`
+
+
+ ${this.value
+ ? html`
+
+ Clear
+
+ `
+ : ""}
+ ${states.length > 0
+ ? html`
+
+ Toggle
+
+ `
+ : ""}
+
+
+ `;
+ }
+
+ private get _value() {
+ return this.value || "";
+ }
+
+ private _openedChanged(ev: PolymerChangedEvent) {
+ this._opened = ev.detail.value;
+ }
+
+ private _valueChanged(ev: PolymerChangedEvent) {
+ const newValue = ev.detail.value;
+ if (newValue !== this._value) {
+ this.value = ev.detail.value;
+ setTimeout(() => {
+ fireEvent(this, "value-changed", { value: this.value });
+ fireEvent(this, "change");
+ }, 0);
+ }
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ paper-input > paper-icon-button {
+ width: 24px;
+ height: 24px;
+ padding: 2px;
+ color: var(--secondary-text-color);
+ }
+ [hidden] {
+ display: none;
+ }
+ `;
+ }
+}
+
+customElements.define("ha-entity-picker", HaEntityPicker);
diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts
index 32d7140ac5..42a622a9e2 100644
--- a/src/components/entity/ha-entity-toggle.ts
+++ b/src/components/entity/ha-entity-toggle.ts
@@ -9,7 +9,7 @@ import {
html,
CSSResult,
css,
- PropertyDeclarations,
+ property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { HassEntity } from "home-assistant-js-websocket";
@@ -17,7 +17,7 @@ import { HassEntity } from "home-assistant-js-websocket";
class HaEntityToggle extends LitElement {
// hass is not a property so that we only re-render on stateObj changes
public hass?: HomeAssistant;
- public stateObj?: HassEntity;
+ @property() public stateObj?: HassEntity;
protected render(): TemplateResult | void {
if (!this.stateObj) {
@@ -51,12 +51,6 @@ class HaEntityToggle extends LitElement {
`;
}
- static get properties(): PropertyDeclarations {
- return {
- stateObj: {},
- };
- }
-
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("click", (ev) => ev.stopPropagation());
diff --git a/src/components/entity/state-badge.js b/src/components/entity/state-badge.js
deleted file mode 100644
index ab3e0906ab..0000000000
--- a/src/components/entity/state-badge.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-
-import "../ha-icon";
-import computeStateDomain from "../../common/entity/compute_state_domain";
-import stateIcon from "../../common/entity/state_icon";
-
-class StateBadge extends PolymerElement {
- static get template() {
- return html`
-
-
-
- `;
- }
-
- static get properties() {
- return {
- stateObj: {
- type: Object,
- observer: "_updateIconAppearance",
- },
- overrideIcon: String,
- };
- }
-
- _computeDomain(stateObj) {
- return computeStateDomain(stateObj);
- }
-
- _computeIcon(stateObj, overrideIcon) {
- return overrideIcon || stateIcon(stateObj);
- }
-
- _updateIconAppearance(newVal) {
- var errorMessage = null;
- const iconStyle = {
- color: "",
- filter: "",
- };
- const hostStyle = {
- backgroundImage: "",
- };
- // hide icon if we have entity picture
- if (newVal.attributes.entity_picture) {
- hostStyle.backgroundImage =
- "url(" + newVal.attributes.entity_picture + ")";
- iconStyle.display = "none";
- } else {
- if (newVal.attributes.hs_color) {
- const hue = newVal.attributes.hs_color[0];
- const sat = newVal.attributes.hs_color[1];
- if (sat > 10) iconStyle.color = `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
- }
- if (newVal.attributes.brightness) {
- const brightness = newVal.attributes.brightness;
- if (typeof brightness !== "number") {
- errorMessage = `Type error: state-badge expected number, but type of ${
- newVal.entity_id
- }.attributes.brightness is ${typeof brightness} (${brightness})`;
- // eslint-disable-next-line
- console.warn(errorMessage);
- }
- // lowest brighntess will be around 50% (that's pretty dark)
- iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
- }
- }
- Object.assign(this.$.icon.style, iconStyle);
- Object.assign(this.style, hostStyle);
- if (errorMessage) {
- throw new Error(`Frontend error: ${errorMessage}`);
- }
- }
-}
-customElements.define("state-badge", StateBadge);
diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts
new file mode 100644
index 0000000000..4554c382fb
--- /dev/null
+++ b/src/components/entity/state-badge.ts
@@ -0,0 +1,127 @@
+import {
+ LitElement,
+ TemplateResult,
+ css,
+ CSSResult,
+ html,
+ property,
+ PropertyValues,
+ query,
+} from "lit-element";
+import "../ha-icon";
+import computeStateDomain from "../../common/entity/compute_state_domain";
+import stateIcon from "../../common/entity/state_icon";
+import { HassEntity } from "home-assistant-js-websocket";
+// Not duplicate, this is for typing.
+// tslint:disable-next-line
+import { HaIcon } from "../ha-icon";
+
+class StateBadge extends LitElement {
+ @property() public stateObj?: HassEntity;
+ @property() public overrideIcon?: string;
+ @query("ha-icon") private _icon!: HaIcon;
+
+ protected render(): TemplateResult | void {
+ const stateObj = this.stateObj;
+
+ if (!stateObj) {
+ return html``;
+ }
+
+ return html`
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues) {
+ if (!changedProps.has("stateObj")) {
+ return;
+ }
+ const stateObj = this.stateObj;
+
+ const iconStyle: Partial = {
+ color: "",
+ filter: "",
+ };
+ const hostStyle: Partial = {
+ backgroundImage: "",
+ };
+ if (stateObj) {
+ // hide icon if we have entity picture
+ if (stateObj.attributes.entity_picture) {
+ hostStyle.backgroundImage =
+ "url(" + stateObj.attributes.entity_picture + ")";
+ iconStyle.display = "none";
+ } else {
+ if (stateObj.attributes.hs_color) {
+ const hue = stateObj.attributes.hs_color[0];
+ const sat = stateObj.attributes.hs_color[1];
+ if (sat > 10) {
+ iconStyle.color = `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
+ }
+ }
+ if (stateObj.attributes.brightness) {
+ const brightness = stateObj.attributes.brightness;
+ if (typeof brightness !== "number") {
+ const errorMessage = `Type error: state-badge expected number, but type of ${
+ stateObj.entity_id
+ }.attributes.brightness is ${typeof brightness} (${brightness})`;
+ // tslint:disable-next-line
+ console.warn(errorMessage);
+ }
+ // lowest brighntess will be around 50% (that's pretty dark)
+ iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
+ }
+ }
+ }
+ Object.assign(this._icon.style, iconStyle);
+ Object.assign(this.style, hostStyle);
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ :host {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ color: var(--paper-item-icon-color, #44739e);
+ border-radius: 50%;
+ height: 40px;
+ text-align: center;
+ background-size: cover;
+ line-height: 40px;
+ }
+
+ ha-icon {
+ transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
+ }
+
+ /* Color the icon if light or sun is on */
+ ha-icon[data-domain="light"][data-state="on"],
+ ha-icon[data-domain="switch"][data-state="on"],
+ ha-icon[data-domain="binary_sensor"][data-state="on"],
+ ha-icon[data-domain="fan"][data-state="on"],
+ ha-icon[data-domain="sun"][data-state="above_horizon"] {
+ color: var(--paper-item-icon-active-color, #fdd835);
+ }
+
+ /* Color the icon if unavailable */
+ ha-icon[data-state="unavailable"] {
+ color: var(--state-icon-unavailable-color);
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "state-badge": StateBadge;
+ }
+}
+
+customElements.define("state-badge", StateBadge);
diff --git a/src/components/ha-card.js b/src/components/ha-card.js
index f497d69d02..44e58f43e9 100644
--- a/src/components/ha-card.js
+++ b/src/components/ha-card.js
@@ -9,9 +9,12 @@ class HaCard extends PolymerElement {
:host {
@apply --paper-material-elevation-1;
display: block;
- border-radius: 2px;
+ border-radius: var(--ha-card-border-radius, 2px);
transition: all 0.3s ease-out;
- background-color: var(--paper-card-background-color, white);
+ background: var(
+ --ha-card-background,
+ var(--paper-card-background-color, white)
+ );
color: var(--primary-text-color);
}
.header {
diff --git a/src/components/ha-icon.js b/src/components/ha-icon.js
deleted file mode 100644
index 29cb8b88b1..0000000000
--- a/src/components/ha-icon.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import "@polymer/iron-icon/iron-icon";
-
-const IronIconClass = customElements.get("iron-icon");
-
-let loaded = false;
-
-class HaIcon extends IronIconClass {
- listen(...args) {
- super.listen(...args);
-
- if (!loaded && this._iconsetName === "mdi") {
- loaded = true;
- import(/* webpackChunkName: "mdi-icons" */ "../resources/mdi-icons");
- }
- }
-}
-
-customElements.define("ha-icon", HaIcon);
diff --git a/src/components/ha-icon.ts b/src/components/ha-icon.ts
new file mode 100644
index 0000000000..2e2a6ba848
--- /dev/null
+++ b/src/components/ha-icon.ts
@@ -0,0 +1,36 @@
+import { Constructor } from "lit-element";
+import "@polymer/iron-icon/iron-icon";
+// Not duplicate, this is for typing.
+// tslint:disable-next-line
+import { IronIconElement } from "@polymer/iron-icon/iron-icon";
+
+const ironIconClass = customElements.get("iron-icon") as Constructor<
+ IronIconElement
+>;
+
+let loaded = false;
+
+export class HaIcon extends ironIconClass {
+ private _iconsetName?: string;
+
+ public listen(
+ node: EventTarget | null,
+ eventName: string,
+ methodName: string
+ ): void {
+ super.listen(node, eventName, methodName);
+
+ if (!loaded && this._iconsetName === "mdi") {
+ loaded = true;
+ import(/* webpackChunkName: "mdi-icons" */ "../resources/mdi-icons");
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-icon": HaIcon;
+ }
+}
+
+customElements.define("ha-icon", HaIcon);
diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts
index 035d1c1802..4cc5aaddf9 100644
--- a/src/components/ha-sidebar.ts
+++ b/src/components/ha-sidebar.ts
@@ -3,8 +3,8 @@ import {
html,
CSSResult,
css,
- PropertyDeclarations,
PropertyValues,
+ property,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "@polymer/app-layout/app-toolbar/app-toolbar";
@@ -82,13 +82,9 @@ const computePanels = (hass: HomeAssistant) => {
* @appliesMixin LocalizeMixin
*/
class HaSidebar extends LitElement {
- public hass?: HomeAssistant;
- public _defaultPage?: string;
-
- constructor() {
- super();
- this._defaultPage = localStorage.defaultPage || DEFAULT_PANEL;
- }
+ @property() public hass?: HomeAssistant;
+ @property() public _defaultPage?: string =
+ localStorage.defaultPage || DEFAULT_PANEL;
protected render() {
const hass = this.hass;
@@ -217,13 +213,6 @@ class HaSidebar extends LitElement {
`;
}
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- _defaultPage: {},
- };
- }
-
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.hass || !changedProps.has("hass")) {
return false;
diff --git a/src/data/automation.ts b/src/data/automation.ts
new file mode 100644
index 0000000000..02032b06c4
--- /dev/null
+++ b/src/data/automation.ts
@@ -0,0 +1,17 @@
+import {
+ HassEntityBase,
+ HassEntityAttributeBase,
+} from "home-assistant-js-websocket";
+
+export interface AutomationEntity extends HassEntityBase {
+ attributes: HassEntityAttributeBase & {
+ id?: string;
+ };
+}
+
+export interface AutomationConfig {
+ alias: string;
+ trigger: any[];
+ condition?: any[];
+ action: any[];
+}
diff --git a/src/data/camera.ts b/src/data/camera.ts
new file mode 100644
index 0000000000..cde6abb596
--- /dev/null
+++ b/src/data/camera.ts
@@ -0,0 +1,12 @@
+import { HomeAssistant } from "../types";
+
+export interface CameraThumbnail {
+ content_type: string;
+ content: string;
+}
+
+export const fetchThumbnail = (hass: HomeAssistant, entityId: string) =>
+ hass.callWS({
+ type: "camera_thumbnail",
+ entity_id: entityId,
+ });
diff --git a/src/data/person.ts b/src/data/person.ts
new file mode 100644
index 0000000000..00e77a838f
--- /dev/null
+++ b/src/data/person.ts
@@ -0,0 +1,46 @@
+import { HomeAssistant } from "../types";
+
+export interface Person {
+ id: string;
+ name: string;
+ user_id?: string;
+ device_trackers?: string[];
+}
+
+export interface PersonMutableParams {
+ name: string;
+ user_id: string | null;
+ device_trackers: string[];
+}
+
+export const fetchPersons = (hass: HomeAssistant) =>
+ hass.callWS<{
+ storage: Person[];
+ config: Person[];
+ }>({ type: "person/list" });
+
+export const createPerson = (
+ hass: HomeAssistant,
+ values: PersonMutableParams
+) =>
+ hass.callWS({
+ type: "person/create",
+ ...values,
+ });
+
+export const updatePerson = (
+ hass: HomeAssistant,
+ personId: string,
+ updates: Partial
+) =>
+ hass.callWS({
+ type: "person/update",
+ person_id: personId,
+ ...updates,
+ });
+
+export const deletePerson = (hass: HomeAssistant, personId: string) =>
+ hass.callWS({
+ type: "person/delete",
+ person_id: personId,
+ });
diff --git a/src/data/script.ts b/src/data/script.ts
new file mode 100644
index 0000000000..0c50a3f6e6
--- /dev/null
+++ b/src/data/script.ts
@@ -0,0 +1,5 @@
+export interface EventAction {
+ event: string;
+ event_data?: { [key: string]: any };
+ event_data_template?: { [key: string]: any };
+}
diff --git a/src/data/zha.ts b/src/data/zha.ts
index f5a67dbb12..8c50b82f3c 100644
--- a/src/data/zha.ts
+++ b/src/data/zha.ts
@@ -7,8 +7,19 @@ export interface ZHADeviceEntity extends HassEntity {
};
}
-export interface ZHAEntities {
- [key: string]: HassEntity[];
+export interface ZHAEntityReference extends HassEntity {
+ name: string;
+}
+
+export interface ZHADevice {
+ name: string;
+ ieee: string;
+ manufacturer: string;
+ model: string;
+ quirk_applied: boolean;
+ quirk_class: string;
+ entities: ZHAEntityReference[];
+ manufacturer_code: number;
}
export interface Attribute {
@@ -19,6 +30,7 @@ export interface Attribute {
export interface Cluster {
name: string;
id: number;
+ endpoint_id: number;
type: string;
}
@@ -29,7 +41,8 @@ export interface Command {
}
export interface ReadAttributeServiceData {
- entity_id: string;
+ ieee: string;
+ endpoint_id: number;
cluster_id: number;
cluster_type: string;
attribute: number;
@@ -41,64 +54,60 @@ export const reconfigureNode = (
ieeeAddress: string
): Promise =>
hass.callWS({
- type: "zha/nodes/reconfigure",
+ type: "zha/devices/reconfigure",
ieee: ieeeAddress,
});
export const fetchAttributesForCluster = (
hass: HomeAssistant,
- entityId: string,
ieeeAddress: string,
+ endpointId: number,
clusterId: number,
clusterType: string
): Promise =>
hass.callWS({
- type: "zha/entities/clusters/attributes",
- entity_id: entityId,
+ type: "zha/devices/clusters/attributes",
ieee: ieeeAddress,
+ endpoint_id: endpointId,
cluster_id: clusterId,
cluster_type: clusterType,
});
+export const fetchDevices = (hass: HomeAssistant): Promise =>
+ hass.callWS({
+ type: "zha/devices",
+ });
+
export const readAttributeValue = (
hass: HomeAssistant,
data: ReadAttributeServiceData
): Promise => {
return hass.callWS({
...data,
- type: "zha/entities/clusters/attributes/value",
+ type: "zha/devices/clusters/attributes/value",
});
};
export const fetchCommandsForCluster = (
hass: HomeAssistant,
- entityId: string,
ieeeAddress: string,
+ endpointId: number,
clusterId: number,
clusterType: string
): Promise =>
hass.callWS({
- type: "zha/entities/clusters/commands",
- entity_id: entityId,
+ type: "zha/devices/clusters/commands",
ieee: ieeeAddress,
+ endpoint_id: endpointId,
cluster_id: clusterId,
cluster_type: clusterType,
});
export const fetchClustersForZhaNode = (
hass: HomeAssistant,
- entityId: string,
ieeeAddress: string
): Promise =>
hass.callWS({
- type: "zha/entities/clusters",
- entity_id: entityId,
+ type: "zha/devices/clusters",
ieee: ieeeAddress,
});
-
-export const fetchEntitiesForZhaNode = (
- hass: HomeAssistant
-): Promise =>
- hass.callWS({
- type: "zha/entities",
- });
diff --git a/src/dialogs/ha-store-auth-card.js b/src/dialogs/ha-store-auth-card.js
index 9284e175f0..2fb802650a 100644
--- a/src/dialogs/ha-store-auth-card.js
+++ b/src/dialogs/ha-store-auth-card.js
@@ -18,6 +18,10 @@ class HaStoreAuth extends LocalizeMixin(PolymerElement) {
right: 16px;
}
+ .card-content {
+ color: var(--primary-text-color);
+ }
+
.card-actions {
text-align: right;
border-top: 0;
diff --git a/src/dialogs/more-info/controls/more-info-content.js b/src/dialogs/more-info/controls/more-info-content.ts
similarity index 57%
rename from src/dialogs/more-info/controls/more-info-content.js
rename to src/dialogs/more-info/controls/more-info-content.ts
index 2d4f20a7c1..e60c03b6e0 100644
--- a/src/dialogs/more-info/controls/more-info-content.js
+++ b/src/dialogs/more-info/controls/more-info-content.ts
@@ -1,4 +1,9 @@
-import { PolymerElement } from "@polymer/polymer/polymer-element";
+import {
+ PropertyDeclarations,
+ PropertyValues,
+ UpdatingElement,
+} from "lit-element";
+import { HassEntity } from "home-assistant-js-websocket";
import "./more-info-alarm_control_panel";
import "./more-info-automation";
@@ -23,26 +28,30 @@ import "./more-info-weather";
import stateMoreInfoType from "../../../common/entity/state_more_info_type";
import dynamicContentUpdater from "../../../common/dom/dynamic_content_updater";
+import { HomeAssistant } from "../../../types";
-class MoreInfoContent extends PolymerElement {
- static get properties() {
+class MoreInfoContent extends UpdatingElement {
+ public hass?: HomeAssistant;
+ public stateObj?: HassEntity;
+ private _detachedChild?: ChildNode;
+
+ static get properties(): PropertyDeclarations {
return {
- hass: Object,
- stateObj: Object,
+ hass: {},
+ stateObj: {},
};
}
- static get observers() {
- return ["stateObjChanged(stateObj, hass)"];
- }
-
- constructor() {
- super();
+ protected firstUpdated(): void {
this.style.display = "block";
}
- stateObjChanged(stateObj, hass) {
- let moreInfoType;
+ // This is not a lit element, but an updating element, so we implement update
+ protected update(changedProps: PropertyValues): void {
+ super.update(changedProps);
+ const stateObj = this.stateObj;
+ const hass = this.hass;
+
if (!stateObj || !hass) {
if (this.lastChild) {
this._detachedChild = this.lastChild;
@@ -51,18 +60,20 @@ class MoreInfoContent extends PolymerElement {
}
return;
}
+
if (this._detachedChild) {
this.appendChild(this._detachedChild);
- this._detachedChild = null;
- }
- if (stateObj.attributes && "custom_ui_more_info" in stateObj.attributes) {
- moreInfoType = stateObj.attributes.custom_ui_more_info;
- } else {
- moreInfoType = "more-info-" + stateMoreInfoType(stateObj);
+ this._detachedChild = undefined;
}
+
+ const moreInfoType =
+ stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
+ ? stateObj.attributes.custom_ui_more_info
+ : "more-info-" + stateMoreInfoType(stateObj);
+
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
- hass: hass,
- stateObj: stateObj,
+ hass,
+ stateObj,
});
}
}
diff --git a/src/layouts/app/disconnect-toast-mixin.ts b/src/layouts/app/disconnect-toast-mixin.ts
index 70411c3a0b..2905ed40f6 100644
--- a/src/layouts/app/disconnect-toast-mixin.ts
+++ b/src/layouts/app/disconnect-toast-mixin.ts
@@ -1,13 +1,14 @@
import { Constructor, LitElement } from "lit-element";
import { HassBaseEl } from "./hass-base-mixin";
import { HaToast } from "../../components/ha-toast";
+import { computeRTL } from "../../common/util/compute_rtl";
export default (superClass: Constructor) =>
class extends superClass {
private _discToast?: HaToast;
- protected hassConnected() {
- super.hassConnected();
+ protected firstUpdated(changedProps) {
+ super.firstUpdated(changedProps);
// Need to load in advance because when disconnected, can't dynamically load code.
import(/* webpackChunkName: "ha-toast" */ "../../components/ha-toast");
}
@@ -24,10 +25,13 @@ export default (superClass: Constructor) =>
if (!this._discToast) {
const el = document.createElement("ha-toast");
el.duration = 0;
- el.text = this.hass!.localize("ui.notification_toast.connection_lost");
this._discToast = el;
this.shadowRoot!.appendChild(el as any);
}
+ this._discToast.dir = computeRTL(this.hass!);
+ this._discToast.text = this.hass!.localize(
+ "ui.notification_toast.connection_lost"
+ );
this._discToast.opened = true;
}
};
diff --git a/src/layouts/app/hass-base-mixin.ts b/src/layouts/app/hass-base-mixin.ts
index f748aca450..33b35200f3 100644
--- a/src/layouts/app/hass-base-mixin.ts
+++ b/src/layouts/app/hass-base-mixin.ts
@@ -1,4 +1,8 @@
-import { Constructor } from "lit-element";
+import {
+ Constructor,
+ // @ts-ignore
+ property,
+} from "lit-element";
import { HomeAssistant } from "../../types";
/* tslint:disable */
@@ -17,14 +21,9 @@ export class HassBaseEl {
export default (superClass: Constructor): Constructor =>
// @ts-ignore
class extends superClass {
- private __provideHass: HTMLElement[];
+ private __provideHass: HTMLElement[] = [];
// @ts-ignore
- protected hass: HomeAssistant;
-
- constructor() {
- super();
- this.__provideHass = [];
- }
+ @property() protected hass: HomeAssistant;
// Exists so all methods can safely call super method
protected hassConnected() {
diff --git a/src/layouts/app/home-assistant.ts b/src/layouts/app/home-assistant.ts
index 6f5fc8a8b0..e7141e3b46 100644
--- a/src/layouts/app/home-assistant.ts
+++ b/src/layouts/app/home-assistant.ts
@@ -1,11 +1,5 @@
import "@polymer/app-route/app-location";
-import {
- html,
- LitElement,
- PropertyDeclarations,
- PropertyValues,
- css,
-} from "lit-element";
+import { html, LitElement, PropertyValues, css, property } from "lit-element";
import "../home-assistant-main";
import "../ha-init-page";
@@ -43,19 +37,9 @@ export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
NotificationMixin,
dialogManagerMixin,
]) {
- private _route?: Route;
- private _error?: boolean;
- private _panelUrl?: string;
-
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- _route: {},
- _routeData: {},
- _panelUrl: {},
- _error: {},
- };
- }
+ @property() private _route?: Route;
+ @property() private _error?: boolean;
+ @property() private _panelUrl?: string;
protected render() {
const hass = this.hass;
diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts
index 8f94618e62..58679ff86a 100644
--- a/src/layouts/home-assistant-main.ts
+++ b/src/layouts/home-assistant-main.ts
@@ -2,10 +2,10 @@ import {
LitElement,
html,
TemplateResult,
- PropertyDeclarations,
CSSResult,
css,
PropertyValues,
+ property,
} from "lit-element";
import "@polymer/app-layout/app-drawer-layout/app-drawer-layout";
import "@polymer/app-layout/app-drawer/app-drawer";
@@ -33,17 +33,9 @@ declare global {
}
class HomeAssistantMain extends LitElement {
- public hass?: HomeAssistant;
- public route?: Route;
- private _narrow?: boolean;
-
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- _narrow: {},
- route: {},
- };
- }
+ @property() public hass?: HomeAssistant;
+ @property() public route?: Route;
+ @property() private _narrow?: boolean;
protected render(): TemplateResult | void {
const hass = this.hass;
diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts
index 1b58560bb2..db43495ebb 100644
--- a/src/layouts/partial-panel-resolver.ts
+++ b/src/layouts/partial-panel-resolver.ts
@@ -1,9 +1,4 @@
-import {
- LitElement,
- html,
- PropertyDeclarations,
- PropertyValues,
-} from "lit-element";
+import { LitElement, html, PropertyValues, property } from "lit-element";
import "./hass-loading-screen";
import { HomeAssistant, Panel, PanelElement, Route } from "../types";
@@ -112,34 +107,16 @@ function ensureLoaded(panel): Promise | null {
}
class PartialPanelResolver extends LitElement {
- public hass?: HomeAssistant;
- public narrow?: boolean;
- public showMenu?: boolean;
- public route?: Route | null;
+ @property() public hass?: HomeAssistant;
+ @property() public narrow?: boolean;
+ @property() public showMenu?: boolean;
+ @property() public route?: Route | null;
- private _routeTail?: Route | null;
+ @property() private _routeTail?: Route | null;
+ @property() private _panelEl?: PanelElement;
+ @property() private _error?: boolean;
private _panel?: Panel;
- private _panelEl?: PanelElement;
- private _error?: boolean;
- private _cache: { [name: string]: PanelElement };
-
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- narrow: {},
- showMenu: {},
- route: {},
-
- _routeTail: {},
- _error: {},
- _panelEl: {},
- };
- }
-
- constructor() {
- super();
- this._cache = {};
- }
+ private _cache: { [name: string]: PanelElement } = {};
protected render() {
if (this._error) {
diff --git a/src/panels/config/area_registry/dialog-area-registry-detail.ts b/src/panels/config/area_registry/dialog-area-registry-detail.ts
index bf8e0d8425..6a777ba1f3 100644
--- a/src/panels/config/area_registry/dialog-area-registry-detail.ts
+++ b/src/panels/config/area_registry/dialog-area-registry-detail.ts
@@ -51,7 +51,13 @@ class DialogAreaDetail extends LitElement {
opened
@opened-changed="${this._openedChanged}"
>
- ${this._params.entry ? this._params.entry.name : "New Area"}
+
+ ${this._params.entry
+ ? this._params.entry.name
+ : this.hass.localize(
+ "ui.panel.config.area_registry.editor.default_name"
+ )}
+
${this._error
? html`
@@ -62,7 +68,7 @@ class DialogAreaDetail extends LitElement {
@@ -76,7 +82,9 @@ class DialogAreaDetail extends LitElement {
@click="${this._deleteEntry}"
.disabled=${this._submitting}
>
- DELETE
+ ${this.hass.localize(
+ "ui.panel.config.area_registry.editor.delete"
+ )}
`
: html``}
@@ -84,7 +92,13 @@ class DialogAreaDetail extends LitElement {
@click="${this._updateEntry}"
.disabled=${nameInvalid || this._submitting}
>
- ${this._params.entry ? "UPDATE" : "CREATE"}
+ ${this._params.entry
+ ? this.hass.localize(
+ "ui.panel.config.area_registry.editor.update"
+ )
+ : this.hass.localize(
+ "ui.panel.config.area_registry.editor.create"
+ )}
diff --git a/src/panels/config/area_registry/ha-config-area-registry.ts b/src/panels/config/area_registry/ha-config-area-registry.ts
index 8fe412539e..43fb8bdc6f 100644
--- a/src/panels/config/area_registry/ha-config-area-registry.ts
+++ b/src/panels/config/area_registry/ha-config-area-registry.ts
@@ -50,7 +50,9 @@ class HaConfigAreaRegistry extends LitElement {
return html`
- Area Registry
+
+ ${this.hass.localize("ui.panel.config.area_registry.picker.header")}
+
Areas are used to organize where devices are. This information will
be used throughout Home Assistant to help you in organizing your
@@ -74,10 +76,14 @@ class HaConfigAreaRegistry extends LitElement {
${this._items.length === 0
? html`
- Looks like you have no areas yet!
+ ${this.hass.localize(
+ "ui.panel.config.area_registry.picker.no_areas"
+ )}
- CREATE AREA
+ ${this.hass.localize(
+ "ui.panel.config.area_registry.picker.create_area"
+ )}
+
`
: html``}
diff --git a/src/panels/config/area_registry/show-dialog-area-registry-detail.ts b/src/panels/config/area_registry/show-dialog-area-registry-detail.ts
index bbcc848b1c..c12b58b042 100644
--- a/src/panels/config/area_registry/show-dialog-area-registry-detail.ts
+++ b/src/panels/config/area_registry/show-dialog-area-registry-detail.ts
@@ -14,7 +14,7 @@ export interface AreaRegistryDetailDialogParams {
}
export const loadAreaRegistryDetailDialog = () =>
- import(/* webpackChunkName: "entity-registry-detail-dialog" */ "./dialog-area-registry-detail");
+ import(/* webpackChunkName: "area-registry-detail-dialog" */ "./dialog-area-registry-detail");
export const showAreaRegistryDetailDialog = (
element: HTMLElement,
diff --git a/src/panels/config/automation/ha-automation-editor.js b/src/panels/config/automation/ha-automation-editor.js
deleted file mode 100644
index 45e010facb..0000000000
--- a/src/panels/config/automation/ha-automation-editor.js
+++ /dev/null
@@ -1,298 +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 "@polymer/paper-fab/paper-fab";
-
-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 Automation from "../js/automation";
-import unmountPreact from "../../../common/preact/unmount";
-import computeStateName from "../../../common/entity/compute_state_name";
-import NavigateMixin from "../../../mixins/navigate-mixin";
-import LocalizeMixin from "../../../mixins/localize-mixin";
-
-function AutomationEditor(mountEl, props, mergeEl) {
- return render(h(Automation, props), mountEl, mergeEl);
-}
-
-/*
- * @appliesMixin LocalizeMixin
- * @appliesMixin NavigateMixin
- */
-class HaAutomationEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
- static get template() {
- return html`
-
-
-
-
-
-
- [[computeName(automation, localize)]]
-
-
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: {
- type: Object,
- observer: "_updateComponent",
- },
-
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
- errors: {
- type: Object,
- value: null,
- },
-
- dirty: {
- type: Boolean,
- value: false,
- },
-
- config: {
- type: Object,
- value: null,
- },
-
- automation: {
- type: Object,
- observer: "automationChanged",
- },
-
- creatingNew: {
- type: Boolean,
- observer: "creatingNewChanged",
- },
-
- isWide: {
- type: Boolean,
- observer: "_updateComponent",
- },
-
- _rendered: {
- type: Object,
- value: null,
- },
-
- _renderScheduled: {
- type: Boolean,
- value: false,
- },
- };
- }
-
- 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();
- }
-
- automationChanged(newVal, oldVal) {
- if (!newVal) return;
- if (!this.hass) {
- setTimeout(() => this.automationChanged(newVal, oldVal), 0);
- return;
- }
- if (oldVal && oldVal.attributes.id === newVal.attributes.id) {
- return;
- }
- this.hass
- .callApi("get", "config/automation/config/" + newVal.attributes.id)
- .then(
- function(config) {
- // Normalize data: ensure trigger, action and condition are lists
- // Happens when people copy paste their automations into the config
- ["trigger", "condition", "action"].forEach(function(key) {
- var value = config[key];
- if (value && !Array.isArray(value)) {
- config[key] = [value];
- }
- });
- this.dirty = false;
- this.config = config;
- this._updateComponent();
- }.bind(this)
- );
- }
-
- creatingNewChanged(newVal) {
- if (!newVal) {
- return;
- }
- this.dirty = false;
- this.config = {
- alias: this.localize("ui.panel.config.automation.editor.default_name"),
- trigger: [{ platform: "state" }],
- condition: [],
- action: [{ service: "" }],
- };
- this._updateComponent();
- }
-
- backTapped() {
- if (
- this.dirty &&
- // eslint-disable-next-line
- !confirm(
- this.localize("ui.panel.config.automation.editor.unsaved_confirm")
- )
- ) {
- return;
- }
- history.back();
- }
-
- async _updateComponent() {
- if (this._renderScheduled || !this.hass || !this.config) return;
- this._renderScheduled = true;
-
- await 0;
-
- if (!this._renderScheduled) return;
-
- this._renderScheduled = false;
-
- this._rendered = AutomationEditor(
- this.$.root,
- {
- automation: this.config,
- onChange: this.configChanged,
- isWide: this.isWide,
- hass: this.hass,
- localize: this.localize,
- },
- this._rendered
- );
- }
-
- saveAutomation() {
- var id = this.creatingNew ? "" + Date.now() : this.automation.attributes.id;
- this.hass
- .callApi("post", "config/automation/config/" + id, this.config)
- .then(
- function() {
- this.dirty = false;
-
- if (this.creatingNew) {
- this.navigate(`/config/automation/edit/${id}`, true);
- }
- }.bind(this),
- function(errors) {
- this.errors = errors.body.message;
- throw errors;
- }.bind(this)
- );
- }
-
- computeName(automation, localize) {
- return automation
- ? computeStateName(automation)
- : localize("ui.panel.config.automation.editor.default_name");
- }
-}
-
-customElements.define("ha-automation-editor", HaAutomationEditor);
diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts
new file mode 100644
index 0000000000..02d50e67f5
--- /dev/null
+++ b/src/panels/config/automation/ha-automation-editor.ts
@@ -0,0 +1,288 @@
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ CSSResult,
+ css,
+ PropertyDeclarations,
+ PropertyValues,
+} from "lit-element";
+import "@polymer/app-layout/app-header/app-header";
+import "@polymer/app-layout/app-toolbar/app-toolbar";
+import "@polymer/paper-icon-button/paper-icon-button";
+import "@polymer/paper-fab/paper-fab";
+import { classMap } from "lit-html/directives/class-map";
+
+import { h, render } from "preact";
+
+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/ha-style";
+import { HomeAssistant } from "../../../types";
+import { AutomationEntity, AutomationConfig } from "../../../data/automation";
+import { navigate } from "../../../common/navigate";
+import { computeRTL } from "../../../common/util/compute_rtl";
+
+function AutomationEditor(mountEl, props, mergeEl) {
+ return render(h(Automation, props), mountEl, mergeEl);
+}
+
+class HaAutomationEditor extends LitElement {
+ public hass?: HomeAssistant;
+ public automation?: AutomationEntity;
+ public isWide?: boolean;
+ public creatingNew?: boolean;
+ private _config?: AutomationConfig;
+ private _dirty?: boolean;
+ private _rendered?: unknown;
+ private _errors?: string;
+
+ static get properties(): PropertyDeclarations {
+ return {
+ hass: {},
+ automation: {},
+ creatingNew: {},
+ isWide: {},
+ _errors: {},
+ _dirty: {},
+ _config: {},
+ };
+ }
+
+ 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 {
+ if (!this.hass) {
+ return;
+ }
+ return html`
+
+
+
+
+
+ ${this.automation
+ ? computeStateName(this.automation)
+ : this.hass.localize(
+ "ui.panel.config.automation.editor.default_name"
+ )}
+
+
+
+
+
+ ${this._errors
+ ? html`
+
${this._errors}
+ `
+ : ""}
+
+
+
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues): void {
+ super.updated(changedProps);
+
+ const oldAutomation = changedProps.get("automation") as AutomationEntity;
+ if (
+ changedProps.has("automation") &&
+ this.automation &&
+ this.hass &&
+ // Only refresh config if we picked a new automation. If same ID, don't fetch it.
+ (!oldAutomation ||
+ oldAutomation.attributes.id !== this.automation.attributes.id)
+ ) {
+ this.hass
+ .callApi(
+ "GET",
+ `config/automation/config/${this.automation.attributes.id}`
+ )
+ .then((config) => {
+ // Normalize data: ensure trigger, action and condition are lists
+ // Happens when people copy paste their automations into the config
+ for (const key of ["trigger", "condition", "action"]) {
+ const value = config[key];
+ if (value && !Array.isArray(value)) {
+ config[key] = [value];
+ }
+ }
+ this._dirty = false;
+ this._config = config;
+ });
+ }
+
+ if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
+ this._dirty = false;
+ this._config = {
+ alias: this.hass.localize(
+ "ui.panel.config.automation.editor.default_name"
+ ),
+ trigger: [{ platform: "state" }],
+ condition: [],
+ action: [{ service: "" }],
+ };
+ }
+
+ 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 {
+ // onChange gets called a lot during initial rendering causing recursing calls.
+ if (!this._rendered) {
+ return;
+ }
+ this._config = config;
+ this._errors = undefined;
+ this._dirty = true;
+ // this._updateComponent();
+ }
+
+ private _backTapped(): void {
+ if (
+ this._dirty &&
+ !confirm(
+ this.hass!.localize("ui.panel.config.automation.editor.unsaved_confirm")
+ )
+ ) {
+ return;
+ }
+ history.back();
+ }
+
+ private _saveAutomation(): void {
+ const id = this.creatingNew
+ ? "" + Date.now()
+ : this.automation!.attributes.id;
+ this.hass!.callApi(
+ "POST",
+ "config/automation/config/" + id,
+ this._config
+ ).then(
+ () => {
+ this._dirty = false;
+
+ if (this.creatingNew) {
+ navigate(this, `/config/automation/edit/${id}`, true);
+ }
+ },
+ (errors) => {
+ this._errors = errors.body.message;
+ throw errors;
+ }
+ );
+ }
+
+ static get styles(): CSSResult[] {
+ return [
+ haStyle,
+ css`
+ .errors {
+ padding: 20px;
+ font-weight: bold;
+ color: var(--google-red-500);
+ }
+ .content {
+ padding-bottom: 20px;
+ }
+ paper-card {
+ display: block;
+ }
+ .triggers,
+ .script {
+ margin-top: -16px;
+ }
+ .triggers paper-card,
+ .script paper-card {
+ margin-top: 16px;
+ }
+ .add-card paper-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 {
+ color: var(--primary-color);
+ }
+ paper-fab {
+ position: fixed;
+ bottom: 16px;
+ right: 16px;
+ z-index: 1;
+ margin-bottom: -80px;
+ transition: margin-bottom 0.3s;
+ }
+
+ paper-fab[is-wide] {
+ bottom: 24px;
+ right: 24px;
+ }
+
+ paper-fab[dirty] {
+ margin-bottom: 0;
+ }
+ `,
+ ];
+ }
+}
+
+customElements.define("ha-automation-editor", HaAutomationEditor);
diff --git a/src/panels/config/cloud/ha-config-cloud-account.js b/src/panels/config/cloud/ha-config-cloud-account.js
index e47e26aa14..ff8998ee41 100644
--- a/src/panels/config/cloud/ha-config-cloud-account.js
+++ b/src/panels/config/cloud/ha-config-cloud-account.js
@@ -39,6 +39,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
}
.content {
padding-bottom: 24px;
+ direction: ltr;
}
paper-card {
display: block;
diff --git a/src/panels/config/cloud/ha-config-cloud-forgot-password.js b/src/panels/config/cloud/ha-config-cloud-forgot-password.js
index 847ebfcb9b..1e8668c756 100644
--- a/src/panels/config/cloud/ha-config-cloud-forgot-password.js
+++ b/src/panels/config/cloud/ha-config-cloud-forgot-password.js
@@ -17,6 +17,7 @@ class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
diff --git a/src/panels/config/zha/ha-config-zha.ts b/src/panels/config/zha/ha-config-zha.ts
index 23ae151636..1ab743c6c6 100755
--- a/src/panels/config/zha/ha-config-zha.ts
+++ b/src/panels/config/zha/ha-config-zha.ts
@@ -14,11 +14,7 @@ import { Cluster } from "../../../data/zha";
import "../../../layouts/ha-app-layout";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
-import {
- ZHAClusterSelectedParams,
- ZHAEntitySelectedParams,
- ZHANodeSelectedParams,
-} from "./types";
+import { ZHAClusterSelectedParams, ZHANodeSelectedParams } from "./types";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-network";
@@ -29,14 +25,12 @@ export class HaConfigZha extends LitElement {
public isWide?: boolean;
private _selectedNode?: HassEntity;
private _selectedCluster?: Cluster;
- private _selectedEntity?: HassEntity;
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
_selectedCluster: {},
- _selectedEntity: {},
_selectedNode: {},
};
}
@@ -64,7 +58,6 @@ export class HaConfigZha extends LitElement {
.hass="${this.hass}"
@zha-cluster-selected="${this._onClusterSelected}"
@zha-node-selected="${this._onNodeSelected}"
- @zha-entity-selected="${this._onEntitySelected}"
>
${this._selectedCluster
? html`
@@ -72,7 +65,6 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
- .selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}"
>
@@ -80,7 +72,6 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
- .selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}"
>
`
@@ -100,13 +91,6 @@ export class HaConfigZha extends LitElement {
): void {
this._selectedNode = selectedNodeEvent.detail.node;
this._selectedCluster = undefined;
- this._selectedEntity = undefined;
- }
-
- private _onEntitySelected(
- selectedEntityEvent: HASSDomEvent
- ): void {
- this._selectedEntity = selectedEntityEvent.detail.entity;
}
static get styles(): CSSResult[] {
diff --git a/src/panels/config/zha/types.ts b/src/panels/config/zha/types.ts
index 23bc9f74d7..1f006964d7 100644
--- a/src/panels/config/zha/types.ts
+++ b/src/panels/config/zha/types.ts
@@ -1,4 +1,3 @@
-import { HassEntity } from "home-assistant-js-websocket";
import { ZHADeviceEntity, Cluster } from "../../../data/zha";
export interface PickerTarget extends EventTarget {
@@ -17,7 +16,8 @@ export interface ChangeEvent {
}
export interface SetAttributeServiceData {
- entity_id: string;
+ ieee: string;
+ endpoint_id: number;
cluster_id: number;
cluster_type: string;
attribute: number;
@@ -26,17 +26,14 @@ export interface SetAttributeServiceData {
}
export interface IssueCommandServiceData {
- entity_id: string;
+ ieee: string;
+ endpoint_id: number;
cluster_id: number;
cluster_type: string;
command: number;
command_type: string;
}
-export interface ZHAEntitySelectedParams {
- entity: HassEntity;
-}
-
export interface ZHANodeSelectedParams {
node: ZHADeviceEntity;
}
diff --git a/src/panels/config/zha/zha-cluster-attributes.ts b/src/panels/config/zha/zha-cluster-attributes.ts
index d19354cdcd..165e336dbe 100644
--- a/src/panels/config/zha/zha-cluster-attributes.ts
+++ b/src/panels/config/zha/zha-cluster-attributes.ts
@@ -10,7 +10,6 @@ import {
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
-import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
@@ -19,7 +18,7 @@ import {
fetchAttributesForCluster,
ReadAttributeServiceData,
readAttributeValue,
- ZHADeviceEntity,
+ ZHADevice,
} from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
@@ -34,8 +33,7 @@ export class ZHAClusterAttributes extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showHelp: boolean;
- public selectedNode?: HassEntity;
- public selectedEntity?: ZHADeviceEntity;
+ public selectedNode?: ZHADevice;
public selectedCluster?: Cluster;
private _attributes: Attribute[];
private _selectedAttributeIndex: number;
@@ -57,7 +55,6 @@ export class ZHAClusterAttributes extends LitElement {
isWide: {},
showHelp: {},
selectedNode: {},
- selectedEntity: {},
selectedCluster: {},
_attributes: {},
_selectedAttributeIndex: {},
@@ -172,49 +169,54 @@ export class ZHAClusterAttributes extends LitElement {
}
private async _fetchAttributesForCluster(): Promise {
- if (this.selectedEntity && this.selectedCluster && this.hass) {
+ if (this.selectedNode && this.selectedCluster && this.hass) {
this._attributes = await fetchAttributesForCluster(
this.hass,
- this.selectedEntity!.entity_id,
- this.selectedEntity!.device_info!.identifiers[0][1],
+ this.selectedNode!.ieee,
+ this.selectedCluster!.endpoint_id,
this.selectedCluster!.id,
this.selectedCluster!.type
);
+ this._attributes.sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ });
}
}
private _computeReadAttributeServiceData():
| ReadAttributeServiceData
| undefined {
- if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) {
+ if (!this.selectedCluster || !this.selectedNode) {
return;
}
return {
- entity_id: this.selectedEntity!.entity_id,
+ ieee: this.selectedNode!.ieee,
+ endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
- : this.selectedNode!.attributes.manufacturer_code,
+ : this.selectedNode!.manufacturer_code,
};
}
private _computeSetAttributeServiceData():
| SetAttributeServiceData
| undefined {
- if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) {
+ if (!this.selectedCluster || !this.selectedNode) {
return;
}
return {
- entity_id: this.selectedEntity!.entity_id,
+ ieee: this.selectedNode!.ieee,
+ endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id,
value: this._attributeValue,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
- : this.selectedNode!.attributes.manufacturer_code,
+ : this.selectedNode!.manufacturer_code,
};
}
@@ -306,8 +308,7 @@ export class ZHAClusterAttributes extends LitElement {
[hidden] {
display: none;
}
-
- `,
+ `,
];
}
}
diff --git a/src/panels/config/zha/zha-cluster-commands.ts b/src/panels/config/zha/zha-cluster-commands.ts
index 398a7ff3d6..14429982f9 100644
--- a/src/panels/config/zha/zha-cluster-commands.ts
+++ b/src/panels/config/zha/zha-cluster-commands.ts
@@ -8,14 +8,13 @@ import {
css,
} from "lit-element";
import "@polymer/paper-card/paper-card";
-import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Cluster,
Command,
fetchCommandsForCluster,
- ZHADeviceEntity,
+ ZHADevice,
} from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
@@ -29,8 +28,7 @@ import {
export class ZHAClusterCommands extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
- public selectedNode?: HassEntity;
- public selectedEntity?: ZHADeviceEntity;
+ public selectedNode?: ZHADevice;
public selectedCluster?: Cluster;
private _showHelp: boolean;
private _commands: Command[];
@@ -50,7 +48,6 @@ export class ZHAClusterCommands extends LitElement {
hass: {},
isWide: {},
selectedNode: {},
- selectedEntity: {},
selectedCluster: {},
_showHelp: {},
_commands: {},
@@ -146,25 +143,29 @@ export class ZHAClusterCommands extends LitElement {
}
private async _fetchCommandsForCluster(): Promise {
- if (this.selectedEntity && this.selectedCluster && this.hass) {
+ if (this.selectedNode && this.selectedCluster && this.hass) {
this._commands = await fetchCommandsForCluster(
this.hass,
- this.selectedEntity!.entity_id,
- this.selectedEntity!.device_info!.identifiers[0][1],
+ this.selectedNode!.ieee,
+ this.selectedCluster!.endpoint_id,
this.selectedCluster!.id,
this.selectedCluster!.type
);
+ this._commands.sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ });
}
}
private _computeIssueClusterCommandServiceData():
| IssueCommandServiceData
| undefined {
- if (!this.selectedEntity || !this.selectedCluster) {
+ if (!this.selectedNode || !this.selectedCluster) {
return;
}
return {
- entity_id: this.selectedEntity!.entity_id,
+ ieee: this.selectedNode!.ieee,
+ endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
command: this._commands[this._selectedCommandIndex].id,
@@ -257,8 +258,7 @@ export class ZHAClusterCommands extends LitElement {
[hidden] {
display: none;
}
-
- `,
+ `,
];
}
}
diff --git a/src/panels/config/zha/zha-clusters.ts b/src/panels/config/zha/zha-clusters.ts
index d580cc49c6..c45772c510 100644
--- a/src/panels/config/zha/zha-clusters.ts
+++ b/src/panels/config/zha/zha-clusters.ts
@@ -11,11 +11,7 @@ import "@polymer/paper-card/paper-card";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
-import {
- Cluster,
- fetchClustersForZhaNode,
- ZHADeviceEntity,
-} from "../../../data/zha";
+import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
@@ -31,14 +27,16 @@ declare global {
}
const computeClusterKey = (cluster: Cluster): string => {
- return `${cluster.name} (id: ${cluster.id}, type: ${cluster.type})`;
+ return `${cluster.name} (Endpoint id: ${cluster.endpoint_id}, Id: ${
+ cluster.id
+ }, Type: ${cluster.type})`;
};
export class ZHAClusters extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showHelp: boolean;
- public selectedEntity?: ZHADeviceEntity;
+ public selectedDevice?: ZHADevice;
private _selectedClusterIndex: number;
private _clusters: Cluster[];
@@ -54,14 +52,14 @@ export class ZHAClusters extends LitElement {
hass: {},
isWide: {},
showHelp: {},
- selectedEntity: {},
+ selectedDevice: {},
_selectedClusterIndex: {},
_clusters: {},
};
}
protected updated(changedProperties: PropertyValues): void {
- if (changedProperties.has("selectedEntity")) {
+ if (changedProperties.has("selectedDevice")) {
this._clusters = [];
this._selectedClusterIndex = -1;
fireEvent(this, "zha-cluster-selected", {
@@ -103,9 +101,11 @@ export class ZHAClusters extends LitElement {
if (this.hass) {
this._clusters = await fetchClustersForZhaNode(
this.hass,
- this.selectedEntity!.entity_id,
- this.selectedEntity!.device_info!.identifiers[0][1]
+ this.selectedDevice!.ieee
);
+ this._clusters.sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ });
}
}
diff --git a/src/panels/config/zha/zha-device-card.ts b/src/panels/config/zha/zha-device-card.ts
new file mode 100644
index 0000000000..449f26d956
--- /dev/null
+++ b/src/panels/config/zha/zha-device-card.ts
@@ -0,0 +1,118 @@
+import {
+ html,
+ LitElement,
+ property,
+ TemplateResult,
+ CSSResult,
+ css,
+} from "lit-element";
+import "@polymer/paper-item/paper-icon-item";
+import "@polymer/paper-item/paper-item-body";
+import "@polymer/paper-card/paper-card";
+import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
+import "@polymer/paper-item/paper-item";
+import "@polymer/paper-listbox/paper-listbox";
+import { fireEvent } from "../../../common/dom/fire_event";
+import { haStyle } from "../../../resources/ha-style";
+import { HomeAssistant } from "../../../types";
+
+import "../../../components/entity/state-badge";
+import { ZHADevice } from "../../../data/zha";
+
+class ZHADeviceCard extends LitElement {
+ @property() public hass?: HomeAssistant;
+ @property() public narrow?: boolean;
+ @property() public device?: ZHADevice;
+
+ protected render(): TemplateResult | void {
+ return html`
+
+
+
+ - IEEE:
+ - ${this.device!.ieee}
+ - Quirk applied:
+ - ${this.device!.quirk_applied}
+ - Quirk:
+ - ${this.device!.quirk_class}
+
+
+
+
+ ${this.device!.entities.map(
+ (entity) => html`
+
+
+
+ ${entity.name}
+ ${entity.entity_id}
+
+
+ `
+ )}
+
+
+ `;
+ }
+
+ private _openMoreInfo(ev: MouseEvent): void {
+ fireEvent(this, "hass-more-info", {
+ entityId: (ev.currentTarget as any).entity.entity_id,
+ });
+ }
+
+ static get styles(): CSSResult[] {
+ return [
+ haStyle,
+ css`
+ :host(:not([narrow])) .device-entities {
+ max-height: 225px;
+ overflow: auto;
+ }
+ paper-card {
+ flex: 1 0 100%;
+ padding-bottom: 10px;
+ min-width: 0;
+ }
+ .device {
+ width: 30%;
+ }
+ .label {
+ font-weight: bold;
+ }
+ .info {
+ color: var(--secondary-text-color);
+ font-weight: bold;
+ }
+ dl dt {
+ float: left;
+ width: 100px;
+ text-align: left;
+ }
+ dt dd {
+ margin-left: 10px;
+ text-align: left;
+ }
+ paper-icon-item {
+ cursor: pointer;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "zha-device-card": ZHADeviceCard;
+ }
+}
+
+customElements.define("zha-device-card", ZHADeviceCard);
diff --git a/src/panels/config/zha/zha-entities.ts b/src/panels/config/zha/zha-entities.ts
deleted file mode 100644
index 9b6f373f07..0000000000
--- a/src/panels/config/zha/zha-entities.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import {
- html,
- LitElement,
- PropertyDeclarations,
- PropertyValues,
- TemplateResult,
- CSSResult,
- css,
-} from "lit-element";
-import "@polymer/paper-button/paper-button";
-import "@polymer/paper-item/paper-item";
-import "@polymer/paper-listbox/paper-listbox";
-import { HassEntity } from "home-assistant-js-websocket";
-import { fireEvent } from "../../../common/dom/fire_event";
-import { fetchEntitiesForZhaNode } from "../../../data/zha";
-import { haStyle } from "../../../resources/ha-style";
-import { HomeAssistant } from "../../../types";
-import { ItemSelectedEvent } from "./types";
-
-declare global {
- // for fire event
- interface HASSDomEvents {
- "zha-entity-selected": {
- entity?: HassEntity;
- };
- }
-}
-
-export class ZHAEntities extends LitElement {
- public hass?: HomeAssistant;
- public showHelp?: boolean;
- public selectedNode?: HassEntity;
- private _selectedEntityIndex: number;
- private _entities: HassEntity[];
-
- constructor() {
- super();
- this._entities = [];
- this._selectedEntityIndex = -1;
- }
-
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- showHelp: {},
- selectedNode: {},
- _selectedEntityIndex: {},
- _entities: {},
- };
- }
-
- protected updated(changedProperties: PropertyValues): void {
- if (changedProperties.has("selectedNode")) {
- this._entities = [];
- this._selectedEntityIndex = -1;
- fireEvent(this, "zha-entity-selected", {
- entity: undefined,
- });
- this._fetchEntitiesForZhaNode();
- }
- super.update(changedProperties);
- }
-
- protected render(): TemplateResult | void {
- return html`
-
-
-
- ${this._entities.map(
- (entry) => html`
- ${entry.entity_id}
- `
- )}
-
-
-
- ${this.showHelp
- ? html`
-
- Select entity to view per-entity options
-
- `
- : ""}
- ${this._selectedEntityIndex !== -1
- ? html`
-
- `
- : ""}
- `;
- }
-
- private async _fetchEntitiesForZhaNode(): Promise {
- if (this.hass) {
- const fetchedEntities = await fetchEntitiesForZhaNode(this.hass);
- this._entities = fetchedEntities[this.selectedNode!.attributes.ieee];
- }
- }
-
- private _selectedEntityChanged(event: ItemSelectedEvent): void {
- this._selectedEntityIndex = event.target!.selected;
- fireEvent(this, "zha-entity-selected", {
- entity: this._entities[this._selectedEntityIndex],
- });
- }
-
- private _showEntityInformation(): void {
- fireEvent(this, "hass-more-info", {
- entityId: this._entities[this._selectedEntityIndex].entity_id,
- });
- }
-
- static get styles(): CSSResult[] {
- return [
- haStyle,
- css`
- .flex {
- -ms-flex: 1 1 0.000000001px;
- -webkit-flex: 1;
- flex: 1;
- -webkit-flex-basis: 0.000000001px;
- flex-basis: 0.000000001px;
- }
-
- .node-picker {
- display: -ms-flexbox;
- display: -webkit-flex;
- display: flex;
- -ms-flex-direction: row;
- -webkit-flex-direction: row;
- flex-direction: row;
- -ms-flex-align: center;
- -webkit-align-items: center;
- align-items: center;
- padding-left: 28px;
- padding-right: 28px;
- padding-bottom: 10px;
- }
- .actions {
- border-top: 1px solid #e8e8e8;
- padding: 5px 16px;
- position: relative;
- }
- .actions paper-button:not([disabled]) {
- color: var(--primary-color);
- font-weight: 500;
- }
- .helpText {
- color: grey;
- padding: 16px;
- }
- `,
- ];
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "zha-entities": ZHAEntities;
- }
-}
-
-customElements.define("zha-entities", ZHAEntities);
diff --git a/src/panels/config/zha/zha-network.ts b/src/panels/config/zha/zha-network.ts
index bbd70a1629..55cb4a4da3 100644
--- a/src/panels/config/zha/zha-network.ts
+++ b/src/panels/config/zha/zha-network.ts
@@ -102,8 +102,7 @@ export class ZHANetwork extends LitElement {
[hidden] {
display: none;
}
-
- `,
+ `,
];
}
}
diff --git a/src/panels/config/zha/zha-node.ts b/src/panels/config/zha/zha-node.ts
index 2c2a680b10..1dd11c8c0a 100644
--- a/src/panels/config/zha/zha-node.ts
+++ b/src/panels/config/zha/zha-node.ts
@@ -4,6 +4,7 @@ import {
PropertyDeclarations,
TemplateResult,
CSSResult,
+ PropertyValues,
css,
} from "lit-element";
import "@polymer/paper-button/paper-button";
@@ -11,29 +12,22 @@ import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
-import { HassEntity } from "home-assistant-js-websocket";
-import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
-import computeStateName from "../../../common/entity/compute_state_name";
-import sortByName from "../../../common/entity/states_sort_by_name";
+import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
-import {
- ItemSelectedEvent,
- NodeServiceData,
- ZHAEntitySelectedParams,
-} from "./types";
+import { ItemSelectedEvent, NodeServiceData } from "./types";
import "./zha-clusters";
-import "./zha-entities";
-import { reconfigureNode } from "../../../data/zha";
+import "./zha-device-card";
+import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha";
declare global {
// for fire event
interface HASSDomEvents {
"zha-node-selected": {
- node?: HassEntity;
+ node?: ZHADevice;
};
}
}
@@ -43,10 +37,9 @@ export class ZHANode extends LitElement {
public isWide?: boolean;
private _showHelp: boolean;
private _selectedNodeIndex: number;
- private _selectedNode?: HassEntity;
- private _selectedEntity?: HassEntity;
+ private _selectedNode?: ZHADevice;
private _serviceData?: {};
- private _nodes: HassEntity[];
+ private _nodes: ZHADevice[];
constructor() {
super();
@@ -62,13 +55,31 @@ export class ZHANode extends LitElement {
_showHelp: {},
_selectedNodeIndex: {},
_selectedNode: {},
+ _entities: {},
_serviceData: {},
- _selectedEntity: {},
+ _nodes: {},
};
}
+ public firstUpdated(changedProperties: PropertyValues): void {
+ super.firstUpdated(changedProperties);
+ if (this._nodes.length === 0) {
+ this._fetchDevices();
+ }
+ this.addEventListener("hass-service-called", (ev) =>
+ this.serviceCalled(ev)
+ );
+ }
+
+ protected serviceCalled(ev): void {
+ // Check if this is for us
+ if (ev.detail.success && ev.detail.service === "remove") {
+ this._selectedNodeIndex = -1;
+ this._fetchDevices();
+ }
+ }
+
protected render(): TemplateResult | void {
- this._nodes = this._computeNodes(this.hass);
return html`
`
: ""}
+ ${this._selectedNodeIndex !== -1
+ ? html`
+
+ `
+ : ""}
${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""}
- ${this._selectedNodeIndex !== -1 ? this._renderEntities() : ""}
- ${this._selectedEntity ? this._renderClusters() : ""}
+ ${this._selectedNode ? this._renderClusters() : ""}
`;
@@ -123,9 +142,6 @@ export class ZHANode extends LitElement {
private _renderNodeActions(): TemplateResult {
return html`
-
Node Information
Reconfigure Node
@@ -158,22 +174,11 @@ export class ZHANode extends LitElement {
`;
}
- private _renderEntities(): TemplateResult {
- return html`
-
- `;
- }
-
private _renderClusters(): TemplateResult {
return html`
`;
@@ -186,50 +191,26 @@ export class ZHANode extends LitElement {
private _selectedNodeChanged(event: ItemSelectedEvent): void {
this._selectedNodeIndex = event!.target!.selected;
this._selectedNode = this._nodes[this._selectedNodeIndex];
- this._selectedEntity = undefined;
fireEvent(this, "zha-node-selected", { node: this._selectedNode });
this._serviceData = this._computeNodeServiceData();
}
private async _onReconfigureNodeClick(): Promise
{
if (this.hass) {
- await reconfigureNode(this.hass, this._selectedNode!.attributes.ieee);
+ await reconfigureNode(this.hass, this._selectedNode!.ieee);
}
}
- private _showNodeInformation(): void {
- fireEvent(this, "hass-more-info", {
- entityId: this._selectedNode!.entity_id,
- });
- }
-
private _computeNodeServiceData(): NodeServiceData {
return {
- ieee_address: this._selectedNode!.attributes.ieee,
+ ieee_address: this._selectedNode!.ieee,
};
}
- private _computeSelectCaption(stateObj: HassEntity): string {
- return (
- computeStateName(stateObj) + " (Node:" + stateObj.attributes.ieee + ")"
- );
- }
-
- private _computeNodes(hass?: HomeAssistant): HassEntity[] {
- if (hass) {
- return Object.keys(hass.states)
- .map((key) => hass.states[key])
- .filter((ent) => ent.entity_id.match("zha[.]"))
- .sort(sortByName);
- } else {
- return [];
- }
- }
-
- private _onEntitySelected(
- entitySelectedEvent: HASSDomEvent
- ): void {
- this._selectedEntity = entitySelectedEvent.detail.entity;
+ private async _fetchDevices() {
+ this._nodes = (await fetchDevices(this.hass!)).sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ });
}
static get styles(): CSSResult[] {
@@ -287,6 +268,17 @@ export class ZHANode extends LitElement {
padding-bottom: 10px;
}
+ .card {
+ box-sizing: border-box;
+ display: flex;
+ flex: 1 0 300px;
+ min-width: 0;
+ max-width: 600px;
+ padding-left: 28px;
+ padding-right: 28px;
+ padding-bottom: 10px;
+ }
+
ha-service-description {
display: block;
color: grey;
diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts
index aab241299b..8311aec7ad 100644
--- a/src/panels/lovelace/cards/hui-alarm-panel-card.ts
+++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts
@@ -2,8 +2,10 @@ import {
html,
LitElement,
PropertyValues,
- PropertyDeclarations,
TemplateResult,
+ CSSResult,
+ css,
+ property,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
@@ -50,20 +52,22 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
return { states: ["arm_home", "arm_away"] };
}
- public hass?: HomeAssistant;
- private _config?: Config;
- private _code?: string;
-
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- _config: {},
- _code: {},
- };
- }
+ @property() public hass?: HomeAssistant;
+ @property() private _config?: Config;
+ @property() private _code?: string;
public getCardSize(): number {
- return 4;
+ if (!this._config || !this.hass) {
+ return 0;
+ }
+
+ const stateObj = this.hass.states[this._config.entity];
+
+ if (!stateObj) {
+ return 0;
+ }
+
+ return stateObj.attributes.code_format !== FORMAT_NUMBER ? 3 : 8;
}
public setConfig(config: Config): void {
@@ -114,7 +118,6 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
}
return html`
- ${this.renderStyle()}
+ static get styles(): CSSResult[] {
+ return [
+ css`
ha-card {
padding-bottom: 16px;
position: relative;
@@ -293,8 +296,8 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
paper-button#disarm {
color: var(--google-red-500);
}
-
- `;
+ `,
+ ];
}
}
diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts
index e6e1c2678b..785eb339e2 100644
--- a/src/panels/lovelace/cards/hui-light-card.ts
+++ b/src/panels/lovelace/cards/hui-light-card.ts
@@ -2,8 +2,8 @@ import {
html,
LitElement,
PropertyValues,
- PropertyDeclarations,
TemplateResult,
+ property,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button";
@@ -53,20 +53,11 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
return {};
}
- public hass?: HomeAssistant;
- private _config?: Config;
+ @property() public hass?: HomeAssistant;
+ @property() private _config?: Config;
+ @property() private _roundSliderStyle?: TemplateResult;
+ @property() private _jQuery?: any;
private _brightnessTimout?: number;
- private _roundSliderStyle?: TemplateResult;
- private _jQuery?: any;
-
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- _config: {},
- roundSliderStyle: {},
- _jQuery: {},
- };
- }
public getCardSize(): number {
return 2;
diff --git a/src/panels/lovelace/cards/hui-shopping-list-card.ts b/src/panels/lovelace/cards/hui-shopping-list-card.ts
index 78eafecd8a..e98053f36f 100644
--- a/src/panels/lovelace/cards/hui-shopping-list-card.ts
+++ b/src/panels/lovelace/cards/hui-shopping-list-card.ts
@@ -1,4 +1,11 @@
-import { html, LitElement, TemplateResult } from "lit-element";
+import {
+ html,
+ LitElement,
+ TemplateResult,
+ css,
+ CSSResult,
+ property,
+} from "lit-element";
import { repeat } from "lit-html/directives/repeat";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-checkbox/paper-checkbox";
@@ -26,25 +33,17 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
await import(/* webpackChunkName: "hui-shopping-list-editor" */ "../editor/config-elements/hui-shopping-list-editor");
return document.createElement("hui-shopping-list-card-editor");
}
+
public static getStubConfig(): object {
return {};
}
- public hass?: HomeAssistant;
- private _config?: Config;
- private _uncheckedItems?: ShoppingListItem[];
- private _checkedItems?: ShoppingListItem[];
+ @property() public hass?: HomeAssistant;
+ @property() private _config?: Config;
+ @property() private _uncheckedItems?: ShoppingListItem[];
+ @property() private _checkedItems?: ShoppingListItem[];
private _unsubEvents?: Promise<() => Promise>;
- static get properties() {
- return {
- hass: {},
- _config: {},
- _uncheckedItems: {},
- _checkedItems: {},
- };
- }
-
public getCardSize(): number {
return (this._config ? (this._config.title ? 1 : 0) : 0) + 3;
}
@@ -82,7 +81,6 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
}
return html`
- ${this.renderStyle()}
+ static get styles(): CSSResult[] {
+ return [
+ css`
.editRow,
.addRow {
display: flex;
@@ -192,6 +190,9 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
padding: 9px 15px 11px 15px;
cursor: pointer;
}
+ paper-item-body {
+ width: 75%;
+ }
paper-checkbox {
padding: 11px 11px 11px 18px;
}
@@ -230,8 +231,8 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
.addRow > ha-icon {
color: var(--secondary-text-color);
}
-
- `;
+ `,
+ ];
}
private async _fetchData(): Promise {
diff --git a/src/panels/lovelace/common/create-row-element.ts b/src/panels/lovelace/common/create-row-element.ts
index feafcb8952..510d52a53a 100644
--- a/src/panels/lovelace/common/create-row-element.ts
+++ b/src/panels/lovelace/common/create-row-element.ts
@@ -46,8 +46,9 @@ const DOMAIN_TO_ELEMENT_TYPE = {
input_select: "input-select",
input_text: "input-text",
light: "toggle",
- media_player: "media-player",
lock: "lock",
+ media_player: "media-player",
+ remote: "toggle",
scene: "scene",
script: "script",
sensor: "sensor",
diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts
index eddafa4486..52ed479310 100644
--- a/src/panels/lovelace/components/hui-card-options.ts
+++ b/src/panels/lovelace/components/hui-card-options.ts
@@ -98,7 +98,11 @@ export class HuiCardOptions extends LitElement {
slot="dropdown-trigger"
>
- Move Card
+ ${this.hass!.localize(
+ "ui.panel.lovelace.editor.edit_card.move"
+ )}
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.delete"
diff --git a/src/panels/lovelace/components/hui-generic-entity-row.ts b/src/panels/lovelace/components/hui-generic-entity-row.ts
index 2409179b65..d9916ffe69 100644
--- a/src/panels/lovelace/components/hui-generic-entity-row.ts
+++ b/src/panels/lovelace/components/hui-generic-entity-row.ts
@@ -5,25 +5,20 @@ import "../../../components/ha-icon";
import computeStateName from "../../../common/entity/compute_state_name";
import {
LitElement,
- PropertyDeclarations,
html,
css,
CSSResult,
PropertyValues,
+ property,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/hui-entities-card";
import { computeRTL } from "../../../common/util/compute_rtl";
class HuiGenericEntityRow extends LitElement {
- public hass?: HomeAssistant;
- public config?: EntitiesCardEntityConfig;
- public showSecondary: boolean;
-
- constructor() {
- super();
- this.showSecondary = true;
- }
+ @property() public hass?: HomeAssistant;
+ @property() public config?: EntitiesCardEntityConfig;
+ @property() public showSecondary: boolean = true;
protected render() {
if (!this.hass || !this.config) {
@@ -70,14 +65,6 @@ class HuiGenericEntityRow extends LitElement {
`;
}
- static get properties(): PropertyDeclarations {
- return {
- hass: {},
- config: {},
- showSecondary: {},
- };
- }
-
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("hass")) {
diff --git a/src/panels/lovelace/components/hui-image.js b/src/panels/lovelace/components/hui-image.js
deleted file mode 100644
index 1ad89c3bac..0000000000
--- a/src/panels/lovelace/components/hui-image.js
+++ /dev/null
@@ -1,199 +0,0 @@
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-import "@polymer/paper-toggle-button/paper-toggle-button";
-
-import { STATES_OFF } from "../../../common/const";
-import LocalizeMixin from "../../../mixins/localize-mixin";
-
-import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
-
-const UPDATE_INTERVAL = 10000;
-const DEFAULT_FILTER = "grayscale(100%)";
-
-/*
- * @appliesMixin LocalizeMixin
- */
-class HuiImage extends LocalizeMixin(PolymerElement) {
- static get template() {
- return html`
- ${this.styleTemplate}
-
-

-
-
- `;
- }
-
- static get styleTemplate() {
- return html`
-
- `;
- }
-
- static get properties() {
- return {
- hass: {
- type: Object,
- observer: "_hassChanged",
- },
- entity: String,
- image: String,
- stateImage: Object,
- cameraImage: String,
- aspectRatio: String,
- filter: String,
- stateFilter: Object,
- _imageSrc: String,
- };
- }
-
- static get observers() {
- return ["_configChanged(image, stateImage, cameraImage, aspectRatio)"];
- }
-
- connectedCallback() {
- super.connectedCallback();
- if (this.cameraImage) {
- this.timer = setInterval(
- () => this._updateCameraImageSrc(),
- UPDATE_INTERVAL
- );
- }
- }
-
- disconnectedCallback() {
- super.disconnectedCallback();
- clearInterval(this.timer);
- }
-
- _configChanged(image, stateImage, cameraImage, aspectRatio) {
- const ratio = parseAspectRatio(aspectRatio);
-
- if (ratio && ratio.w > 0 && ratio.h > 0) {
- this.$.wrapper.style.paddingBottom = `${(
- (100 * ratio.h) /
- ratio.w
- ).toFixed(2)}%`;
- this.$.wrapper.classList.add("ratio");
- }
-
- if (cameraImage) {
- this._updateCameraImageSrc();
- } else if (image && !stateImage) {
- this._imageSrc = image;
- }
- }
-
- _onImageError() {
- this._imageSrc = null;
- this.$.image.classList.add("hidden");
- if (!this.$.wrapper.classList.contains("ratio")) {
- this.$.brokenImage.style.setProperty(
- "height",
- `${this._lastImageHeight || "100"}px`
- );
- }
- this.$.brokenImage.classList.remove("hidden");
- }
-
- _onImageLoad() {
- this.$.image.classList.remove("hidden");
- this.$.brokenImage.classList.add("hidden");
- if (!this.$.wrapper.classList.contains("ratio")) {
- this._lastImageHeight = this.$.image.offsetHeight;
- }
- }
-
- _hassChanged(hass) {
- if (this.cameraImage || !this.entity) {
- return;
- }
-
- const stateObj = hass.states[this.entity];
- const newState = !stateObj ? "unavailable" : stateObj.state;
-
- if (newState === this._currentState) return;
- this._currentState = newState;
-
- this._updateStateImage();
- this._updateStateFilter(stateObj);
- }
-
- _updateStateImage() {
- if (!this.stateImage) {
- this._imageFallback = true;
- return;
- }
- const stateImg = this.stateImage[this._currentState];
- this._imageSrc = stateImg || this.image;
- this._imageFallback = !stateImg;
- }
-
- _updateStateFilter(stateObj) {
- let filter;
- if (!this.stateFilter) {
- filter = this.filter;
- } else {
- filter = this.stateFilter[this._currentState] || this.filter;
- }
-
- const isOff = !stateObj || STATES_OFF.includes(stateObj.state);
- this.$.image.style.filter =
- filter || (isOff && this._imageFallback && DEFAULT_FILTER) || "";
- }
-
- async _updateCameraImageSrc() {
- try {
- const { content_type: contentType, content } = await this.hass.callWS({
- type: "camera_thumbnail",
- entity_id: this.cameraImage,
- });
- this._imageSrc = `data:${contentType};base64, ${content}`;
- this._onImageLoad();
- } catch (err) {
- this._onImageError();
- }
- }
-}
-
-customElements.define("hui-image", HuiImage);
diff --git a/src/panels/lovelace/components/hui-image.ts b/src/panels/lovelace/components/hui-image.ts
new file mode 100644
index 0000000000..cdc7b06aa1
--- /dev/null
+++ b/src/panels/lovelace/components/hui-image.ts
@@ -0,0 +1,226 @@
+import "@polymer/paper-toggle-button/paper-toggle-button";
+
+import { STATES_OFF } from "../../../common/const";
+
+import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ property,
+ CSSResult,
+ css,
+ PropertyValues,
+ query,
+} from "lit-element";
+import { HomeAssistant } from "../../../types";
+import { styleMap } from "lit-html/directives/style-map";
+import { classMap } from "lit-html/directives/class-map";
+import { b64toBlob } from "../../../common/file/b64-to-blob";
+import { fetchThumbnail } from "../../../data/camera";
+
+const UPDATE_INTERVAL = 10000;
+const DEFAULT_FILTER = "grayscale(100%)";
+
+export interface StateSpecificConfig {
+ [state: string]: string;
+}
+
+/*
+ * @appliesMixin LocalizeMixin
+ */
+class HuiImage extends LitElement {
+ @property() public hass?: HomeAssistant;
+ @property() public entity?: string;
+ @property() public image?: string;
+ @property() public stateImage?: StateSpecificConfig;
+ @property() public cameraImage?: string;
+ @property() public aspectRatio?: string;
+ @property() public filter?: string;
+ @property() public stateFilter?: StateSpecificConfig;
+
+ @property() private _loadError?: boolean;
+ @property() private _cameraImageSrc?: string;
+ @query("img") private _image!: HTMLImageElement;
+ private _lastImageHeight?: number;
+ private _cameraUpdater?: number;
+ private _attached?: boolean;
+
+ public connectedCallback() {
+ super.connectedCallback();
+ this._attached = true;
+ this._startUpdateCameraInterval();
+ }
+
+ public disconnectedCallback() {
+ super.disconnectedCallback();
+ this._attached = false;
+ this._stopUpdateCameraInterval();
+ }
+
+ protected render(): TemplateResult | void {
+ const ratio = this.aspectRatio ? parseAspectRatio(this.aspectRatio) : null;
+ const stateObj =
+ this.hass && this.entity ? this.hass.states[this.entity] : undefined;
+ const state = stateObj ? stateObj.state : "unavailable";
+
+ // Figure out image source to use
+ let imageSrc: string | undefined;
+ // Track if we are we using a fallback image, used for filter.
+ let imageFallback = !this.stateImage;
+
+ if (this.cameraImage) {
+ imageSrc = this._cameraImageSrc;
+ } else if (this.stateImage) {
+ const stateImage = this.stateImage[state];
+
+ if (stateImage) {
+ imageSrc = stateImage;
+ } else {
+ imageSrc = this.image;
+ imageFallback = true;
+ }
+ } else {
+ imageSrc = this.image;
+ }
+
+ // Figure out filter to use
+ let filter = this.filter || "";
+
+ if (this.stateFilter && this.stateFilter[state]) {
+ filter = this.stateFilter[state];
+ }
+
+ if (!filter && this.entity) {
+ const isOff = !stateObj || STATES_OFF.includes(state);
+ filter = isOff && imageFallback ? DEFAULT_FILTER : "";
+ }
+
+ return html`
+ 0 && ratio.h > 0
+ ? `${((100 * ratio.h) / ratio.w).toFixed(2)}%`
+ : "",
+ })}
+ class=${classMap({
+ ratio: Boolean(ratio && ratio.w > 0 && ratio.h > 0),
+ })}
+ >
+

+
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues): void {
+ if (changedProps.has("cameraImage")) {
+ this._updateCameraImageSrc();
+ this._startUpdateCameraInterval();
+ return;
+ }
+ }
+
+ private _startUpdateCameraInterval() {
+ this._stopUpdateCameraInterval();
+ if (this.cameraImage && this._attached) {
+ this._cameraUpdater = window.setInterval(
+ () => this._updateCameraImageSrc(),
+ UPDATE_INTERVAL
+ );
+ }
+ }
+
+ private _stopUpdateCameraInterval() {
+ if (this._cameraUpdater) {
+ clearInterval(this._cameraUpdater);
+ }
+ }
+
+ private _onImageError() {
+ this._loadError = true;
+ }
+
+ private async _onImageLoad() {
+ this._loadError = false;
+ await this.updateComplete;
+ this._lastImageHeight = this._image.offsetHeight;
+ }
+
+ private async _updateCameraImageSrc() {
+ if (!this.hass || !this.cameraImage) {
+ return;
+ }
+ if (this._cameraImageSrc) {
+ URL.revokeObjectURL(this._cameraImageSrc);
+ this._cameraImageSrc = undefined;
+ }
+ try {
+ const { content_type: contentType, content } = await fetchThumbnail(
+ this.hass,
+ this.cameraImage
+ );
+ this._cameraImageSrc = URL.createObjectURL(
+ b64toBlob(content, contentType)
+ );
+ this._onImageLoad();
+ } catch (err) {
+ this._onImageError();
+ }
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ img {
+ display: block;
+ height: auto;
+ transition: filter 0.2s linear;
+ width: 100%;
+ }
+
+ .ratio {
+ position: relative;
+ width: 100%;
+ height: 0;
+ }
+
+ .ratio img,
+ .ratio div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ #brokenImage {
+ background: grey url("/static/images/image-broken.svg") center/36px
+ no-repeat;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-image": HuiImage;
+ }
+}
+
+customElements.define("hui-image", HuiImage);
diff --git a/src/panels/lovelace/components/hui-yaml-editor.ts b/src/panels/lovelace/components/hui-yaml-editor.ts
index f83d392bee..2162225ae3 100644
--- a/src/panels/lovelace/components/hui-yaml-editor.ts
+++ b/src/panels/lovelace/components/hui-yaml-editor.ts
@@ -3,44 +3,67 @@ import CodeMirror from "codemirror";
import "codemirror/mode/yaml/yaml";
// @ts-ignore
import codeMirrorCSS from "codemirror/lib/codemirror.css";
+import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
+import { computeRTL } from "../../../common/util/compute_rtl";
+
declare global {
interface HASSDomEvents {
"yaml-changed": {
value: string;
};
+ "yaml-save": undefined;
}
}
export class HuiYamlEditor extends HTMLElement {
+ public _hass?: HomeAssistant;
public codemirror: CodeMirror;
private _value: string;
public constructor() {
super();
+ CodeMirror.commands.save = (cm: CodeMirror) => {
+ fireEvent(cm.getWrapperElement(), "yaml-save");
+ };
this._value = "";
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
`;
}
+ set hass(hass: HomeAssistant) {
+ this._hass = hass;
+ if (this._hass) {
+ this.setScrollBarDirection();
+ }
+ }
+
set value(value: string) {
if (this.codemirror) {
if (value !== this.codemirror.getValue()) {
@@ -72,7 +95,12 @@ export class HuiYamlEditor extends HTMLElement {
cm.replaceSelection(spaces);
},
},
+ gutters:
+ this._hass && computeRTL(this._hass!)
+ ? ["rtl-gutter", "CodeMirror-linenumbers"]
+ : [],
});
+ this.setScrollBarDirection();
this.codemirror.on("changes", () => this._onChange());
} else {
this.codemirror.refresh();
@@ -82,6 +110,16 @@ export class HuiYamlEditor extends HTMLElement {
private _onChange(): void {
fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
}
+
+ private setScrollBarDirection() {
+ if (!this.codemirror) {
+ return;
+ }
+
+ this.codemirror
+ .getWrapperElement()
+ .classList.toggle("rtl", computeRTL(this._hass!));
+ }
}
declare global {
diff --git a/src/panels/lovelace/editor/card-editor/hui-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts
index 3898e7b9ac..1e08ff49dd 100644
--- a/src/panels/lovelace/editor/card-editor/hui-edit-card.ts
+++ b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts
@@ -119,8 +119,10 @@ export class HuiEditCard extends LitElement {
? this._configElement
: html`
`}
diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts
index 969a48f2e6..d5a3dad07b 100644
--- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts
@@ -57,7 +57,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
return html``;
}
- const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
+ const states = ["arm_home", "arm_away", "arm_night", "armed_custom_bypass"];
return html`
${configElementStyle} ${this.renderStyle()}
diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts
index 5e1accff3d..d95a95f9b1 100644
--- a/src/panels/lovelace/ha-panel-lovelace.ts
+++ b/src/panels/lovelace/ha-panel-lovelace.ts
@@ -95,6 +95,7 @@ class LovelacePanel extends LitElement {
if (state === "yaml-editor") {
return html`
diff --git a/src/panels/lovelace/hui-editor.ts b/src/panels/lovelace/hui-editor.ts
index fcdb102773..f92de99ca8 100644
--- a/src/panels/lovelace/hui-editor.ts
+++ b/src/panels/lovelace/hui-editor.ts
@@ -18,6 +18,7 @@ import "./components/hui-yaml-editor";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HuiYamlEditor } from "./components/hui-yaml-editor";
+import { HomeAssistant } from "../../types";
const lovelaceStruct = struct.interface({
title: "string?",
@@ -26,6 +27,7 @@ const lovelaceStruct = struct.interface({
});
class LovelaceFullConfigEditor extends LitElement {
+ public hass?: HomeAssistant;
public lovelace?: Lovelace;
public closeEditor?: () => void;
private _saving?: boolean;
@@ -34,6 +36,7 @@ class LovelaceFullConfigEditor extends LitElement {
static get properties() {
return {
+ hass: {},
lovelace: {},
_saving: {},
_changed: {},
@@ -61,7 +64,11 @@ class LovelaceFullConfigEditor extends LitElement {
-
+
@@ -99,7 +106,6 @@ class LovelaceFullConfigEditor extends LitElement {
.content {
height: calc(100vh - 68px);
- direction: ltr;
}
hui-code-editor {
diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts
index 043c61e784..3c07001b5f 100644
--- a/src/panels/lovelace/hui-root.ts
+++ b/src/panels/lovelace/hui-root.ts
@@ -49,6 +49,7 @@ import { showEditLovelaceDialog } from "./editor/lovelace-editor/show-edit-lovel
import { Lovelace } from "./types";
import { afterNextRender } from "../../common/util/render-status";
import { haStyle } from "../../resources/ha-style";
+import { computeRTL, computeRTLDirection } from "../../common/util/compute_rtl";
// CSS and JS should only be imported once. Modules and HTML are safe.
const CSS_CACHE = {};
@@ -243,6 +244,7 @@ class HUIRoot extends LitElement {
scrollable
.selected="${this._curView}"
@iron-activate="${this._handleViewSelected}"
+ dir="${computeRTLDirection(this.hass!)}"
>
${this.lovelace!.config.views.map(
(view) => html`
@@ -252,7 +254,9 @@ class HUIRoot extends LitElement {
@@ -277,7 +281,9 @@ class HUIRoot extends LitElement {
${this._element}
+ ${this._elements}
`;
}
@@ -54,30 +57,47 @@ export class HuiUnusedEntities extends LitElement {
return html`
`;
}
- private _createElement(): void {
- if (this._hass) {
- const entities = computeUnusedEntities(this._hass, this._config!).map(
- (entity) => ({
- entity,
- secondary_info: "entity-id",
- })
- );
- this._element = createCardElement({
- type: "entities",
- title: "Unused entities",
- entities,
- show_header_toggle: false,
- });
- this._element!.hass = this._hass;
+ private _createElements(): void {
+ if (!this._hass) {
+ return;
}
+ const domains: { [domain: string]: string[] } = {};
+ computeUnusedEntities(this._hass, this._config!).forEach((entity) => {
+ const domain = computeDomain(entity);
+
+ if (!(domain in domains)) {
+ domains[domain] = [];
+ }
+ domains[domain].push(entity);
+ });
+ this._elements = Object.keys(domains)
+ .sort()
+ .map((domain) => {
+ const el = createCardElement({
+ type: "entities",
+ title: this._hass!.localize(`domain.${domain}`) || domain,
+ entities: domains[domain].map((entity) => ({
+ entity,
+ secondary_info: "entity-id",
+ })),
+ show_header_toggle: false,
+ });
+ el.hass = this._hass;
+ return el;
+ });
}
}
diff --git a/src/translations/en.json b/src/translations/en.json
index bac990dfb5..d20f31f8cf 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -530,7 +530,18 @@
"introduction": "Here it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.",
"area_registry": {
"caption": "Area Registry",
- "description": "Overview of all areas in your home."
+ "description": "Overview of all areas in your home.",
+ "picker": {
+ "header": "Area Registry"
+ },
+ "no_areas": "Looks like you have no areas yet!",
+ "create_area": "CREATE AREA",
+ "editor": {
+ "default_name": "New Area",
+ "delete": "DELETE",
+ "update": "UPDATE",
+ "create": "CREATE"
+ }
},
"core": {
"caption": "General",
@@ -565,7 +576,11 @@
},
"customize": {
"caption": "Customization",
- "description": "Customize your entities"
+ "description": "Customize your entities",
+ "picker": {
+ "header": "Customization",
+ "introduction": "Tweak per-entity attributes. Added/edited customizations will take effect immediately. Removed customizations will take effect when the entity is updated."
+ }
},
"automation": {
"caption": "Automation",
@@ -755,7 +770,27 @@
},
"entity_registry": {
"caption": "Entity Registry",
- "description": "Overview of all known entities."
+ "description": "Overview of all known entities.",
+ "picker": {
+ "header": "Entity Registry",
+ "unavailable": "(unavailable)"
+ },
+ "editor": {
+ "unavailable": "This entity is not currently available.",
+ "default_name": "New Area",
+ "delete": "DELETE",
+ "update": "UPDATE"
+ }
+ },
+ "person": {
+ "caption": "Persons",
+ "description": "Manage the persons that Home Assistant tracks.",
+ "detail": {
+ "name": "Name",
+ "device_tracker_intro": "Select the devices that belong to this person.",
+ "device_tracker_picked": "Track Device",
+ "device_tracker_pick": "Pick device to track"
+ }
},
"integrations": {
"caption": "Integrations",
@@ -774,7 +809,8 @@
"hub": "Connected via",
"firmware": "Firmware: {version}",
"device_unavailable": "device unavailable",
- "entity_unavailable": "entity unavailable"
+ "entity_unavailable": "entity unavailable",
+ "no_area": "No Area"
}
},
"users": {
@@ -847,7 +883,8 @@
"toggle_editor": "Toggle Editor",
"add": "Add Card",
"edit": "Edit",
- "delete": "Delete"
+ "delete": "Delete",
+ "move": "Move"
},
"save_config": {
"header": "Take control of your Lovelace UI",
@@ -956,6 +993,29 @@
"working": "Please wait",
"unknown_error": "Something went wrong",
"providers": {
+ "command_line": {
+ "step": {
+ "init": {
+ "data": {
+ "username": "[%key:ui::panel::page-authorize::form::providers::homeassistant::step::init::data::username%]",
+ "password": "[%key:ui::panel::page-authorize::form::providers::homeassistant::step::init::data::password%]"
+ }
+ },
+ "mfa": {
+ "data": {
+ "code": "[%key:ui::panel::page-authorize::form::providers::homeassistant::step::mfa::data::code%]"
+ },
+ "description": "[%key:ui::panel::page-authorize::form::providers::homeassistant::step::mfa::description%]"
+ }
+ },
+ "error": {
+ "invalid_auth": "[%key:ui::panel::page-authorize::form::providers::homeassistant::error::invalid_auth%]",
+ "invalid_code": "[%key:ui::panel::page-authorize::form::providers::homeassistant::error::invalid_code%]"
+ },
+ "abort": {
+ "login_expired": "[%key:ui::panel::page-authorize::form::providers::homeassistant::abort::login_expired%]"
+ }
+ },
"homeassistant": {
"step": {
"init": {
diff --git a/src/types.ts b/src/types.ts
index e72f40b124..50964a2640 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -29,6 +29,13 @@ declare global {
getComputedStyleValue(element, propertyName);
};
}
+ // for fire event
+ interface HASSDomEvents {
+ "value-changed": {
+ value: unknown;
+ };
+ change: undefined;
+ }
}
export interface WebhookError {
diff --git a/translations/ca.json b/translations/ca.json
index 4e1ac7db7f..ffb3b45dff 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -28,7 +28,7 @@
"disarmed": "Desactivada",
"armed_home": "Activada, mode a casa",
"armed_away": "Activada, mode fora",
- "armed_night": "Activada, mode nit",
+ "armed_night": "Activada, mode nocturn",
"pending": "Pendent",
"arming": "Activant",
"disarming": "Desactivant",
@@ -301,7 +301,8 @@
"period": "Període"
},
"logbook": {
- "showing_entries": "Mostrant entrades de"
+ "showing_entries": "Mostrant entrades de",
+ "period": "Període"
},
"mailbox": {
"empty": "No tens missatges",
@@ -789,10 +790,16 @@
"para_sure": "Estàs segur que vols prendre el control de la interfície d'usuari?",
"cancel": "M'ho he repensat",
"save": "Prendre el control"
+ },
+ "menu": {
+ "raw_editor": "Editor de codi"
}
},
"menu": {
- "configure_ui": "Configurar la interfície d'usuari"
+ "configure_ui": "Configurar la interfície d'usuari",
+ "unused_entities": "Entitats sense utilitzar",
+ "help": "Ajuda",
+ "refresh": "Actualitzar"
}
}
},
@@ -864,7 +871,7 @@
"disarm": "Desactivar",
"arm_home": "Activar, a casa",
"arm_away": "Activar, fora",
- "arm_night": "Activar, nit",
+ "arm_night": "Activar, nocturn",
"armed_custom_bypass": "Bypass personalitzat"
},
"automation": {
@@ -1028,7 +1035,8 @@
"zha": "ZHA",
"hassio": "Hass.io",
"homeassistant": "Home Assistant",
- "lovelace": "Lovelace"
+ "lovelace": "Lovelace",
+ "system_health": "Estat del sistema"
},
"attribute": {
"weather": {
diff --git a/translations/cs.json b/translations/cs.json
index 1aa8ddb202..435ccbdd82 100644
--- a/translations/cs.json
+++ b/translations/cs.json
@@ -427,6 +427,10 @@
"webhook": {
"label": "Webhook",
"webhook_id": "Webhook ID"
+ },
+ "geo_location": {
+ "label": "Geolokace",
+ "zone": "Zóna"
}
}
},
@@ -556,6 +560,12 @@
"device_unavailable": "zařízení není k dispozici",
"entity_unavailable": "entita není k dispozici"
}
+ },
+ "zha": {
+ "caption": "ZHA"
+ },
+ "area_registry": {
+ "description": "Přehled všech oblastí ve vaší domácnosti."
}
},
"profile": {
@@ -758,7 +768,9 @@
}
},
"menu": {
- "configure_ui": "Konfigurovat UI"
+ "configure_ui": "Konfigurovat UI",
+ "help": "Pomoc",
+ "refresh": "Obnovit"
}
}
},
@@ -928,6 +940,18 @@
"save": "Uložit",
"name": "Název",
"entity_id": "Entity ID"
+ },
+ "more_info_control": {
+ "script": {
+ "last_action": "Poslední akce"
+ },
+ "sun": {
+ "rising": "Vychází",
+ "setting": "Zapadá"
+ },
+ "updater": {
+ "title": "Pokyny pro aktualizaci"
+ }
}
},
"auth_store": {
@@ -978,7 +1002,10 @@
"weblink": "Webový odkaz",
"zwave": "Z-Wave",
"vacuum": "Vysavač",
- "zha": "ZHA"
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace"
},
"attribute": {
"weather": {
diff --git a/translations/da.json b/translations/da.json
index c646247b8a..d2bc987dd3 100644
--- a/translations/da.json
+++ b/translations/da.json
@@ -301,7 +301,8 @@
"period": "Periode"
},
"logbook": {
- "showing_entries": "Viser emner for"
+ "showing_entries": "Viser emner for",
+ "period": "Periode"
},
"mailbox": {
"empty": "Du har ingen beskeder",
@@ -420,13 +421,27 @@
"label": "Zone",
"entity": "Enhed med placering",
"zone": "Zone",
- "event": "Begivenhed",
+ "event": "Begivenhed:",
"enter": "Ankom",
"leave": "Forlade"
},
"webhook": {
"label": "Webhook",
"webhook_id": "Webhook-ID"
+ },
+ "time_pattern": {
+ "label": "Tidsmønster",
+ "hours": "Timer",
+ "minutes": "Minutter",
+ "seconds": "Sekunder"
+ },
+ "geo_location": {
+ "label": "Geolokation",
+ "source": "Kilde",
+ "zone": "Zone",
+ "event": "Begivenhed",
+ "enter": "Ankommer",
+ "leave": "Forlad"
}
}
},
@@ -437,7 +452,7 @@
"duplicate": "Kopier",
"delete": "Slet",
"delete_confirm": "Er du sikker på du vil slette?",
- "unsupported_condition": "Ikke-understøttet betingelse: {betingelse}",
+ "unsupported_condition": "Ikke-understøttet betingelse: {condition}",
"type_select": "Betingelsestype",
"type": {
"state": {
@@ -448,7 +463,7 @@
"label": "Numerisk stadie",
"above": "Over",
"below": "Under",
- "value_template": "Værdi skabelon (ikke krævet)"
+ "value_template": "Værdi skabelon (valgfri)"
},
"sun": {
"label": "Sol",
@@ -556,12 +571,27 @@
"device_unavailable": "enhed utilgængelig",
"entity_unavailable": "entitet utilgængelig"
}
+ },
+ "zha": {
+ "caption": "ZHA",
+ "description": "Zigbee Home Automation opsætning",
+ "services": {
+ "reconfigure": "Genkonfigurer ZHA-enhed (helbred enhed). Brug dette hvis du har problemer med enheden. Hvis den pågældende enhed er en batteridrevet enhed skal du sørge for at den er vågen og accepterer kommandoer når du bruger denne service."
+ }
+ },
+ "area_registry": {
+ "caption": "Område registrering",
+ "description": "Oversigt over alle områder i dit hjem."
+ },
+ "entity_registry": {
+ "caption": "Enheds registrering",
+ "description": "Oversigt over alle kendte enheder."
}
},
"profile": {
"push_notifications": {
"header": "Push notifikationer",
- "description": "Send meddelelser til denne enhed",
+ "description": "Send meddelelser til denne enhed.",
"error_load_platform": "Konfigurer notify.html5",
"error_use_https": "Kræver SSL aktiveret til frontend.",
"push_notifications": "Push-meddelelser",
@@ -724,6 +754,11 @@
"checked_items": "Markerede elementer",
"clear_items": "Ryd markerede elementer",
"add_item": "Tilføj element"
+ },
+ "empty_state": {
+ "title": "Velkommen hjem",
+ "no_devices": "Denne side giver dig mulighed for at styre dine enheder, men det ser ud til, at du endnu ikke har konfigureret enheder. Gå til integrationssiden for at komme i gang.",
+ "go_to_integrations_page": "Gå til integrationssiden."
}
},
"editor": {
@@ -755,10 +790,16 @@
"para_sure": "Er du sikker på du ønsker at tage kontrol over din brugergrænseflade?",
"cancel": "Glem det",
"save": "tag kontrol"
+ },
+ "menu": {
+ "raw_editor": "Tekstbaseret redigering"
}
},
"menu": {
- "configure_ui": "Konfigurer UI"
+ "configure_ui": "Konfigurer UI",
+ "unused_entities": "Ubrugte enheder",
+ "help": "Hjælp",
+ "refresh": "Opdater"
}
}
},
@@ -928,6 +969,19 @@
"save": "Gem",
"name": "Navn",
"entity_id": "Enheds ID"
+ },
+ "more_info_control": {
+ "script": {
+ "last_action": "Senest udløst"
+ },
+ "sun": {
+ "elevation": "Elevation",
+ "rising": "Solopgang",
+ "setting": "Indstilling"
+ },
+ "updater": {
+ "title": "Opdateringsvejledning"
+ }
}
},
"auth_store": {
@@ -977,7 +1031,12 @@
"updater": "Opdater",
"weblink": "Link",
"zwave": "Z-Wave",
- "vacuum": "Støvsuger"
+ "vacuum": "Støvsuger",
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "System sundhed"
},
"attribute": {
"weather": {
diff --git a/translations/de.json b/translations/de.json
index f716c40018..e4e371c465 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -301,7 +301,8 @@
"period": "Zeitraum"
},
"logbook": {
- "showing_entries": "Zeige Einträge für"
+ "showing_entries": "Zeige Einträge für",
+ "period": "Zeitraum"
},
"mailbox": {
"empty": "Du hast keine Nachrichten",
@@ -433,6 +434,14 @@
"hours": "Stunden",
"minutes": "Minuten",
"seconds": "Sekunden"
+ },
+ "geo_location": {
+ "label": "Geolokalisierung",
+ "source": "Quelle",
+ "zone": "Zone",
+ "event": "Ereignis:",
+ "enter": "Betreten",
+ "leave": "Verlassen"
}
}
},
@@ -566,6 +575,14 @@
"zha": {
"caption": "ZHA",
"description": "Zigbee Home Automation Netzwerkmanagement"
+ },
+ "area_registry": {
+ "caption": "Gebietsregister",
+ "description": "Überblick über alle Bereiche in Deinem Haus."
+ },
+ "entity_registry": {
+ "caption": "Entitätsregister",
+ "description": "Überblick aller bekannten Elemente."
}
},
"profile": {
@@ -734,6 +751,11 @@
"checked_items": "Markierte Artikel",
"clear_items": "Markierte Elemente löschen",
"add_item": "Artikel hinzufügen"
+ },
+ "empty_state": {
+ "title": "Willkommen zu Hause",
+ "no_devices": "Auf dieser Seite kannst du deine Geräte steuern, es sieht jedoch so aus, als hättest du noch keine eingerichtet. Gehe zur Integrationsseite, um damit zu beginnen.",
+ "go_to_integrations_page": "Gehe zur Integrationsseite."
}
},
"editor": {
@@ -765,10 +787,16 @@
"para_sure": "Bist du dir sicher, dass du die Benutzeroberfläche selbst verwalten möchtest?",
"cancel": "Abbrechen",
"save": "Kontrolle übernehmen"
+ },
+ "menu": {
+ "raw_editor": "Raw-Konfigurationseditor"
}
},
"menu": {
- "configure_ui": "Benutzeroberfläche konfigurieren"
+ "configure_ui": "Benutzeroberfläche konfigurieren",
+ "unused_entities": "Ungenutzte Elemente",
+ "help": "Hilfe",
+ "refresh": "Aktualisieren"
}
}
},
@@ -945,8 +973,8 @@
},
"sun": {
"elevation": "Höhe",
- "rising": "Aufgehend",
- "setting": "Einstellung"
+ "rising": "Aufgang",
+ "setting": "Untergang"
},
"updater": {
"title": "Update-Anweisungen"
@@ -1001,7 +1029,11 @@
"weblink": "Weblink",
"zwave": "Z-Wave",
"vacuum": "Staubsauger",
- "zha": "ZHA"
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Systemzustand"
},
"attribute": {
"weather": {
diff --git a/translations/el.json b/translations/el.json
index 5e5e7b7cb4..1a3cc56a83 100644
--- a/translations/el.json
+++ b/translations/el.json
@@ -301,7 +301,8 @@
"period": "Περίοδος"
},
"logbook": {
- "showing_entries": "Εμφανίζοντα καταχωρήσεις για"
+ "showing_entries": "Εμφανίζοντα καταχωρήσεις για",
+ "period": "Περίοδος"
},
"mailbox": {
"empty": "Δεν έχετε μηνύματα",
@@ -433,6 +434,13 @@
"hours": "Ώρες",
"minutes": "Λεπτά",
"seconds": "Δευτερόλεπτα"
+ },
+ "geo_location": {
+ "label": "Γεωγραφική θέση",
+ "source": "Πηγή",
+ "zone": "Ζώνη",
+ "enter": "Είσοδος",
+ "leave": "Αποχώρηση"
}
}
},
@@ -556,7 +564,7 @@
"no_device": "Οντότητες χωρίς συσκευές",
"delete_confirm": "Είστε σίγουρος ότι θέλετε να διαγραφεί αυτή η ενοποίηση;",
"restart_confirm": "Επανεκκινήστε το Home Assistant για να ολοκληρώσετε την κατάργηση αυτής της ενοποίησης",
- "manuf": "από {κατασκευαστής}",
+ "manuf": "από {manufacturer}",
"hub": "Συνδεδεμένο μέσω",
"firmware": "Υλικολογισμικό: {έκδοση}",
"device_unavailable": "συσκευή μη διαθέσιμη",
@@ -566,6 +574,14 @@
"zha": {
"caption": "ZHA",
"description": "Διαχείριση του δικτύου ZigBee Home Automation"
+ },
+ "area_registry": {
+ "caption": "Περιοχή Μητρώου",
+ "description": "Επισκόπηση όλων των περιοχών στο σπίτι σας."
+ },
+ "entity_registry": {
+ "caption": "Μητρώο οντοτήτων",
+ "description": "Επισκόπηση όλων των γνωστών οντοτήτων."
}
},
"profile": {
@@ -734,6 +750,11 @@
"checked_items": "Επιλεγμένα στοιχεία",
"clear_items": "Εκκαθάριση επιλεγμένων στοιχείων",
"add_item": "Προσθήκη στοιχείου"
+ },
+ "empty_state": {
+ "title": "Καλωσορίσατε στην αρχική σελίδα",
+ "no_devices": "Αυτή η σελίδα σάς επιτρέπει να ελέγχετε τις συσκευές σας, ωστόσο φαίνεται ότι δεν έχετε ακόμα ρυθμίσει συσκευές. Μεταβείτε στη σελίδα ενοποίησης για να ξεκινήσετε.",
+ "go_to_integrations_page": "Μεταβείτε στη σελίδα ενοποίησης."
}
},
"editor": {
@@ -768,7 +789,10 @@
}
},
"menu": {
- "configure_ui": "Διαμορφώστε το περιβάλλον χρήστη"
+ "configure_ui": "Διαμορφώστε το περιβάλλον χρήστη",
+ "unused_entities": "Αχρησιμοποίητες οντότητες",
+ "help": "Βοήθεια",
+ "refresh": "Ανανέωση"
}
}
},
@@ -1001,7 +1025,11 @@
"weblink": "Σύνδεσμος",
"zwave": "Z-Wave",
"vacuum": "Εκκένωση ",
- "zha": "ΖΗΑ"
+ "zha": "ΖΗΑ",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Υγεία Συστήματος"
},
"attribute": {
"weather": {
diff --git a/translations/es-419.json b/translations/es-419.json
index 2ee15f68b6..264bbc98d8 100644
--- a/translations/es-419.json
+++ b/translations/es-419.json
@@ -423,6 +423,17 @@
"event": "Evento:",
"enter": "Entrar",
"leave": "Salir"
+ },
+ "webhook": {
+ "label": "Webhook"
+ },
+ "time_pattern": {
+ "hours": "Horas",
+ "minutes": "Minutos",
+ "seconds": "Hass.io"
+ },
+ "geo_location": {
+ "event": "Evento:"
}
}
},
@@ -552,6 +563,9 @@
"device_unavailable": "dispositivo no disponible",
"entity_unavailable": "entidad no disponible"
}
+ },
+ "zha": {
+ "caption": "ZHA"
}
},
"profile": {
@@ -726,7 +740,8 @@
"edit_card": {
"header": "Configuración de la tarjeta",
"save": "Guardar",
- "toggle_editor": "Cambiar editor"
+ "toggle_editor": "Cambiar editor",
+ "edit": "Editar"
},
"migrate": {
"header": "Configuración inválida",
@@ -734,6 +749,9 @@
"para_migrate": "Home Assistant puede agregar ID a todas sus tarjetas y vistas automáticamente por usted presionando el botón 'Migrar configuración'.",
"migrate": "Migrar configuración"
}
+ },
+ "menu": {
+ "help": "Ayuda"
}
}
},
@@ -951,7 +969,10 @@
"updater": "Actualizador",
"weblink": "Enlace web",
"zwave": "",
- "vacuum": "Aspiradora"
+ "vacuum": "Aspiradora",
+ "zha": "ZHA",
+ "lovelace": "Lovelace",
+ "system_health": "Estado del sistema"
},
"attribute": {
"weather": {
diff --git a/translations/es.json b/translations/es.json
index 977ee77bf4..2f6eb013a7 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -301,7 +301,8 @@
"period": "Periodo"
},
"logbook": {
- "showing_entries": "Mostrando entradas del"
+ "showing_entries": "Mostrando entradas del",
+ "period": "Periodo"
},
"mailbox": {
"empty": "No tiene ningún mensaje",
@@ -433,6 +434,14 @@
"hours": "Horas",
"minutes": "Minutos",
"seconds": "Segundos"
+ },
+ "geo_location": {
+ "label": "Geolocalización",
+ "source": "Fuente",
+ "zone": "Zona",
+ "event": "Evento:",
+ "enter": "Entrar",
+ "leave": "Salir"
}
}
},
@@ -565,7 +574,18 @@
},
"zha": {
"caption": "ZHA",
- "description": "Gestión de red de Zigbee Home Automation"
+ "description": "Gestión de red de Zigbee Home Automation",
+ "services": {
+ "reconfigure": "Reconfigura el dispositivo ZHA (curar dispositivo). Usa esto si tienes problemas con el dispositivo. Si el dispositivo en cuestión es un dispositivo alimentado por batería, asegurate de que está activo y aceptando comandos cuando uses este servicio."
+ }
+ },
+ "area_registry": {
+ "caption": "Registro de área",
+ "description": "Visión general de todas las áreas de tu casa."
+ },
+ "entity_registry": {
+ "caption": "Registro de Entidades",
+ "description": "Resumen de todas las entidades conocidas."
}
},
"profile": {
@@ -734,6 +754,11 @@
"checked_items": "Elementos marcados",
"clear_items": "Borrar elementos marcados",
"add_item": "Añadir artículo"
+ },
+ "empty_state": {
+ "title": "Bienvenido a casa",
+ "no_devices": "Esta página te permite controlar tus dispositivos, aunque parece que aún no has configurado ninguno. Dirígete a la página de integraciones para empezar.",
+ "go_to_integrations_page": "Ir a la página de integraciones."
}
},
"editor": {
@@ -765,10 +790,16 @@
"para_sure": "¿Está seguro de que desea tomar el control de su interfaz de usuario?",
"cancel": "No importa",
"save": "Tomar el control"
+ },
+ "menu": {
+ "raw_editor": "Editor de configuración en bruto"
}
},
"menu": {
- "configure_ui": "Configurar la interfaz de usuario"
+ "configure_ui": "Configurar la interfaz de usuario",
+ "unused_entities": "Entidades no utilizadas",
+ "help": "Ayuda",
+ "refresh": "Refrescar"
}
}
},
@@ -1001,7 +1032,11 @@
"weblink": "Enlace web",
"zwave": "Z-Wave",
"vacuum": "Aspiradora",
- "zha": "ZHA"
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Salud del sistema"
},
"attribute": {
"weather": {
diff --git a/translations/fr.json b/translations/fr.json
index 07d65addcb..3e93ff16a6 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -301,7 +301,8 @@
"period": "Période"
},
"logbook": {
- "showing_entries": "Afficher les entrées pour le"
+ "showing_entries": "Afficher les entrées pour le",
+ "period": "Période"
},
"mailbox": {
"empty": "Vous n'avez aucun message",
@@ -433,6 +434,14 @@
"hours": "Heures",
"minutes": "Minutes",
"seconds": "Secondes"
+ },
+ "geo_location": {
+ "label": "Géolocalisation",
+ "source": "Source",
+ "zone": "Zone",
+ "event": "Événement :",
+ "enter": "Entre",
+ "leave": "Quitte"
}
}
},
@@ -562,6 +571,21 @@
"device_unavailable": "appareil indisponible",
"entity_unavailable": "entité indisponible"
}
+ },
+ "zha": {
+ "caption": "ZHA",
+ "description": "Gestion de réseau domotique ZigBee",
+ "services": {
+ "reconfigure": "Reconfigurer le périphérique ZHA. Utilisez cette option si vous rencontrez des problèmes avec le périphérique. Si l'appareil en question est un appareil alimenté par batterie, assurez-vous qu'il soit allumé et qu'il accepte les commandes lorsque vous utilisez ce service."
+ }
+ },
+ "area_registry": {
+ "caption": "Registre des pièces",
+ "description": "Vue d'ensemble de toutes les pièces de votre maison."
+ },
+ "entity_registry": {
+ "caption": "Registre des entités",
+ "description": "Vue d'ensemble de toutes les entités connues."
}
},
"profile": {
@@ -730,6 +754,11 @@
"checked_items": "Éléments cochés",
"clear_items": "Effacer éléments cochés",
"add_item": "Ajouter un élément"
+ },
+ "empty_state": {
+ "title": "Bienvenue à la maison",
+ "no_devices": "Cette page vous permet de contrôler vos périphériques. Toutefois, il semble que vous n’ayez pas encore configuré de périphériques. Rendez-vous sur la page des intégrations pour commencer.",
+ "go_to_integrations_page": "Aller à la page des intégrations."
}
},
"editor": {
@@ -761,10 +790,16 @@
"para_sure": "Êtes-vous sûr de vouloir prendre le controle de l'interface utilisateur?",
"cancel": "Oublie ce que j'ai dit, c'est pas grave.",
"save": "Prenez le contrôle"
+ },
+ "menu": {
+ "raw_editor": "Éditeur de configuration"
}
},
"menu": {
- "configure_ui": "Configurer l'interface utilisateur"
+ "configure_ui": "Configurer l'interface utilisateur",
+ "unused_entities": "Entités inutilisées",
+ "help": "Aide",
+ "refresh": "Actualiser"
}
}
},
@@ -941,7 +976,8 @@
},
"sun": {
"elevation": "Élévation",
- "rising": "Lever"
+ "rising": "Lever",
+ "setting": "Coucher"
},
"updater": {
"title": "Instructions de mise à jour"
@@ -995,7 +1031,12 @@
"updater": "Mise à jour",
"weblink": "Lien",
"zwave": "Z-Wave",
- "vacuum": "Aspirateur"
+ "vacuum": "Aspirateur",
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Santé du système"
},
"attribute": {
"weather": {
diff --git a/translations/he.json b/translations/he.json
index 204ca0327c..362981d2e2 100644
--- a/translations/he.json
+++ b/translations/he.json
@@ -353,20 +353,20 @@
"description": "צור וערוך אוטומציות",
"picker": {
"header": "עורך אוטומציה",
- "introduction": "עורך אוטומציה מאפשר לך ליצור ולערוך אוטומציה. אנא קרא את [ההוראות] (https:\/\/home-assistant.io\/docs\/automation\/editor\/) כדי לוודא שהגדרת את ה - Home Assistant כהלכה.",
+ "introduction": "עורך אוטומציה מאפשר לך ליצור ולערוך אוטומציות. אנא קרא את [ההוראות](https:\/\/home-assistant.io\/docs\/automation\/editor\/) כדי לוודא שהגדרת את ה - Home Assistant כהלכה.",
"pick_automation": "בחר אוטומציה לעריכה",
"no_automations": "לא הצלחנו למצוא שום אוטומציה הניתנת לעריכה",
"add_automation": "הוסף אוטומציה"
},
"editor": {
- "introduction": "השתמש אוטומציות להביא את החיים לבית שלך",
+ "introduction": "השתמש באוטומציות להביא את הבית שלך לחיים",
"default_name": "אוטומציה חדשה",
"save": "שמור",
"unsaved_confirm": "יש לך שינויים שלא נשמרו. אתה בטוח שאתה רוצה לעזוב?",
"alias": "שם",
"triggers": {
"header": "טריגרים",
- "introduction": "טריגרים הם מה שמתחיל כל אוטומציה. ניתן לציין מספר טריגרים עבור אותו כלל. לאחר הפעלת טריגר, ה - Assistant Assistant יאמת את התנאים, אם קיימים, ויקרא לפעולה. \n\n [למידע נוסף על גורמים טריגרים.) (Https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
+ "introduction": "טריגרים הם מה שמתחיל כל אוטומציה. ניתן לציין מספר טריגרים עבור אותו כלל. לאחר הפעלת טריגר, ה - Home Assistant יאמת את התנאים, אם קיימים, ויקרא לפעולה. \n\n[למד עוד על טריגרים](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
"add": "הוספת טריגר",
"duplicate": "שכפל",
"delete": "מחק",
@@ -447,7 +447,7 @@
},
"conditions": {
"header": "תנאים",
- "introduction": "התנאים הם חלק אופציונלי של כלל אוטומציה, וניתן להשתמש בהם כדי למנוע פעולה כלשהי בעת הפעלתה. התנאים נראים דומים מאוד לטריגרים אך הם שונים מאוד. הטריגר יסתכל על האירועים המתרחשים במערכת בעוד תנאי רק מסתכל על איך המערכת נראית עכשיו. הטריגר יכול שמתג נדלק. תנאי יכול לראות רק אם מתג מופעל או כבוי. \n\n [למידע נוסף על תנאים.] (Https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)",
+ "introduction": "התנאים הם חלק אופציונלי של כלל אוטומציה, וניתן להשתמש בהם כדי למנוע פעולה כלשהי בעת הפעלתה. התנאים נראים דומים מאוד לטריגרים אך הם שונים מאוד. הטריגר יסתכל על האירועים המתרחשים במערכת בעוד תנאי רק מסתכל על איך המערכת נראית עכשיו. הטריגר יכול שמתג נדלק. תנאי יכול לראות רק אם מתג מופעל או כבוי. \n\n[למידע נוסף על תנאים](Https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)",
"add": "הוסף תנאי",
"duplicate": "שכפל",
"delete": "מחק",
@@ -492,7 +492,7 @@
},
"actions": {
"header": "פעולות",
- "introduction": "הפעולות הן מה שHome Assistant יעשה כאשר אוטומציה מופעלת. \n\n [למידע נוסף על פעולות.] (https:\/\/home-assistant.io\/docs\/automation\/action\/)",
+ "introduction": "הפעולות הן מה שHome Assistant יעשה כאשר אוטומציה מופעלת. \n\n[למידע נוסף על פעולות](https:\/\/home-assistant.io\/docs\/automation\/action\/)",
"add": "הוסף פעולה",
"duplicate": "שכפל",
"delete": "מחק",
@@ -506,7 +506,7 @@
},
"delay": {
"label": "עיכוב",
- "delay": "עיקוב"
+ "delay": "עיכוב"
},
"wait_template": {
"label": "לחכות",
@@ -517,7 +517,7 @@
"label": "תנאי"
},
"event": {
- "label": "אירוע אש",
+ "label": "ירה אירוע",
"event": "ארוע",
"service_data": "נתוני שירות"
}
@@ -557,7 +557,7 @@
"description": "ניהול התקנים ושירותים מחוברים",
"discovered": "זוהו",
"configured": "הוגדר",
- "new": "הגדר אינטגריצה",
+ "new": "הגדר אינטגרציה",
"configure": "הגדר",
"none": "כלום אינו הוגדר עדיין",
"config_entry": {
diff --git a/translations/hu.json b/translations/hu.json
index 7f99f3fcf0..e3bc5bbf51 100644
--- a/translations/hu.json
+++ b/translations/hu.json
@@ -576,10 +576,12 @@
"description": "Zigbee Home Automation hálózat menedzsment"
},
"area_registry": {
- "description": "Az összes otthoni terület áttekintése."
+ "caption": "Terület Nyilvántartás",
+ "description": "Az összes otthoni terület áttekintése"
},
"entity_registry": {
- "description": "Az összes ismert entitás áttekintése."
+ "caption": "Entitás Nyilvántartás",
+ "description": "Az összes ismert entitás áttekintése"
}
},
"profile": {
@@ -750,7 +752,9 @@
"add_item": "Tétel hozzáadása"
},
"empty_state": {
- "title": "Üdv Itthon"
+ "title": "Üdv Itthon",
+ "no_devices": "Ez az oldal lehetővé teszi az eszközeid vezérlését, de úgy tűnik, hogy még nincs beállítva egy sem. A kezdéshez lépj át az integrációs oldalra.",
+ "go_to_integrations_page": "Ugrás az 'integrációk' oldalra."
}
},
"editor": {
@@ -782,6 +786,9 @@
"para_sure": "Biztosan át szeretnéd venni az irányítást a felhasználói felületed felett?",
"cancel": "Mégsem",
"save": "Irányítás átvétele"
+ },
+ "menu": {
+ "raw_editor": "Konfiguráció szerkesztő"
}
},
"menu": {
@@ -956,7 +963,7 @@
"dialogs": {
"more_info_settings": {
"save": "Mentés",
- "name": "Név",
+ "name": "Név felülbírálása",
"entity_id": "Entitás ID"
},
"more_info_control": {
@@ -1024,7 +1031,8 @@
"zha": "ZHA",
"hassio": "Hass.io",
"homeassistant": "Home Assistant",
- "lovelace": "Lovelace"
+ "lovelace": "Lovelace",
+ "system_health": "Rendszer Állapot"
},
"attribute": {
"weather": {
diff --git a/translations/it.json b/translations/it.json
index 91f0762f5f..abba80f194 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -301,7 +301,8 @@
"period": "Periodo"
},
"logbook": {
- "showing_entries": "Mostra registrazioni per"
+ "showing_entries": "Mostra registrazioni per",
+ "period": "Periodo"
},
"mailbox": {
"empty": "Non hai nessun messaggio",
@@ -426,13 +427,21 @@
},
"webhook": {
"label": "Webhook",
- "webhook_id": "ID Webhook"
+ "webhook_id": "Webhook ID"
},
"time_pattern": {
"label": "Pattern temporale",
"hours": "Ore",
"minutes": "Minuti",
"seconds": "Secondi"
+ },
+ "geo_location": {
+ "label": "Geolocalizzazione",
+ "source": "Fonte",
+ "zone": "Zona",
+ "event": "Evento:",
+ "enter": "Ingresso",
+ "leave": "Uscita"
}
}
},
@@ -544,7 +553,7 @@
"description_not_login": "Accesso non effettuato"
},
"integrations": {
- "caption": "integrazioni",
+ "caption": "Integrazioni",
"description": "Gestisci dispositivi e servizi connessi",
"discovered": "Scoperto",
"configured": "Configurato",
@@ -565,7 +574,18 @@
},
"zha": {
"caption": "ZHA",
- "description": "Gestione rete Zigbee Home Automation"
+ "description": "Gestione rete Zigbee Home Automation",
+ "services": {
+ "reconfigure": "Riconfigurare il dispositivo ZHA (dispositivo di guarigione). Utilizzare questa opzione se si verificano problemi con il dispositivo. Se il dispositivo in questione è un dispositivo alimentato a batteria, assicurarsi che sia sveglio e che accetti i comandi quando si utilizza questo servizio."
+ }
+ },
+ "area_registry": {
+ "caption": "Registro di area",
+ "description": "Panoramica di tutte le aree della tua casa."
+ },
+ "entity_registry": {
+ "caption": "Registro delle entità",
+ "description": "Panoramica di tutte le entità conosciute."
}
},
"profile": {
@@ -702,7 +722,7 @@
"data": {
"user": "Utente"
},
- "description": "Perfavore, scegli l'utente con cui vuoi effettuare l'accesso:"
+ "description": "Per favore, scegli l'utente con cui vuoi effettuare l'accesso:"
}
},
"abort": {
@@ -734,6 +754,11 @@
"checked_items": "Elementi selezionati",
"clear_items": "Cancella gli elementi selezionati",
"add_item": "Aggiungi elemento"
+ },
+ "empty_state": {
+ "title": "Benvenuto a casa",
+ "no_devices": "Questa pagina ti consente di controllare i tuoi dispositivi, tuttavia sembra che tu non abbia ancora configurato uno. Vai alla pagina delle integrazioni per iniziare.",
+ "go_to_integrations_page": "Vai alla pagina delle integrazioni."
}
},
"editor": {
@@ -765,10 +790,16 @@
"para_sure": "Sei sicuro di voler prendere il controllo della tua interfaccia utente?",
"cancel": "Rinuncia",
"save": "Prendere il controllo"
+ },
+ "menu": {
+ "raw_editor": "Editor di configurazione grezzo"
}
},
"menu": {
- "configure_ui": "Configurare l'interfaccia utente"
+ "configure_ui": "Configurare l'interfaccia utente",
+ "unused_entities": "Entità non utilizzate",
+ "help": "Aiuto",
+ "refresh": "Aggiorna"
}
}
},
@@ -936,7 +967,7 @@
"dialogs": {
"more_info_settings": {
"save": "Salva",
- "name": "Nome",
+ "name": "Sovrascrittura del nome",
"entity_id": "ID Entità"
},
"more_info_control": {
@@ -1001,7 +1032,11 @@
"weblink": "Link Web",
"zwave": "Z-Wave",
"vacuum": "Aspirapolvere",
- "zha": "ZHA"
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Salute del sistema"
},
"attribute": {
"weather": {
diff --git a/translations/ko.json b/translations/ko.json
index 1d3acfe9e4..95e3a7a126 100644
--- a/translations/ko.json
+++ b/translations/ko.json
@@ -312,7 +312,7 @@
},
"config": {
"header": "Home Assistant 설정",
- "introduction": "여기에서 구성요소와 Home Assistant를 설정 할 수 있습니다. 아직 여기서 모두 설정 할 수는 없지만, 곧 구현되도록 작업 중입니다",
+ "introduction": "여기에서 구성요소와 Home Assistant 를 설정 할 수 있습니다. 아직 여기서 모두 설정 할 수는 없지만, 곧 구현되도록 작업 중입니다",
"core": {
"caption": "일반",
"description": "설정 파일의 유효성을 검사하고 서버를 제어합니다",
@@ -329,7 +329,7 @@
},
"reloading": {
"heading": "설정 새로고침",
- "introduction": "Home Assistant의 일부 설정은 재시작 없이 다시 읽어들일 수 있습니다. 새로고침을 누르면 현재 구성을 내리고 새로운 설정 내용을 읽어들입니다",
+ "introduction": "Home Assistant 의 일부 설정은 재시작 없이 다시 읽어들일 수 있습니다. 새로고침을 누르면 현재 구성을 내리고 새로운 설정 내용을 읽어들입니다",
"core": "코어 새로고침",
"group": "그룹 새로고침",
"automation": "자동화 새로고침",
@@ -366,7 +366,7 @@
"alias": "이름",
"triggers": {
"header": "트리거",
- "introduction": "트리거는 자동화 규칙을 처리하는 시작점 입니다. 같은 자동화 규칙에 여러 개의 트리거를 지정할 수 있습니다. 트리거가 발동되면 Home Assistant는 조건을 확인하고 동작을 호출합니다. \n\n[트리거에 대해 자세히 알아보기](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
+ "introduction": "트리거는 자동화 규칙을 처리하는 시작점 입니다. 같은 자동화 규칙에 여러 개의 트리거를 지정할 수 있습니다. 트리거가 발동되면 Home Assistant 는 조건을 확인하고 동작을 호출합니다. \n\n[트리거에 대해 자세히 알아보기](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
"add": "트리거 추가",
"duplicate": "복제",
"delete": "삭제",
@@ -492,7 +492,7 @@
},
"actions": {
"header": "동작",
- "introduction": "동작은 자동화가 트리거 될 때 Home Assistant가 수행할 작업입니다.\n\n [동작에 대해 자세히 알아보기](https:\/\/home-assistant.io\/docs\/automation\/action\/)",
+ "introduction": "동작은 자동화가 트리거 될 때 Home Assistant 가 수행할 작업입니다.\n\n [동작에 대해 자세히 알아보기](https:\/\/home-assistant.io\/docs\/automation\/action\/)",
"add": "동작 추가",
"duplicate": "복제",
"delete": "삭제",
@@ -561,7 +561,7 @@
"configure": "설정하기",
"none": "설정된 구성요소가 없습니다",
"config_entry": {
- "no_devices": "이 통합 구성요소에는 장치가 없습니다.",
+ "no_devices": "이 통합 구성요소는 설정해야 할 장치가 없습니다.",
"no_device": "장치가 없는 구성요소",
"delete_confirm": "이 통합 구성요소를 제거 하시겠습니까?",
"restart_confirm": "통합 구성요소 제거 완료를 위해 Home Assistant 를 재시작합니다",
diff --git a/translations/lb.json b/translations/lb.json
index 561a143457..ed17fcbb04 100644
--- a/translations/lb.json
+++ b/translations/lb.json
@@ -301,7 +301,8 @@
"period": "Zäitraum"
},
"logbook": {
- "showing_entries": "Weist Beiträg fir"
+ "showing_entries": "Weist Beiträg fir",
+ "period": "Zäitraum"
},
"mailbox": {
"empty": "Dir hutt keng Noriicht",
@@ -317,7 +318,7 @@
"description": "Konfiguratioun validéieren an de Server kontrolléieren",
"section": {
"core": {
- "header": "Konfiguracja i konktrola serwera",
+ "header": "Konfiguratioun an Server Kontroll",
"introduction": "D'Ännere vun der Konfiguratioun kann e lästege Prozess sinn. Mir wëssen dat. Dës Sektioun probéiert fir Äert Liewen e bësse méi einfach ze maachen.",
"validation": {
"heading": "Validatioun vun der Konfiguratioun",
@@ -757,7 +758,7 @@
"empty_state": {
"title": "Wëllkomm Doheem",
"no_devices": "Dës Säit erlaabt et iech är Apparater ze kontrolléiere, awer wéi et schéngt sinn nach keng Apparater ageriicht. Gitt op d'Integratioun's Säit fir unzefänken.",
- "go_to_integrations_page": "Zur Integratiouns Säit goen"
+ "go_to_integrations_page": "Zur Integratiouns Säit goen"
}
},
"editor": {
@@ -792,7 +793,10 @@
}
},
"menu": {
- "configure_ui": "UI konfiguréieren"
+ "configure_ui": "UI konfiguréieren",
+ "unused_entities": "Onbenotzt Entitéiten",
+ "help": "Hëllef",
+ "refresh": "Erneieren"
}
}
},
@@ -1028,7 +1032,8 @@
"zha": "ZHA",
"hassio": "Hass.io",
"homeassistant": "Home Assistant",
- "lovelace": "Lovelace"
+ "lovelace": "Lovelace",
+ "system_health": "System Zoustand"
},
"attribute": {
"weather": {
diff --git a/translations/lt.json b/translations/lt.json
index 9174cf8938..b12f36e64e 100644
--- a/translations/lt.json
+++ b/translations/lt.json
@@ -61,11 +61,27 @@
}
}
}
+ },
+ "lovelace": {
+ "cards": {
+ "empty_state": {
+ "go_to_integrations_page": "Į integracijų puslapį"
+ }
+ },
+ "menu": {
+ "unused_entities": "Nepanaudoti elementai",
+ "help": "Pagalba",
+ "refresh": "Atnaujinti"
+ }
+ },
+ "logbook": {
+ "period": "Laikotarpis"
}
}
},
"domain": {
- "zwave": "Z-Wave"
+ "zwave": "Z-Wave",
+ "system_health": "Sistemos sveikata"
},
"state": {
"climate": {
diff --git a/translations/nl.json b/translations/nl.json
index 5b3ec44043..09f08dfc3d 100644
--- a/translations/nl.json
+++ b/translations/nl.json
@@ -301,7 +301,8 @@
"period": "Periode"
},
"logbook": {
- "showing_entries": "Toont gegevens van"
+ "showing_entries": "Toont gegevens van",
+ "period": "Periode"
},
"mailbox": {
"empty": "Je hebt geen berichten",
@@ -433,6 +434,14 @@
"hours": "Uren",
"minutes": "Minuten",
"seconds": "Seconden"
+ },
+ "geo_location": {
+ "label": "Geolocatie",
+ "source": "Bron",
+ "zone": "Zone",
+ "event": "Gebeurtenis:",
+ "enter": "Invoeren",
+ "leave": "Verlaten"
}
}
},
@@ -565,7 +574,18 @@
},
"zha": {
"caption": "ZHA",
- "description": "Zigbee Home Automation netwerkbeheer"
+ "description": "Zigbee Home Automation netwerkbeheer",
+ "services": {
+ "reconfigure": "Herconfigureer het ZHA-apparaat (heal device). Gebruik dit als je problemen hebt met het apparaat. Als het een apparaat met batterij is, zorg dan dat het wakker is en commando's accepteert wanneer je deze service gebruikt."
+ }
+ },
+ "area_registry": {
+ "caption": "Gebiedenregister",
+ "description": "Overzicht van alle gebieden in je huis."
+ },
+ "entity_registry": {
+ "caption": "Entiteiten register",
+ "description": "Overzicht van alle gekende entiteiten"
}
},
"profile": {
@@ -734,6 +754,11 @@
"checked_items": "Geselecteerde items",
"clear_items": "Geselecteerde items wissen",
"add_item": "Item toevoegen"
+ },
+ "empty_state": {
+ "title": "Welkom thuis",
+ "no_devices": "Op deze pagina kun je je apparaten bedienen, maar het lijkt er op dat je nog geen apparaten hebt ingesteld. Ga naar de integraties-pagina om te beginnen.",
+ "go_to_integrations_page": "Ga naar de integraties-pagina."
}
},
"editor": {
@@ -765,10 +790,16 @@
"para_sure": "Weet je zeker dat je de controle wilt over je gebruikersinterface?",
"cancel": "Laat maar",
"save": "Neem over"
+ },
+ "menu": {
+ "raw_editor": "Platte configuratie-editor"
}
},
"menu": {
- "configure_ui": "Configureer UI"
+ "configure_ui": "Configureer UI",
+ "unused_entities": "Ongebruikte entiteiten",
+ "help": "Help",
+ "refresh": "Vernieuwen"
}
}
},
@@ -936,7 +967,7 @@
"dialogs": {
"more_info_settings": {
"save": "Opslaan",
- "name": "Naam",
+ "name": "Naam overschrijven",
"entity_id": "Entiteits-ID"
},
"more_info_control": {
@@ -1001,7 +1032,11 @@
"weblink": "Web link",
"zwave": "Z-Wave",
"vacuum": "Stofzuigen",
- "zha": "ZHA"
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Systeemstatus"
},
"attribute": {
"weather": {
diff --git a/translations/pl.json b/translations/pl.json
index a34e0e4dd9..680d81c343 100644
--- a/translations/pl.json
+++ b/translations/pl.json
@@ -301,7 +301,8 @@
"period": "Okres"
},
"logbook": {
- "showing_entries": "Wyświetlanie rekordów dla"
+ "showing_entries": "Wyświetlanie rekordów dla",
+ "period": "Okres"
},
"mailbox": {
"empty": "Nie masz żadnych wiadomości",
@@ -789,10 +790,16 @@
"para_sure": "Czy na pewno chcesz przejąć kontrolę nad interfejsem użytkownika?",
"cancel": "Nieważne",
"save": "Przejmuję kontrolę"
+ },
+ "menu": {
+ "raw_editor": "Ręczny edytor konfiguracji"
}
},
"menu": {
- "configure_ui": "Konfiguracja interfejsu użytkownika"
+ "configure_ui": "Konfiguracja interfejsu użytkownika",
+ "unused_entities": "Nieużywane encje",
+ "help": "Pomoc",
+ "refresh": "Odśwież"
}
}
},
@@ -1028,7 +1035,8 @@
"zha": "ZHA",
"hassio": "Hass.io",
"homeassistant": "Home Assistant",
- "lovelace": "Lovelace"
+ "lovelace": "Lovelace",
+ "system_health": "Kondycja systemu"
},
"attribute": {
"weather": {
diff --git a/translations/pt.json b/translations/pt.json
index 9a47c977c2..fca1749d38 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -301,7 +301,8 @@
"period": "Período"
},
"logbook": {
- "showing_entries": "Mostrar valores para"
+ "showing_entries": "Mostrar valores para",
+ "period": "Período"
},
"mailbox": {
"empty": "Não tem qualquer mensagem nova",
@@ -425,8 +426,22 @@
"leave": "Sair"
},
"webhook": {
- "label": "",
- "webhook_id": ""
+ "label": "Webhook ID",
+ "webhook_id": "Webhook ID"
+ },
+ "time_pattern": {
+ "label": "Padrão de tempo",
+ "hours": "Horas",
+ "minutes": "Minutos",
+ "seconds": "Segundos"
+ },
+ "geo_location": {
+ "label": "Geolocalização",
+ "source": "Fonte",
+ "zone": "Zona",
+ "event": "Evento:",
+ "enter": "Entrar",
+ "leave": "Sair"
}
}
},
@@ -556,6 +571,17 @@
"device_unavailable": "Dispositivo indisponível",
"entity_unavailable": "Entidade indisponível"
}
+ },
+ "zha": {
+ "caption": "ZHA"
+ },
+ "area_registry": {
+ "caption": "Registo de áreas",
+ "description": "Visão geral de todas as áreas da sua casa."
+ },
+ "entity_registry": {
+ "caption": "Registo de Entidades",
+ "description": "Visão geral de todas as entidades conhecidas."
}
},
"profile": {
@@ -724,6 +750,10 @@
"checked_items": "Itens marcados",
"clear_items": "Limpar itens marcados",
"add_item": "Adicionar Item"
+ },
+ "empty_state": {
+ "title": "Bem-vindo a casa",
+ "go_to_integrations_page": "Ir para a página das integrações."
}
},
"editor": {
@@ -758,7 +788,10 @@
}
},
"menu": {
- "configure_ui": "Configurar UI"
+ "configure_ui": "Configurar UI",
+ "unused_entities": "Entidades não utilizadas",
+ "help": "Ajuda",
+ "refresh": "Atualizar"
}
}
},
@@ -928,6 +961,19 @@
"save": "Salvar",
"name": "Nome",
"entity_id": "ID da entidade"
+ },
+ "more_info_control": {
+ "script": {
+ "last_action": "Última ação"
+ },
+ "sun": {
+ "elevation": "Elevação",
+ "rising": "Nascer do sol",
+ "setting": "Por do sol"
+ },
+ "updater": {
+ "title": "Instruções de atualização"
+ }
}
},
"auth_store": {
@@ -977,7 +1023,12 @@
"updater": "Atualizador",
"weblink": "Weblink",
"zwave": "Z-Wave",
- "vacuum": "Aspiração"
+ "vacuum": "Aspiração",
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Integridade Do Sistema"
},
"attribute": {
"weather": {
diff --git a/translations/ru.json b/translations/ru.json
index 48d5b93d0a..bc5e3a0161 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -967,7 +967,7 @@
"dialogs": {
"more_info_settings": {
"save": "Сохранить",
- "name": "Название",
+ "name": "Переопределение имени",
"entity_id": "ID объекта"
},
"more_info_control": {
diff --git a/translations/sk.json b/translations/sk.json
index 33d9fb0495..12eb45a830 100644
--- a/translations/sk.json
+++ b/translations/sk.json
@@ -301,7 +301,8 @@
"period": "Obdobie"
},
"logbook": {
- "showing_entries": "Zobrazujú sa záznamy za obdobie"
+ "showing_entries": "Zobrazujú sa záznamy za obdobie",
+ "period": "Obdobie"
},
"mailbox": {
"empty": "Nemáte žiadne správy",
@@ -433,6 +434,13 @@
"hours": "Hodín",
"minutes": "Minút",
"seconds": "Sekúnd"
+ },
+ "geo_location": {
+ "label": "Geolokácia",
+ "source": "Zdroj",
+ "zone": "Zóna",
+ "event": "Udalosť:",
+ "leave": "Opustiť"
}
}
},
@@ -566,6 +574,14 @@
"zha": {
"caption": "ZHA",
"description": "Správa siete ZigBee Home Automation"
+ },
+ "area_registry": {
+ "caption": "Register oblastí",
+ "description": "Prehľad všetkých oblastí vo vašej domácnosti."
+ },
+ "entity_registry": {
+ "caption": "Register entít",
+ "description": "Prehľad všetkých známych entít."
}
},
"profile": {
@@ -593,7 +609,7 @@
"description": "Každý obnovovací token predstavuje prihlásenú reláciu. Obnovovacie tokeny sa po kliknutí na tlačidlo Odhlásiť sa automaticky odstránia. Pre váš účet sú aktívne nasledovné obnovovacie tokeny.",
"token_title": "Obnovovací token pre {clientId}",
"created_at": "Vytvorený {date}",
- "confirm_delete": "Naozaj chcete odstrániť obnovovací token pre {name} ?",
+ "confirm_delete": "Naozaj chcete odstrániť obnovovací token pre {name}?",
"delete_failed": "Nepodarilo sa odstrániť obnovovací token",
"last_used": "Naposledy použitý {date} z {location}",
"not_used": "Nikdy nebol použitý",
@@ -604,7 +620,7 @@
"description": "Vytvorte prístupové tokeny s dlhou životnosťou, ktoré umožnia vašim skriptom komunikovať s vašou inštanciou Home Assistant. Každý token bude platný 10 rokov od vytvorenia. Nasledujúce prístupové tokeny s dlhou životnosťou sú v súčasnosti aktívne.",
"learn_auth_requests": "Zistite, ako vytvárať overené požiadavky.",
"created_at": "Vytvorený {date}",
- "confirm_delete": "Naozaj chcete odstrániť prístupový token pre {name} ?",
+ "confirm_delete": "Naozaj chcete odstrániť prístupový token pre {name}?",
"delete_failed": "Nepodarilo sa odstrániť prístupový token.",
"create": "Vytvoriť Token",
"create_failed": "Nepodarilo sa vytvoriť prístupový token.",
@@ -614,7 +630,7 @@
"last_used": "Naposledy použitý {date} z {location}",
"not_used": "Nikdy nebol použitý"
},
- "current_user": "Momentálne ste prihlásení ako {fullName} .",
+ "current_user": "Momentálne ste prihlásení ako {fullName}.",
"is_owner": "Ste vlastníkom.",
"logout": "Odhlásiť sa",
"change_password": {
@@ -629,7 +645,7 @@
"header": "Multifaktorové autentifikačné moduly",
"disable": "Zakázať",
"enable": "Povoliť",
- "confirm_disable": "Naozaj chcete zakázať {name} ?"
+ "confirm_disable": "Naozaj chcete zakázať {name}?"
},
"mfa_setup": {
"title_aborted": "Prerušené",
@@ -642,7 +658,7 @@
"page-authorize": {
"initializing": "Inicializácia",
"authorizing_client": "Chystáte sa poskytnúť {clientId} prístup k vašej inštancii Home Assistantu.",
- "logging_in_with": "Prihlasovanie pomocou ** {authProviderName} **.",
+ "logging_in_with": "Prihlasovanie pomocou **{authProviderName}**.",
"pick_auth_provider": "Alebo sa prihláste prostredníctvom",
"abort_intro": "Prihlásenie bolo zrušené",
"form": {
@@ -661,7 +677,7 @@
"data": {
"code": "Kód dvojfaktorovej autentifikácie"
},
- "description": "Otvorte ** {mfa_module_name} ** v zariadení, aby ste si pozreli svoj dvojfaktorový autentifikačný kód a overili svoju totožnosť:"
+ "description": "Otvorte **{mfa_module_name}** v zariadení, aby ste si pozreli svoj dvojfaktorový autentifikačný kód a overili svoju totožnosť:"
}
},
"error": {
@@ -734,6 +750,10 @@
"checked_items": "Označené položky",
"clear_items": "Vymazať označené položky",
"add_item": "Pridať položku"
+ },
+ "empty_state": {
+ "title": "Vitajte doma",
+ "go_to_integrations_page": "Prejsť na stránku integrácií"
}
},
"editor": {
@@ -763,12 +783,15 @@
"header": "Prevziať kontrolu rozhrania Lovelace",
"para": "Štandardne bude Home Assistant udržiavať vaše používateľské rozhranie a aktualizovať ho, keď budú k dispozícii nové entity alebo komponenty rozhrania Lovelace. Ak prevezmete kontrolu, už za vás nebudeme automaticky robiť zmeny.",
"para_sure": "Skutočne chcete prevziať kontrolu vášho používateľského rozhrania?",
- "cancel": "Nevadí",
+ "cancel": "Zrušiť",
"save": "Prevziať kontrolu"
}
},
"menu": {
- "configure_ui": "Konfigurovať používateľské rozhranie"
+ "configure_ui": "Konfigurovať používateľské rozhranie",
+ "unused_entities": "Nepoužité entity",
+ "help": "Pomoc",
+ "refresh": "Obnoviť"
}
}
},
@@ -1001,7 +1024,10 @@
"weblink": "Webový odkaz",
"zwave": "Z-Wave",
"vacuum": "Vysávač",
- "zha": "ZHA"
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace"
},
"attribute": {
"weather": {
diff --git a/translations/uk.json b/translations/uk.json
index 2da28f10ed..37bb91250f 100644
--- a/translations/uk.json
+++ b/translations/uk.json
@@ -257,7 +257,14 @@
"windy-variant": "Вітряно"
},
"vacuum": {
- "idle": "Очікування"
+ "cleaning": "Прибирання",
+ "docked": "Пристиковано",
+ "error": "Помилка",
+ "idle": "Очікування",
+ "off": "Вимкнено",
+ "on": "Увімкнено",
+ "paused": "Призупинено",
+ "returning": "Повернення до дока"
}
},
"state_badge": {
@@ -294,7 +301,8 @@
"period": "Період"
},
"logbook": {
- "showing_entries": "Відображення записів за"
+ "showing_entries": "Відображення записів за",
+ "period": "Період"
},
"mailbox": {
"empty": "У вас немає повідомлень",
@@ -362,16 +370,37 @@
"add": "Додати тригер",
"duplicate": "Дублювати",
"delete": "Видалити",
+ "delete_confirm": "Ви впевнені, що хочете видалити?",
"unsupported_platform": "Непідтримувана платформа: {platform}",
+ "type_select": "Тип подразнювача",
"type": {
+ "event": {
+ "label": "Подія:",
+ "event_type": "Тип події",
+ "event_data": "Дані події "
+ },
+ "state": {
+ "label": "Статус",
+ "from": "Від",
+ "to": "До",
+ "for": "Протягом"
+ },
"homeassistant": {
+ "label": "Home Assistant",
"event": "Подія:",
"start": "Початок",
"shutdown": "Завершення роботи"
},
+ "mqtt": {
+ "label": "MQTT",
+ "topic": "Тема",
+ "payload": "Корисне навантаження (необов'язково)"
+ },
"numeric_state": {
+ "label": "Числовий стан",
"above": "Вище",
- "below": "Нижче"
+ "below": "Нижче",
+ "value_template": "Значення шаблону (необов'язково)"
},
"sun": {
"label": "Сонце",
@@ -380,44 +409,76 @@
"sunset": "Захід сонця",
"offset": "Зміщення (необов'язково)"
},
+ "template": {
+ "label": "Шаблон",
+ "value_template": "Значення шаблону"
+ },
"time": {
"label": "Час",
"at": "У"
},
"zone": {
"label": "Зона",
+ "entity": "Місце розташування об'єкту",
"zone": "Зона:",
"event": "Подія:",
"enter": "Увійшов",
"leave": "Залишив"
},
- "state": {
- "for": "Протягом"
+ "webhook": {
+ "label": "Webhook",
+ "webhook_id": "Webhook ID"
},
"time_pattern": {
"label": "Шаблон часу",
"hours": "Годин",
"minutes": "Хвилин",
"seconds": "Секунд"
+ },
+ "geo_location": {
+ "label": "Геолокація",
+ "source": "Джерело",
+ "zone": "Зона",
+ "event": "Подія:",
+ "enter": "Увійти",
+ "leave": "Залишити"
}
}
},
"conditions": {
"header": "Умови",
"introduction": "Умови є необов'язковою частиною правила автоматизації і можуть використовуватися для запобігання дії, що відбувається під час запуску. Умови виглядають дуже схоже на тригери, але вони різні. Тригер буде дивитися на події, що відбуваються в системі, в той час як умова тільки дивиться на те, як система виглядає зараз. Тригер може спостерігати, що перемикач включений. Умова може бачити тільки, якщо перемикач ввімкнено або вимкнено. \n\n [Докладніше про умови.] (Https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)",
+ "add": "Додати умову",
"duplicate": "Дублювати",
"delete": "Видалити",
"delete_confirm": "Ви впевнені, що хочете видалити?",
+ "unsupported_condition": "Непідтримувана умова: {condition}",
+ "type_select": "Тип умови",
"type": {
+ "numeric_state": {
+ "above": "Над",
+ "below": "Нижче"
+ },
"sun": {
+ "label": "Сонце",
"before": "Перед:",
"after": "Після:",
"before_offset": "Перед зміщенням (необов'язково)",
- "after_offset": "Після зміщення (опціонально)"
+ "after_offset": "Після зміщення (опціонально)",
+ "sunrise": "Схід",
+ "sunset": "Захід"
+ },
+ "template": {
+ "label": "Шаблон"
},
"time": {
+ "label": "Час",
"after": "Після",
"before": "До"
+ },
+ "zone": {
+ "label": "Зона",
+ "zone": "Зона"
}
}
},
@@ -431,12 +492,26 @@
"unsupported_action": "Непідтримувана дія: {action}",
"type_select": "Тип дії",
"type": {
+ "service": {
+ "label": "Викликати послугу",
+ "service_data": "Дані послуги "
+ },
"delay": {
- "label": "Затримка"
+ "label": "Затримка",
+ "delay": "Затримка"
},
"wait_template": {
"label": "Чекати",
+ "wait_template": "Шаблон Wait",
"timeout": "Тайм-аут (необов'язково)"
+ },
+ "condition": {
+ "label": "Умова"
+ },
+ "event": {
+ "label": "Викликати подію",
+ "event": "Подія:",
+ "service_data": "Дані послуги"
}
}
}
@@ -447,7 +522,8 @@
"description": "Створення та редагування скриптів"
},
"zwave": {
- "caption": "Z-Wave"
+ "caption": "Z-Wave",
+ "description": "Керуйте мережею Z-Wave"
},
"users": {
"caption": "Користувачі",
@@ -487,6 +563,21 @@
"device_unavailable": "пристрій недоступний",
"entity_unavailable": "Недоступний"
}
+ },
+ "zha": {
+ "caption": "ZHA",
+ "description": "Управління мережею домашньої автоматизації Zigbee",
+ "services": {
+ "reconfigure": "Переконфігуруйте пристрій ZHA (пристрій загоєння). Використовуйте це, якщо у вас виникли проблеми з пристроєм. Якщо пристрій, про який йде мова, живиться від батареї, будь ласка, переконайтеся, що він є активним і приймає команди під час використання цієї команди."
+ }
+ },
+ "area_registry": {
+ "caption": "Реєстр зони",
+ "description": "Огляд всіх областей у вашому домі."
+ },
+ "entity_registry": {
+ "caption": "Реєстр об'єктів",
+ "description": "Огляд всіх відомих об'єктів."
}
},
"profile": {
@@ -608,10 +699,12 @@
}
},
"error": {
- "invalid_auth": "Невірний пароль API"
+ "invalid_auth": "Невірний пароль API",
+ "invalid_code": "Невірний код авторизації"
},
"abort": {
- "no_api_password_set": "Ви не маєте налаштованого пароля API."
+ "no_api_password_set": "Ви не маєте налаштованого пароля API.",
+ "login_expired": "Термін дії сессії закінчився, авторизуйтесь ще раз"
}
},
"trusted_networks": {
@@ -652,6 +745,11 @@
"checked_items": "Позначені елементи",
"clear_items": "Очистити позначені елементи",
"add_item": "Додати елемент"
+ },
+ "empty_state": {
+ "title": "Ласкаво просимо додому",
+ "no_devices": "Ця сторінка дозволяє керувати пристроями, однак, схоже, у вас ще немає налаштованих пристроїв. Щоб почати, перейдіть на сторінку інтеграції.",
+ "go_to_integrations_page": "Перейти на сторінку інтеграцій."
}
},
"editor": {
@@ -679,14 +777,20 @@
},
"save_config": {
"header": "Візьміть під свій контроль Lovelace UI",
- "para": "За замовчуванням Home Assistant буде підтримувати ваш користувальницький інтерфейс, оновлюючи його, коли з'являться нові об'єкти або компоненти Lovelace. Якщо ви візьмете під контроль, ми більше не будемо автоматично вносити зміни для вас.",
- "para_sure": "Ви впевнені, що хочете взяти під свій контроль користувальницький інтерфейс?",
+ "para": "За замовчуванням Home Assistant буде підтримувати ваш інтерфейс, оновлюючи його, коли з'являться нові об'єкти або компоненти Lovelace. Якщо ви візьмете під контроль, ми більше не будемо автоматично вносити зміни для вас.",
+ "para_sure": "Ви впевнені, що хочете взяти під свій контроль інтерфейс?",
"cancel": "Неважливо",
"save": "Взяти під контроль"
+ },
+ "menu": {
+ "raw_editor": "Текстовий редактор"
}
},
"menu": {
- "configure_ui": "Налаштувати інтерфейс користувача"
+ "configure_ui": "Налаштувати інтерфейс користувача",
+ "unused_entities": "Незадіяні об'єкти",
+ "help": "Допомога",
+ "refresh": "Оновити"
}
}
},
@@ -732,6 +836,24 @@
"visibility": "Видимість",
"wind_speed": "Швидкість вітру"
},
+ "cardinal_direction": {
+ "e": "E",
+ "ene": "ENE",
+ "ese": "ESE",
+ "n": "N",
+ "ne": "NE",
+ "nne": "NNE",
+ "nw": "NW",
+ "nnw": "NNW",
+ "s": "S",
+ "se": "SE",
+ "sse": "SSE",
+ "ssw": "SSW",
+ "sw": "SW",
+ "w": "W",
+ "wnw": "WNW",
+ "wsw": "WSW"
+ },
"forecast": "Прогноз"
},
"alarm_control_panel": {
@@ -775,7 +897,8 @@
"operation": "Режим",
"fan_mode": "Режим вентилятора",
"swing_mode": "Режим гойдання",
- "away_mode": "Режиму очікування"
+ "away_mode": "Режиму очікування",
+ "aux_heat": "Aux тепла"
},
"lock": {
"code": "Код",
@@ -784,6 +907,9 @@
},
"vacuum": {
"actions": {
+ "resume_cleaning": "Відновити очищення",
+ "return_to_base": "Повернутись до дока",
+ "start_cleaning": "Почати очищення",
"turn_on": "Ввімкнути",
"turn_off": "Вимкнути"
}
@@ -832,7 +958,8 @@
"dialogs": {
"more_info_settings": {
"save": "Зберегти",
- "name": "Назва"
+ "name": "Назва",
+ "entity_id": "Ідентифікатор об'єкта"
},
"more_info_control": {
"script": {
@@ -873,6 +1000,12 @@
"fan": "Вентилятор",
"history_graph": "Графік історії",
"group": "Група",
+ "image_processing": "Обробка зображень ",
+ "input_boolean": "Введення логічного значення",
+ "input_datetime": "Введення дати",
+ "input_select": "Вибрати",
+ "input_number": "Введіть номер",
+ "input_text": "Введення тексту",
"light": "Освітлення",
"lock": "Замок",
"mailbox": "Поштова скринька",
@@ -888,7 +1021,13 @@
"switch": "Перемикач",
"updater": "Оновлювач",
"weblink": "Посилання",
- "zwave": "Z-Wave"
+ "zwave": "Z-Wave",
+ "vacuum": "Пилосос",
+ "zha": "ZHA",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "Безпека системи"
},
"attribute": {
"weather": {
diff --git a/translations/zh-Hans.json b/translations/zh-Hans.json
index 0734e3e2d9..5acaaad1a5 100644
--- a/translations/zh-Hans.json
+++ b/translations/zh-Hans.json
@@ -301,7 +301,8 @@
"period": "日期范围"
},
"logbook": {
- "showing_entries": "显示以下日期的条目"
+ "showing_entries": "显示以下日期的条目",
+ "period": "周期"
},
"mailbox": {
"empty": "您还没有任何消息",
@@ -432,6 +433,14 @@
"hours": "小时",
"minutes": "分",
"seconds": "秒"
+ },
+ "geo_location": {
+ "label": "地理位置",
+ "source": "位置来源",
+ "zone": "区域",
+ "event": "事件:",
+ "enter": "进入",
+ "leave": "离开"
}
}
},
@@ -564,6 +573,12 @@
},
"zha": {
"description": "Zigbee 智能家居(ZHA) 网络管理"
+ },
+ "area_registry": {
+ "description": "您家中所有区域的概览。"
+ },
+ "entity_registry": {
+ "description": "所有已知实体的概览。"
}
},
"profile": {
@@ -732,6 +747,11 @@
"checked_items": "已完成项目",
"clear_items": "清除已完成项目",
"add_item": "新增项目"
+ },
+ "empty_state": {
+ "title": "欢迎回家",
+ "no_devices": "此页面是用来控制设备的,不过您好像还没有配置好任何设备。请前往集成页面以开始。",
+ "go_to_integrations_page": "前往集成页面。"
}
},
"editor": {
@@ -763,10 +783,16 @@
"para_sure": "您确定要自行编辑用户界面吗?",
"cancel": "没关系",
"save": "自行编辑"
+ },
+ "menu": {
+ "raw_editor": "原始配置编辑器"
}
},
"menu": {
- "configure_ui": "配置 UI"
+ "configure_ui": "配置 UI",
+ "unused_entities": "未使用的实体",
+ "help": "帮助",
+ "refresh": "刷新"
}
}
},
@@ -998,7 +1024,11 @@
"updater": "更新提示",
"weblink": "网址",
"zwave": "Z-Wave",
- "vacuum": "扫地机"
+ "vacuum": "扫地机",
+ "hassio": "Hass.io",
+ "homeassistant": "Home Assistant",
+ "lovelace": "Lovelace",
+ "system_health": "系统状态"
},
"attribute": {
"weather": {
diff --git a/tsconfig.json b/tsconfig.json
index 387ca8661e..ddc30c8b56 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,7 @@
{
"compilerOptions": {
+ "jsx": "react",
+ "jsxFactory": "h",
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
@@ -12,6 +14,7 @@
"strict": true,
"noImplicitAny": false,
"skipLibCheck": true,
- "resolveJsonModule": true
+ "resolveJsonModule": true,
+ "experimentalDecorators": true
}
}
diff --git a/webpack.config.js b/webpack.config.js
index 46e71154de..ec6d52dc1a 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -8,7 +8,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const zopfli = require("@gfx/zopfli");
const translationMetadata = require("./build-translations/translationMetadata.json");
const { babelLoaderConfig } = require("./config/babel.js");
-const { minimizer } = require("./config/minimizer.js");
+const webpackBase = require("./config/webpack.js");
const version = fs.readFileSync("setup.py", "utf8").match(/\d{8}\.\d+/);
if (!version) {
@@ -76,9 +76,7 @@ function createConfig(isProdBuild, latestBuild) {
},
],
},
- optimization: {
- minimizer,
- },
+ optimization: webpackBase.optimization,
plugins: [
new webpack.DefinePlugin({
__DEV__: JSON.stringify(!isProdBuild),
@@ -123,18 +121,7 @@ function createConfig(isProdBuild, latestBuild) {
!latestBuild && "public/__init__.py",
].filter(Boolean)
),
- // Ignore moment.js locales
- new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
- // Color.js is bloated, it contains all color definitions for all material color sets.
- new webpack.NormalModuleReplacementPlugin(
- /@polymer\/paper-styles\/color\.js$/,
- path.resolve(__dirname, "src/util/empty.js")
- ),
- // Ignore roboto pointing at CDN. We use local font-roboto-local.
- new webpack.NormalModuleReplacementPlugin(
- /@polymer\/font-roboto\/roboto\.js$/,
- path.resolve(__dirname, "src/util/empty.js")
- ),
+ ...webpackBase.plugins,
isProdBuild &&
!isCI &&
!isStatsBuild &&
@@ -207,17 +194,7 @@ function createConfig(isProdBuild, latestBuild) {
path: path.resolve(__dirname, buildPath),
publicPath,
},
- resolve: {
- extensions: [".ts", ".js", ".json"],
- alias: {
- react: "preact-compat",
- "react-dom": "preact-compat",
- // Not necessary unless you consume a module using `createClass`
- "create-react-class": "preact-compat/lib/create-react-class",
- // Not necessary unless you consume a module requiring `react-dom-factories`
- "react-dom-factories": "preact-compat/lib/react-dom-factories",
- },
- },
+ resolve: webpackBase.resolve,
};
}
diff --git a/yarn.lock b/yarn.lock
index a0aebc0d47..17185f5b93 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -72,6 +72,17 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
+"@babel/helper-create-class-features-plugin@^7.3.0":
+ version "7.3.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.2.tgz#ba1685603eb1c9f2f51c9106d5180135c163fe73"
+ integrity sha512-tdW8+V8ceh2US4GsYdNVNoohq5uVwOf9k6krjwW4E1lINcHgttnWcNqgdoessn12dAy8QkbezlbQh2nXISNY+A==
+ dependencies:
+ "@babel/helper-function-name" "^7.1.0"
+ "@babel/helper-member-expression-to-functions" "^7.0.0"
+ "@babel/helper-optimise-call-expression" "^7.0.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/helper-replace-supers" "^7.2.3"
+
"@babel/helper-define-map@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c"
@@ -168,7 +179,7 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0":
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3":
version "7.2.3"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5"
integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==
@@ -247,6 +258,23 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
+"@babel/plugin-proposal-class-properties@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd"
+ integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.3.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-proposal-decorators@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.3.0.tgz#637ba075fa780b1f75d08186e8fb4357d03a72a7"
+ integrity sha512-3W/oCUmsO43FmZIqermmq6TKaRSYhmh/vybPfVFwQWdSb8xwki38uAIvknCRzuyHRuYfCYmJzL9or1v0AffPjg==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.3.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-decorators" "^7.2.0"
+
"@babel/plugin-proposal-json-strings@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
@@ -287,6 +315,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-syntax-decorators@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b"
+ integrity sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
"@babel/plugin-syntax-dynamic-import@^7.0.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
@@ -1607,6 +1642,11 @@
resolved "https://registry.yarnpkg.com/@types/launchpad/-/launchpad-0.6.0.tgz#37296109b7f277f6e6c5fd7e0c0706bc918fbb51"
integrity sha1-NylhCbfyd/bmxf1+DAcGvJGPu1E=
+"@types/memoize-one@^4.1.0":
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/@types/memoize-one/-/memoize-one-4.1.0.tgz#62119f26055b3193ae43ca1882c5b29b88b71ece"
+ integrity sha512-cmSgi6JMX/yBwgpVm4GooNWIH+vEeJoa8FAa6ExOhpJbC0Juq32/uYKiKb3VPSqrEA0aOnjvwZanla3O1WZMbw==
+
"@types/merge-stream@^1.0.28":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@types/merge-stream/-/merge-stream-1.1.2.tgz#a880ff66b1fbbb5eef4958d015c5947a9334dbb1"
@@ -2224,20 +2264,15 @@
"@webassemblyjs/wast-parser" "1.7.11"
"@xtuc/long" "4.2.1"
-"@webcomponents/shadycss@^1.5.2", "@webcomponents/shadycss@^1.6.0":
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.8.0.tgz#10340b87626f876841033b21b5221c3701252e10"
- integrity sha512-bx0TzeZ11VqYDGLuXfznen8+4u0hADk2dD5RNMFxWL9MM4c5NXCDCgNXgssb4i2zQOos/GGe4tl5AuE0LzJkLA==
+"@webcomponents/shadycss@^1.5.2", "@webcomponents/shadycss@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.9.0.tgz#8450465037370d4f5c32e801bb2554a7cf2f5037"
+ integrity sha512-g8Xa+6RSEME4g/wLJW4YII0eq15rvXp76RxPAuv7hx+Bdoi7GzZJ/EoZOUfyIbqAsQbII1TcWD4/+Xhs5NcM1w==
-"@webcomponents/webcomponentsjs@2.2.1", "@webcomponents/webcomponentsjs@^1.0.7", "@webcomponents/webcomponentsjs@^2.0.0":
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.2.1.tgz#bf28cdad466aaf85d88ee56aebed32c83f4c1328"
- integrity sha512-lZZ+Lkke6JhsJcQQqSVk1Pny6/8y4qhJ98LO7a/MwBSRO8WqHqK1X2vscfeL8vOnYGFnmBUyVG95lwYv/AXyLQ==
-
-"@webcomponents/webcomponentsjs@^2.2.0":
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.2.4.tgz#b520d586e46517914d9708c2220b263116d5f86a"
- integrity sha512-YkLxK9Mbw6QK5bNZ67Rarb/yj0gN+Ziy5+2sLjM0lDb3XafM36gXKZXaIBz4zvLA/cYYTdg+l1LlqXeHEcmeiA==
+"@webcomponents/webcomponentsjs@^1.0.7", "@webcomponents/webcomponentsjs@^2.0.0", "@webcomponents/webcomponentsjs@^2.2.6":
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.2.6.tgz#b6aa1f837f05ec2a6996eb09185d5ade042ba4a9"
+ integrity sha512-TbuyV7hHIq4jMlvQ8pF3C6QNEqFeBX4be9AND5DO8UCRRYcZBpjl1yH9IChbebeWr1QNcK5WlZC3voAlQCdisQ==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
@@ -8660,17 +8695,17 @@ listr@^0.14.2:
p-map "^2.0.0"
rxjs "^6.3.3"
-lit-element@2.0.0-rc.5:
- version "2.0.0-rc.5"
- resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.0.0-rc.5.tgz#3e4323c9ace59d49c3a0950551a3f923e0ccf86b"
- integrity sha512-cMmWNWSFyYfXAd09bnqFhqDr5kuR/5guImD5ZRTk223EiBJaoo7naZnQngSYAMjgDn1CSbTE1LRtzviMS+g0RA==
+lit-element@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.0.1.tgz#9ec5871d3b64487f432c7c071df80ef031d7091b"
+ integrity sha512-2bu3B2ZYUZgntvOgu3i5mRK8geo45CLUwxwJEYK54hyednoJasjiTZPB13NBg1D+hNM2JfmWTWJnh1QEUQv7zw==
dependencies:
- lit-html "^1.0.0-rc.2"
+ lit-html "^1.0.0"
-lit-html@1.0.0-rc.2, lit-html@^1.0.0-rc.2:
- version "1.0.0-rc.2"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.0.0-rc.2.tgz#b9c904520fe005d349aa737a86d83645d97d5a89"
- integrity sha512-4bq34lhVmwWly1zBXicOBJLOwaWfjOVbchEEmFnZLuztxjh5wRd2WqV0URX8Q47MQ7PaIjn/eXyTRKsYhSAeRw==
+lit-html@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.0.0.tgz#3dc3781a8ca68a9b5c2ff2a61e263662b9b2267b"
+ integrity sha512-oeWlpLmBW3gFl7979Wol2LKITpmKTUFNn7PnFbh6YNynF61W74l6x5WhwItAwPRSATpexaX1egNnRzlN4GOtfQ==
load-json-file@^1.0.0:
version "1.1.0"
@@ -9436,6 +9471,11 @@ mem@^4.0.0:
mimic-fn "^1.0.0"
p-is-promise "^1.1.0"
+memoize-one@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.0.tgz#d55007dffefb8de7546659a1722a5d42e128286e"
+ integrity sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==
+
memory-fs@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"