mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-24 08:39:26 +00:00
Compare commits
99 Commits
remove-unc
...
move_secti
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f75d3f887e | ||
![]() |
c05824c641 | ||
![]() |
3abdffda9c | ||
![]() |
67da851efc | ||
![]() |
5463a27255 | ||
![]() |
ec0434c9b0 | ||
![]() |
7d8cb5c863 | ||
![]() |
4f01348ffb | ||
![]() |
2af3400464 | ||
![]() |
b6e220a4c5 | ||
![]() |
d5d45f100e | ||
![]() |
6b9ca60c47 | ||
![]() |
bc445a1e27 | ||
![]() |
a087b4c43e | ||
![]() |
8f67ddf968 | ||
![]() |
9ef07484dd | ||
![]() |
5287061699 | ||
![]() |
3ef1110109 | ||
![]() |
2efe2589d2 | ||
![]() |
4fb596357d | ||
![]() |
dd98ec771d | ||
![]() |
94f74308d8 | ||
![]() |
b982884933 | ||
![]() |
c47c6e358b | ||
![]() |
46394d0bf9 | ||
![]() |
4dc154201a | ||
![]() |
ebdbab81d3 | ||
![]() |
155098bc41 | ||
![]() |
291638a9dd | ||
![]() |
763c672e36 | ||
![]() |
c945534640 | ||
![]() |
5b3074d939 | ||
![]() |
3b89b72568 | ||
![]() |
1d9fa1522c | ||
![]() |
4db743db00 | ||
![]() |
d5f8231f97 | ||
![]() |
32c403d069 | ||
![]() |
220da51606 | ||
![]() |
9ae234a02f | ||
![]() |
401bbed67b | ||
![]() |
83190c21db | ||
![]() |
ccdd906e2f | ||
![]() |
f4c932ef9c | ||
![]() |
0892ed18e5 | ||
![]() |
3afc218adc | ||
![]() |
841b9c0917 | ||
![]() |
a479c6e786 | ||
![]() |
babb723521 | ||
![]() |
29954e530e | ||
![]() |
fb3c94f403 | ||
![]() |
dd8c1d359c | ||
![]() |
45e09a262b | ||
![]() |
d6d61a4137 | ||
![]() |
8fe7711634 | ||
![]() |
a5ec7fc251 | ||
![]() |
b9935717dc | ||
![]() |
bb25817bae | ||
![]() |
bf8a33e086 | ||
![]() |
bf56f50e0a | ||
![]() |
3a8e2c429f | ||
![]() |
e8fca5d93c | ||
![]() |
a39cf99024 | ||
![]() |
0ff27154e6 | ||
![]() |
766fd4cbf5 | ||
![]() |
1869260868 | ||
![]() |
93046d78f6 | ||
![]() |
3e51f9a505 | ||
![]() |
d95bf64edf | ||
![]() |
47f7cf5419 | ||
![]() |
267fc3743d | ||
![]() |
a6d73f7615 | ||
![]() |
b360c854a8 | ||
![]() |
a088b20987 | ||
![]() |
af6dd545dc | ||
![]() |
a26df88022 | ||
![]() |
86ec272581 | ||
![]() |
2a803e09a4 | ||
![]() |
35ebfc15c9 | ||
![]() |
f0a9185e4a | ||
![]() |
b3766cbc62 | ||
![]() |
3469013f1a | ||
![]() |
03486e4125 | ||
![]() |
20560fb847 | ||
![]() |
bad18da658 | ||
![]() |
e26c7c491a | ||
![]() |
b0c8ae0c94 | ||
![]() |
cc1658cbab | ||
![]() |
4b768f0635 | ||
![]() |
989057d947 | ||
![]() |
6671d24fa6 | ||
![]() |
297c721229 | ||
![]() |
70c502bb45 | ||
![]() |
add0b55657 | ||
![]() |
50e559487d | ||
![]() |
4cd02c81bb | ||
![]() |
d044f4d34e | ||
![]() |
696717dd90 | ||
![]() |
22c3132638 | ||
![]() |
ce32de6e23 |
@@ -1,26 +1,19 @@
|
|||||||
// Tasks to compress
|
// Tasks to compress
|
||||||
|
|
||||||
import { deleteAsync } from "del";
|
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import gulpIf from "gulp-if";
|
|
||||||
import vinylPaths from "vinyl-paths";
|
|
||||||
import zopfli from "gulp-zopfli-green";
|
import zopfli from "gulp-zopfli-green";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
const zopfliOptions = { threshold: 150 };
|
const zopfliOptions = { threshold: 150 };
|
||||||
|
|
||||||
const compressedExt = /\.gz$/;
|
|
||||||
const deleteUncompressed = (p) => deleteAsync(p.replace(compressedExt, ""));
|
|
||||||
|
|
||||||
const compressDist = (rootDir) =>
|
const compressDist = (rootDir) =>
|
||||||
gulp
|
gulp
|
||||||
.src([
|
.src([
|
||||||
`${rootDir}/**/*.{js?(.map),json,css,svg,xml}`,
|
`${rootDir}/**/*.{js,json,css,svg,xml}`,
|
||||||
`${rootDir}/{authorize,onboarding}.html`,
|
`${rootDir}/{authorize,onboarding}.html`,
|
||||||
])
|
])
|
||||||
.pipe(zopfli(zopfliOptions))
|
.pipe(zopfli(zopfliOptions))
|
||||||
.pipe(gulp.dest(rootDir))
|
.pipe(gulp.dest(rootDir));
|
||||||
.pipe(gulpIf(compressedExt, vinylPaths(deleteUncompressed)));
|
|
||||||
|
|
||||||
gulp.task("compress-app", () => compressDist(paths.app_output_root));
|
gulp.task("compress-app", () => compressDist(paths.app_output_root));
|
||||||
gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root));
|
gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root));
|
||||||
|
@@ -270,7 +270,7 @@ export class HcMain extends HassElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
if (msg.urlPath === "lovelace") {
|
if (msg.urlPath === "lovelace" || msg.urlPath === undefined) {
|
||||||
msg.urlPath = null;
|
msg.urlPath = null;
|
||||||
}
|
}
|
||||||
this._lovelacePath = msg.viewPath;
|
this._lovelacePath = msg.viewPath;
|
||||||
|
@@ -17,6 +17,7 @@ export const basicTrace: DemoTrace = {
|
|||||||
{
|
{
|
||||||
path: "trigger/0",
|
path: "trigger/0",
|
||||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||||
|
changed_variables: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"condition/0": [
|
"condition/0": [
|
||||||
|
@@ -17,6 +17,7 @@ export const motionLightTrace: DemoTrace = {
|
|||||||
{
|
{
|
||||||
path: "trigger/0",
|
path: "trigger/0",
|
||||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||||
|
changed_variables: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"action/0": [
|
"action/0": [
|
||||||
|
@@ -55,6 +55,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@@ -60,6 +60,7 @@ export class DemoAutomationTrace extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@@ -1263,6 +1263,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
.card-actions {
|
.card-actions {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
.changelog {
|
.changelog {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
@@ -154,12 +154,16 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
|
inset-inline-end: 16px;
|
||||||
|
inset-inline-start: initial;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
margin: 18px 42px 0 18px;
|
margin: 18px 42px 0 18px;
|
||||||
|
margin-inline-start: 18px;
|
||||||
|
margin-inline-end: 42px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
|
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@@ -27,6 +25,8 @@ import type { HomeAssistant } from "../../../../src/types";
|
|||||||
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
|
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
|
||||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||||
import "../../../../src/components/ha-textfield";
|
import "../../../../src/components/ha-textfield";
|
||||||
|
import "../../../../src/components/ha-list-new";
|
||||||
|
import "../../../../src/components/ha-list-item-new";
|
||||||
|
|
||||||
@customElement("dialog-hassio-repositories")
|
@customElement("dialog-hassio-repositories")
|
||||||
class HassioRepositoriesDialog extends LitElement {
|
class HassioRepositoriesDialog extends LitElement {
|
||||||
@@ -106,44 +106,46 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="form">
|
<div class="form">
|
||||||
${repositories.length
|
<ha-list-new>
|
||||||
? repositories.map(
|
${repositories.length
|
||||||
(repo) => html`
|
? repositories.map(
|
||||||
<paper-item class="option">
|
(repo) => html`
|
||||||
<paper-item-body three-line>
|
<ha-list-item-new class="option">
|
||||||
<div>${repo.name}</div>
|
${repo.name}
|
||||||
<div secondary>${repo.maintainer}</div>
|
<div slot="supporting-text">
|
||||||
<div secondary>${repo.url}</div>
|
<div>${repo.maintainer}</div>
|
||||||
</paper-item-body>
|
<div>${repo.url}</div>
|
||||||
<div class="delete">
|
</div>
|
||||||
<ha-icon-button
|
<div class="delete" slot="end">
|
||||||
.label=${this._dialogParams!.supervisor.localize(
|
<ha-icon-button
|
||||||
"dialog.repositories.remove"
|
.label=${this._dialogParams!.supervisor.localize(
|
||||||
)}
|
"dialog.repositories.remove"
|
||||||
.disabled=${usedRepositories.includes(repo.slug)}
|
)}
|
||||||
.slug=${repo.slug}
|
.disabled=${usedRepositories.includes(repo.slug)}
|
||||||
.path=${usedRepositories.includes(repo.slug)
|
.slug=${repo.slug}
|
||||||
? mdiDeleteOff
|
.path=${usedRepositories.includes(repo.slug)
|
||||||
: mdiDelete}
|
? mdiDeleteOff
|
||||||
@click=${this._removeRepository}
|
: mdiDelete}
|
||||||
>
|
@click=${this._removeRepository}
|
||||||
</ha-icon-button>
|
>
|
||||||
<simple-tooltip
|
</ha-icon-button>
|
||||||
animation-delay="0"
|
<simple-tooltip
|
||||||
position="bottom"
|
animation-delay="0"
|
||||||
offset="1"
|
position="bottom"
|
||||||
>
|
offset="1"
|
||||||
${this._dialogParams!.supervisor.localize(
|
>
|
||||||
usedRepositories.includes(repo.slug)
|
${this._dialogParams!.supervisor.localize(
|
||||||
? "dialog.repositories.used"
|
usedRepositories.includes(repo.slug)
|
||||||
: "dialog.repositories.remove"
|
? "dialog.repositories.used"
|
||||||
)}
|
: "dialog.repositories.remove"
|
||||||
</simple-tooltip>
|
)}
|
||||||
</div>
|
</simple-tooltip>
|
||||||
</paper-item>
|
</div>
|
||||||
`
|
</ha-list-item-new>
|
||||||
)
|
`
|
||||||
: html`<paper-item> No repositories </paper-item>`}
|
)
|
||||||
|
: html`<ha-list-item-new> No repositories </ha-list-item-new>`}
|
||||||
|
</ha-list-new>
|
||||||
<div class="layout horizontal bottom">
|
<div class="layout horizontal bottom">
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
class="flex-auto"
|
class="flex-auto"
|
||||||
@@ -206,6 +208,9 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
div.delete ha-icon-button {
|
div.delete ha-icon-button {
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
ha-list-item-new {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
55
package.json
55
package.json
@@ -32,8 +32,8 @@
|
|||||||
"@codemirror/language": "6.10.1",
|
"@codemirror/language": "6.10.1",
|
||||||
"@codemirror/legacy-modes": "6.3.3",
|
"@codemirror/legacy-modes": "6.3.3",
|
||||||
"@codemirror/search": "6.5.6",
|
"@codemirror/search": "6.5.6",
|
||||||
"@codemirror/state": "6.4.0",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.24.0",
|
"@codemirror/view": "6.24.1",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.2",
|
"@formatjs/intl-datetimeformat": "6.12.2",
|
||||||
"@formatjs/intl-displaynames": "6.6.6",
|
"@formatjs/intl-displaynames": "6.6.6",
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
"@formatjs/intl-numberformat": "8.10.0",
|
"@formatjs/intl-numberformat": "8.10.0",
|
||||||
"@formatjs/intl-pluralrules": "5.2.12",
|
"@formatjs/intl-pluralrules": "5.2.12",
|
||||||
"@formatjs/intl-relativetimeformat": "11.2.12",
|
"@formatjs/intl-relativetimeformat": "11.2.12",
|
||||||
"@fullcalendar/core": "6.1.10",
|
"@fullcalendar/core": "6.1.11",
|
||||||
"@fullcalendar/daygrid": "6.1.10",
|
"@fullcalendar/daygrid": "6.1.11",
|
||||||
"@fullcalendar/interaction": "6.1.10",
|
"@fullcalendar/interaction": "6.1.11",
|
||||||
"@fullcalendar/list": "6.1.10",
|
"@fullcalendar/list": "6.1.11",
|
||||||
"@fullcalendar/luxon3": "6.1.10",
|
"@fullcalendar/luxon3": "6.1.11",
|
||||||
"@fullcalendar/timegrid": "6.1.10",
|
"@fullcalendar/timegrid": "6.1.11",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.0",
|
||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.7",
|
"@lit-labs/motion": "1.0.7",
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
"@material/mwc-radio": "0.27.0",
|
"@material/mwc-radio": "0.27.0",
|
||||||
"@material/mwc-ripple": "0.27.0",
|
"@material/mwc-ripple": "0.27.0",
|
||||||
"@material/mwc-select": "0.27.0",
|
"@material/mwc-select": "0.27.0",
|
||||||
|
"@material/mwc-snackbar": "0.27.0",
|
||||||
"@material/mwc-switch": "0.27.0",
|
"@material/mwc-switch": "0.27.0",
|
||||||
"@material/mwc-tab": "0.27.0",
|
"@material/mwc-tab": "0.27.0",
|
||||||
"@material/mwc-tab-bar": "0.27.0",
|
"@material/mwc-tab-bar": "0.27.0",
|
||||||
@@ -80,17 +81,16 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "=1.2.0",
|
"@material/web": "=1.3.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
"@polymer/paper-listbox": "3.0.1",
|
"@polymer/paper-listbox": "3.0.1",
|
||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/paper-toast": "3.0.1",
|
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.3.6",
|
"@vaadin/combo-box": "24.3.7",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.3.6",
|
"@vaadin/vaadin-themable-mixin": "24.3.7",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@@ -99,6 +99,7 @@
|
|||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.1",
|
"chart.js": "4.4.1",
|
||||||
|
"color-name": "2.0.0",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.36.0",
|
"core-js": "3.36.0",
|
||||||
"cropperjs": "1.6.1",
|
"cropperjs": "1.6.1",
|
||||||
@@ -109,7 +110,7 @@
|
|||||||
"element-internals-polyfill": "1.3.10",
|
"element-internals-polyfill": "1.3.10",
|
||||||
"fuse.js": "7.0.0",
|
"fuse.js": "7.0.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"hls.js": "1.5.5",
|
"hls.js": "1.5.7",
|
||||||
"home-assistant-js-websocket": "9.1.0",
|
"home-assistant-js-websocket": "9.1.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.11",
|
"intl-messageformat": "10.5.11",
|
||||||
@@ -155,11 +156,11 @@
|
|||||||
"@babel/plugin-transform-runtime": "7.23.9",
|
"@babel/plugin-transform-runtime": "7.23.9",
|
||||||
"@babel/preset-env": "7.23.9",
|
"@babel/preset-env": "7.23.9",
|
||||||
"@babel/preset-typescript": "7.23.3",
|
"@babel/preset-typescript": "7.23.3",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.10.0",
|
"@bundle-stats/plugin-webpack-filter": "4.10.1",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.1.0",
|
"@lokalise/node-api": "12.1.0",
|
||||||
"@octokit/auth-oauth-device": "6.0.1",
|
"@octokit/auth-oauth-device": "7.0.0",
|
||||||
"@octokit/plugin-retry": "6.0.1",
|
"@octokit/plugin-retry": "7.0.1",
|
||||||
"@octokit/rest": "20.0.2",
|
"@octokit/rest": "20.0.2",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.4",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
@@ -170,8 +171,8 @@
|
|||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.13",
|
"@types/chromecast-caf-receiver": "6.0.13",
|
||||||
"@types/chromecast-caf-sender": "1.0.8",
|
"@types/chromecast-caf-sender": "1.0.8",
|
||||||
|
"@types/color-name": "1.1.3",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
"@types/gulp-if": "^3",
|
|
||||||
"@types/html-minifier-terser": "7.0.2",
|
"@types/html-minifier-terser": "7.0.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/leaflet": "1.9.8",
|
"@types/leaflet": "1.9.8",
|
||||||
@@ -180,19 +181,19 @@
|
|||||||
"@types/mocha": "10.0.6",
|
"@types/mocha": "10.0.6",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/serve-handler": "6.1.4",
|
"@types/serve-handler": "6.1.4",
|
||||||
"@types/sortablejs": "1.15.7",
|
"@types/sortablejs": "1.15.8",
|
||||||
"@types/tar": "6.1.11",
|
"@types/tar": "6.1.11",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.0.1",
|
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||||
"@typescript-eslint/parser": "7.0.1",
|
"@typescript-eslint/parser": "7.1.0",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"chai": "5.1.0",
|
"chai": "5.1.0",
|
||||||
"del": "7.1.0",
|
"del": "7.1.0",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "17.1.0",
|
"eslint-config-airbnb-typescript": "17.1.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
@@ -208,8 +209,7 @@
|
|||||||
"glob": "10.3.10",
|
"glob": "10.3.10",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"gulp-flatmap": "1.0.2",
|
"gulp-flatmap": "1.0.2",
|
||||||
"gulp-if": "3.0.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
"gulp-json-transform": "0.4.8",
|
|
||||||
"gulp-merge-json": "2.1.2",
|
"gulp-merge-json": "2.1.2",
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.1",
|
"gulp-zopfli-green": "6.0.1",
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.3.0",
|
"mocha": "10.3.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "10.0.3",
|
"open": "10.0.4",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
"rollup": "2.79.1",
|
"rollup": "2.79.1",
|
||||||
@@ -241,14 +241,13 @@
|
|||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.3.3",
|
||||||
"vinyl-buffer": "1.0.1",
|
"vinyl-buffer": "1.0.1",
|
||||||
"vinyl-paths": "5.0.0",
|
|
||||||
"vinyl-source-stream": "2.0.0",
|
"vinyl-source-stream": "2.0.0",
|
||||||
"webpack": "5.90.2",
|
"webpack": "5.90.3",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "4.15.1",
|
"webpack-dev-server": "5.0.2",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "6.0.0",
|
"webpackbar": "6.0.1",
|
||||||
"workbox-build": "7.0.0"
|
"workbox-build": "7.0.0"
|
||||||
},
|
},
|
||||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240207.0"
|
version = "20240228.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@@ -1,19 +1,25 @@
|
|||||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { ensureArray } from "../array/ensure-array";
|
||||||
import { isComponentLoaded } from "./is_component_loaded";
|
import { isComponentLoaded } from "./is_component_loaded";
|
||||||
|
|
||||||
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) =>
|
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
(isCore(page) || isLoadedIntegration(hass, page)) &&
|
(isCore(page) || isLoadedIntegration(hass, page)) &&
|
||||||
!hideAdvancedPage(hass, page);
|
!hideAdvancedPage(hass, page) &&
|
||||||
|
isNotLoadedIntegration(hass, page);
|
||||||
|
|
||||||
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
page.component
|
!page.component ||
|
||||||
? isComponentLoaded(hass, page.component)
|
ensureArray(page.component).some((integration) =>
|
||||||
: page.components
|
isComponentLoaded(hass, integration)
|
||||||
? page.components.some((integration) =>
|
);
|
||||||
isComponentLoaded(hass, integration)
|
|
||||||
)
|
const isNotLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
: true;
|
!page.not_component ||
|
||||||
|
!ensureArray(page.not_component).some((integration) =>
|
||||||
|
isComponentLoaded(hass, integration)
|
||||||
|
);
|
||||||
|
|
||||||
const isCore = (page: PageNavigation) => page.core;
|
const isCore = (page: PageNavigation) => page.core;
|
||||||
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
||||||
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
|
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
import { MAIN_WINDOW_NAME } from "../../data/main_window";
|
import { MAIN_WINDOW_NAME } from "../../data/main_window";
|
||||||
|
|
||||||
export const mainWindow =
|
export const mainWindow = (() => {
|
||||||
window.name === MAIN_WINDOW_NAME
|
try {
|
||||||
? window
|
return window.name === MAIN_WINDOW_NAME
|
||||||
: parent.name === MAIN_WINDOW_NAME
|
? window
|
||||||
? parent
|
: parent.name === MAIN_WINDOW_NAME
|
||||||
: top!;
|
? parent
|
||||||
|
: top!;
|
||||||
|
} catch {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
@@ -20,14 +20,14 @@ function findNestedItem(
|
|||||||
}, obj);
|
}, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nestedArrayMove<T>(
|
export function nestedArrayMove<A>(
|
||||||
obj: T | T[],
|
obj: A,
|
||||||
oldIndex: number,
|
oldIndex: number,
|
||||||
newIndex: number,
|
newIndex: number,
|
||||||
oldPath?: ItemPath,
|
oldPath?: ItemPath,
|
||||||
newPath?: ItemPath
|
newPath?: ItemPath
|
||||||
): T | T[] {
|
): A {
|
||||||
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
|
const newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
|
||||||
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
|
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
|
||||||
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
|
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
|
||||||
|
|
||||||
|
@@ -75,6 +75,8 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
private _paddingYAxisInternal = 0;
|
private _paddingYAxisInternal = 0;
|
||||||
|
|
||||||
|
private _datasetOrder: number[] = [];
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._releaseCanvas();
|
this._releaseCanvas();
|
||||||
@@ -165,7 +167,17 @@ export class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// put the legend labels in sorted order if provided
|
||||||
if (changedProps.has("data")) {
|
if (changedProps.has("data")) {
|
||||||
|
this._datasetOrder = this.data.datasets.map((_, index) => index);
|
||||||
|
if (this.data?.datasets.some((dataset) => dataset.order)) {
|
||||||
|
this._datasetOrder.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(this.data.datasets[a].order || 0) -
|
||||||
|
(this.data.datasets[b].order || 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.externalHidden) {
|
if (this.externalHidden) {
|
||||||
this._hiddenDatasets = new Set();
|
this._hiddenDatasets = new Set();
|
||||||
if (this.data?.datasets) {
|
if (this.data?.datasets) {
|
||||||
@@ -205,8 +217,9 @@ export class HaChartBase extends LitElement {
|
|||||||
${this.options?.plugins?.legend?.display === true
|
${this.options?.plugins?.legend?.display === true
|
||||||
? html`<div class="chartLegend">
|
? html`<div class="chartLegend">
|
||||||
<ul>
|
<ul>
|
||||||
${this.data.datasets.map((dataset, index) =>
|
${this._datasetOrder.map((index) => {
|
||||||
this.extraData?.[index]?.show_legend === false
|
const dataset = this.data.datasets[index];
|
||||||
|
return this.extraData?.[index]?.show_legend === false
|
||||||
? nothing
|
? nothing
|
||||||
: html`<li
|
: html`<li
|
||||||
.datasetIndex=${index}
|
.datasetIndex=${index}
|
||||||
@@ -228,8 +241,8 @@ export class HaChartBase extends LitElement {
|
|||||||
${this.extraData?.[index]?.legend_label ??
|
${this.extraData?.[index]?.legend_label ??
|
||||||
dataset.label}
|
dataset.label}
|
||||||
</div>
|
</div>
|
||||||
</li>`
|
</li>`;
|
||||||
)}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>`
|
</div>`
|
||||||
: ""}
|
: ""}
|
||||||
|
@@ -111,7 +111,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
config: this.hass.config,
|
config: this.hass.config,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
suggestedMin: this.startTime,
|
min: this.startTime,
|
||||||
suggestedMax: this.endTime,
|
suggestedMax: this.endTime,
|
||||||
ticks: {
|
ticks: {
|
||||||
maxRotation: 0,
|
maxRotation: 0,
|
||||||
|
@@ -114,7 +114,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
config: this.hass.config,
|
config: this.hass.config,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
suggestedMin: this.startTime,
|
min: this.startTime,
|
||||||
suggestedMax: this.endTime,
|
suggestedMax: this.endTime,
|
||||||
ticks: {
|
ticks: {
|
||||||
autoSkip: true,
|
autoSkip: true,
|
||||||
|
@@ -233,16 +233,32 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
|
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this._computedStartTime = new Date(
|
let minTimeAll = (this.historyData?.timeline ?? []).reduce(
|
||||||
(this.historyData?.timeline ?? []).reduce(
|
(minTime, stateInfo) =>
|
||||||
(minTime, stateInfo) =>
|
Math.min(
|
||||||
Math.min(
|
minTime,
|
||||||
minTime,
|
new Date(stateInfo.data[0].last_changed).getTime()
|
||||||
new Date(stateInfo.data[0].last_changed).getTime()
|
),
|
||||||
),
|
new Date().getTime()
|
||||||
new Date().getTime()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
minTimeAll = (this.historyData?.line ?? []).reduce(
|
||||||
|
(minTimeLine, line) =>
|
||||||
|
Math.min(
|
||||||
|
minTimeLine,
|
||||||
|
line.data.reduce(
|
||||||
|
(minTimeData, data) =>
|
||||||
|
Math.min(
|
||||||
|
minTimeData,
|
||||||
|
new Date(data.states[0].last_changed).getTime()
|
||||||
|
),
|
||||||
|
minTimeLine
|
||||||
|
)
|
||||||
|
),
|
||||||
|
minTimeAll
|
||||||
|
);
|
||||||
|
|
||||||
|
this._computedStartTime = new Date(minTimeAll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -205,7 +205,9 @@ export class TimelineController extends BarController {
|
|||||||
|
|
||||||
const y = vScale.getPixelForValue(this.index);
|
const y = vScale.getPixelForValue(this.index);
|
||||||
|
|
||||||
const xStart = iScale.getPixelForValue(data.start.getTime());
|
const xStart = iScale.getPixelForValue(
|
||||||
|
Math.max(iScale.min, data.start.getTime())
|
||||||
|
);
|
||||||
const xEnd = iScale.getPixelForValue(data.end.getTime());
|
const xEnd = iScale.getPixelForValue(data.end.getTime());
|
||||||
const width = xEnd - xStart;
|
const width = xEnd - xStart;
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ export class TimeLineScale extends TimeScale {
|
|||||||
max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit);
|
max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit);
|
||||||
|
|
||||||
// Make sure that max is strictly higher than min (required by the lookup table)
|
// Make sure that max is strictly higher than min (required by the lookup table)
|
||||||
this.min = Math.min(min, max - 1);
|
this.min = adapter.parse(options.min, this) ?? Math.min(min, max - 1);
|
||||||
this.max = Math.max(min + 1, max);
|
this.max = adapter.parse(options.max, this) ?? Math.max(min + 1, max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,8 @@ class HaDataTableIcon extends LitElement {
|
|||||||
div {
|
div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 28px;
|
right: 28px;
|
||||||
|
inset-inline-end: 28px;
|
||||||
|
inset-inline-start: initial;
|
||||||
z-index: 1002;
|
z-index: 1002;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
@@ -32,7 +32,9 @@ export class StateBadge extends LitElement {
|
|||||||
|
|
||||||
@property() public overrideImage?: string;
|
@property() public overrideImage?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public stateColor = false;
|
// Cannot be a boolean attribute because undefined is treated different than
|
||||||
|
// false. When it is undefined, state is still colored for light entities.
|
||||||
|
@property({ attribute: false }) public stateColor?: boolean;
|
||||||
|
|
||||||
@property() public color?: string;
|
@property() public color?: string;
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ export class StateBadge extends LitElement {
|
|||||||
const domain = this.stateObj
|
const domain = this.stateObj
|
||||||
? computeStateDomain(this.stateObj)
|
? computeStateDomain(this.stateObj)
|
||||||
: undefined;
|
: undefined;
|
||||||
return this.stateColor || (domain === "light" && this.stateColor !== false);
|
return this.stateColor ?? domain === "light";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
@@ -156,6 +156,7 @@ class HaClimateState extends LitElement {
|
|||||||
|
|
||||||
.current {
|
.current {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-label {
|
.state-label {
|
||||||
|
@@ -127,9 +127,11 @@ export class HaControlButton extends LitElement {
|
|||||||
opacity 180ms ease-in-out;
|
opacity 180ms ease-in-out;
|
||||||
opacity: var(--control-button-background-opacity);
|
opacity: var(--control-button-background-opacity);
|
||||||
}
|
}
|
||||||
.button ::slotted(*) {
|
.button {
|
||||||
transition: color 180ms ease-in-out;
|
transition: color 180ms ease-in-out;
|
||||||
color: var(--control-button-icon-color);
|
color: var(--control-button-icon-color);
|
||||||
|
}
|
||||||
|
.button ::slotted(*) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.button:disabled {
|
.button:disabled {
|
||||||
|
@@ -273,9 +273,13 @@ export class HaControlNumberButton extends LitElement {
|
|||||||
}
|
}
|
||||||
.button.minus {
|
.button.minus {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
inset-inline-end: initial;
|
||||||
}
|
}
|
||||||
.button.plus {
|
.button.plus {
|
||||||
right: 0;
|
right: 0;
|
||||||
|
inset-inline-start: initial;
|
||||||
|
inset-inline-end: 0;
|
||||||
}
|
}
|
||||||
.unit {
|
.unit {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import { FormfieldBase } from "@material/mwc-formfield/mwc-formfield-base";
|
import { FormfieldBase } from "@material/mwc-formfield/mwc-formfield-base";
|
||||||
import { styles } from "@material/mwc-formfield/mwc-formfield.css";
|
import { styles } from "@material/mwc-formfield/mwc-formfield.css";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("ha-formfield")
|
@customElement("ha-formfield")
|
||||||
export class HaFormfield extends FormfieldBase {
|
export class HaFormfield extends FormfieldBase {
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
protected _labelClick() {
|
protected _labelClick() {
|
||||||
const input = this.input as HTMLInputElement | undefined;
|
const input = this.input as HTMLInputElement | undefined;
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
@@ -44,6 +46,9 @@ export class HaFormfield extends FormfieldBase {
|
|||||||
padding-inline-start: 4px;
|
padding-inline-start: 4px;
|
||||||
padding-inline-end: 0;
|
padding-inline-end: 0;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) label {
|
||||||
|
color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -136,6 +136,8 @@ class HaMenuButton extends LitElement {
|
|||||||
height: 12px;
|
height: 12px;
|
||||||
top: 9px;
|
top: 9px;
|
||||||
right: 7px;
|
right: 7px;
|
||||||
|
inset-inline-end: 7px;
|
||||||
|
inset-inline-start: initial;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid var(--app-header-background-color);
|
border: 2px solid var(--app-header-background-color);
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,9 @@ class HaMetric extends LitElement {
|
|||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading"> ${this.heading} </span>
|
<span slot="heading"> ${this.heading} </span>
|
||||||
<div slot="description" .title=${this.tooltip ?? ""}>
|
<div slot="description" .title=${this.tooltip ?? ""}>
|
||||||
<span class="value"> ${roundedValue} % </span>
|
<span class="value">
|
||||||
|
<div>${roundedValue} %</div>
|
||||||
|
</span>
|
||||||
<ha-bar
|
<ha-bar
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
"target-warning": roundedValue > 50,
|
"target-warning": roundedValue > 50,
|
||||||
@@ -70,6 +72,10 @@ class HaMetric extends LitElement {
|
|||||||
padding-inline-start: initial;
|
padding-inline-start: initial;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.value > div {
|
||||||
|
direction: ltr;
|
||||||
|
text-align: var(--float-start);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -186,6 +186,8 @@ class HaQrScanner extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
inset-inline-end: 8px;
|
||||||
|
inset-inline-start: initial;
|
||||||
background: #727272b2;
|
background: #727272b2;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
@@ -102,7 +102,10 @@ export class HaSelectSelector extends LitElement {
|
|||||||
${this.label}
|
${this.label}
|
||||||
${options.map(
|
${options.map(
|
||||||
(item: SelectOption) => html`
|
(item: SelectOption) => html`
|
||||||
<ha-formfield .label=${item.label}>
|
<ha-formfield
|
||||||
|
.label=${item.label}
|
||||||
|
.disabled=${item.disabled || this.disabled}
|
||||||
|
>
|
||||||
<ha-radio
|
<ha-radio
|
||||||
.checked=${item.value === this.value}
|
.checked=${item.value === this.value}
|
||||||
.value=${item.value}
|
.value=${item.value}
|
||||||
|
@@ -93,6 +93,8 @@ export class HaServiceControl extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public hidePicker = false;
|
@property({ type: Boolean, reflect: true }) public hidePicker = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public hideDescription = false;
|
||||||
|
|
||||||
@state() private _value!: this["value"];
|
@state() private _value!: this["value"];
|
||||||
|
|
||||||
@state() private _checkedKeys = new Set();
|
@state() private _checkedKeys = new Set();
|
||||||
@@ -373,7 +375,8 @@ export class HaServiceControl extends LitElement {
|
|||||||
)) ||
|
)) ||
|
||||||
serviceData?.description;
|
serviceData?.description;
|
||||||
|
|
||||||
return html`${this.hidePicker
|
return html`
|
||||||
|
${this.hidePicker
|
||||||
? nothing
|
? nothing
|
||||||
: html`<ha-service-picker
|
: html`<ha-service-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -381,29 +384,33 @@ export class HaServiceControl extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._serviceChanged}
|
@value-changed=${this._serviceChanged}
|
||||||
></ha-service-picker>`}
|
></ha-service-picker>`}
|
||||||
<div class="description">
|
${this.hideDescription
|
||||||
${description ? html`<p>${description}</p>` : ""}
|
? nothing
|
||||||
${this._manifest
|
: html`
|
||||||
? html` <a
|
<div class="description">
|
||||||
href=${this._manifest.is_built_in
|
${description ? html`<p>${description}</p>` : ""}
|
||||||
? documentationUrl(
|
${this._manifest
|
||||||
this.hass,
|
? html` <a
|
||||||
`/integrations/${this._manifest.domain}`
|
href=${this._manifest.is_built_in
|
||||||
)
|
? documentationUrl(
|
||||||
: this._manifest.documentation}
|
this.hass,
|
||||||
title=${this.hass.localize(
|
`/integrations/${this._manifest.domain}`
|
||||||
"ui.components.service-control.integration_doc"
|
)
|
||||||
)}
|
: this._manifest.documentation}
|
||||||
target="_blank"
|
title=${this.hass.localize(
|
||||||
rel="noreferrer"
|
"ui.components.service-control.integration_doc"
|
||||||
>
|
)}
|
||||||
<ha-icon-button
|
target="_blank"
|
||||||
.path=${mdiHelpCircle}
|
rel="noreferrer"
|
||||||
class="help-icon"
|
>
|
||||||
></ha-icon-button>
|
<ha-icon-button
|
||||||
</a>`
|
.path=${mdiHelpCircle}
|
||||||
: ""}
|
class="help-icon"
|
||||||
</div>
|
></ha-icon-button>
|
||||||
|
</a>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
${serviceData && "target" in serviceData
|
${serviceData && "target" in serviceData
|
||||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
${hasOptional
|
${hasOptional
|
||||||
@@ -517,7 +524,8 @@ export class HaServiceControl extends LitElement {
|
|||||||
></ha-selector>
|
></ha-selector>
|
||||||
</ha-settings-row>`
|
</ha-settings-row>`
|
||||||
: "";
|
: "";
|
||||||
})}`;
|
})}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _localizeValueCallback = (key: string) => {
|
private _localizeValueCallback = (key: string) => {
|
||||||
|
@@ -114,11 +114,14 @@ class HaServicePicker extends LitElement {
|
|||||||
if (!filter) {
|
if (!filter) {
|
||||||
return processedServices;
|
return processedServices;
|
||||||
}
|
}
|
||||||
return processedServices.filter(
|
const split_filter = filter.split(" ");
|
||||||
(service) =>
|
return processedServices.filter((service) => {
|
||||||
service.service.toLowerCase().includes(filter) ||
|
const lower_service_name = service.name.toLowerCase();
|
||||||
service.name?.toLowerCase().includes(filter)
|
const lower_service = service.service.toLowerCase();
|
||||||
);
|
return split_filter.every(
|
||||||
|
(f) => lower_service_name.includes(f) || lower_service.includes(f)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1010,8 +1010,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
.profile paper-icon-item {
|
.profile paper-icon-item {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
margin-inline-start: 4px;
|
padding-inline-start: 4px;
|
||||||
margin-inline-end: auto;
|
padding-inline-end: auto;
|
||||||
}
|
}
|
||||||
.profile .item-text {
|
.profile .item-text {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
@@ -1040,6 +1040,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 14px;
|
bottom: 14px;
|
||||||
left: 26px;
|
left: 26px;
|
||||||
|
inset-inline-start: 26px;
|
||||||
|
inset-inline-end: initial;
|
||||||
font-size: 0.65em;
|
font-size: 0.65em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,9 +14,16 @@ declare global {
|
|||||||
oldPath?: ItemPath;
|
oldPath?: ItemPath;
|
||||||
newPath?: ItemPath;
|
newPath?: ItemPath;
|
||||||
};
|
};
|
||||||
|
"drag-start": undefined;
|
||||||
|
"drag-end": undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HaSortableOptions = Omit<
|
||||||
|
SortableInstance.SortableOptions,
|
||||||
|
"onStart" | "onChoose" | "onEnd"
|
||||||
|
>;
|
||||||
|
|
||||||
@customElement("ha-sortable")
|
@customElement("ha-sortable")
|
||||||
export class HaSortable extends LitElement {
|
export class HaSortable extends LitElement {
|
||||||
private _sortable?: SortableInstance;
|
private _sortable?: SortableInstance;
|
||||||
@@ -36,14 +43,17 @@ export class HaSortable extends LitElement {
|
|||||||
@property({ type: String, attribute: "handle-selector" })
|
@property({ type: String, attribute: "handle-selector" })
|
||||||
public handleSelector?: string;
|
public handleSelector?: string;
|
||||||
|
|
||||||
@property({ type: String, attribute: "group" })
|
@property({ type: String })
|
||||||
public group?: string;
|
public group?: string | SortableInstance.GroupOptions;
|
||||||
|
|
||||||
@property({ type: Number, attribute: "swap-threshold" })
|
|
||||||
public swapThreshold?: number;
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "invert-swap" })
|
@property({ type: Boolean, attribute: "invert-swap" })
|
||||||
public invertSwap?: boolean;
|
public invertSwap: boolean = false;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public options?: HaSortableOptions;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public rollback: boolean = true;
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues<this>) {
|
protected updated(changedProperties: PropertyValues<this>) {
|
||||||
if (changedProperties.has("disabled")) {
|
if (changedProperties.has("disabled")) {
|
||||||
@@ -72,6 +82,9 @@ export class HaSortable extends LitElement {
|
|||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._shouldBeDestroy = false;
|
this._shouldBeDestroy = false;
|
||||||
|
if (this.hasUpdated) {
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createRenderRoot() {
|
protected createRenderRoot() {
|
||||||
@@ -114,26 +127,20 @@ export class HaSortable extends LitElement {
|
|||||||
|
|
||||||
const options: SortableInstance.Options = {
|
const options: SortableInstance.Options = {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
swapThreshold: 1,
|
...this.options,
|
||||||
onChoose: this._handleChoose,
|
onChoose: this._handleChoose,
|
||||||
|
onStart: this._handleStart,
|
||||||
onEnd: this._handleEnd,
|
onEnd: this._handleEnd,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.draggableSelector) {
|
if (this.draggableSelector) {
|
||||||
options.draggable = this.draggableSelector;
|
options.draggable = this.draggableSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.swapThreshold !== undefined) {
|
|
||||||
options.swapThreshold = this.swapThreshold;
|
|
||||||
}
|
|
||||||
if (this.invertSwap !== undefined) {
|
|
||||||
options.invertSwap = this.invertSwap;
|
|
||||||
}
|
|
||||||
if (this.handleSelector) {
|
if (this.handleSelector) {
|
||||||
options.handle = this.handleSelector;
|
options.handle = this.handleSelector;
|
||||||
}
|
}
|
||||||
if (this.draggableSelector) {
|
if (this.invertSwap !== undefined) {
|
||||||
options.draggable = this.draggableSelector;
|
options.invertSwap = this.invertSwap;
|
||||||
}
|
}
|
||||||
if (this.group) {
|
if (this.group) {
|
||||||
options.group = this.group;
|
options.group = this.group;
|
||||||
@@ -143,8 +150,9 @@ export class HaSortable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleEnd = async (evt: SortableEvent) => {
|
private _handleEnd = async (evt: SortableEvent) => {
|
||||||
|
fireEvent(this, "drag-end");
|
||||||
// put back in original location
|
// put back in original location
|
||||||
if ((evt.item as any).placeholder) {
|
if (this.rollback && (evt.item as any).placeholder) {
|
||||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||||
delete (evt.item as any).placeholder;
|
delete (evt.item as any).placeholder;
|
||||||
}
|
}
|
||||||
@@ -170,7 +178,12 @@ export class HaSortable extends LitElement {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _handleStart = () => {
|
||||||
|
fireEvent(this, "drag-start");
|
||||||
|
};
|
||||||
|
|
||||||
private _handleChoose = (evt: SortableEvent) => {
|
private _handleChoose = (evt: SortableEvent) => {
|
||||||
|
if (!this.rollback) return;
|
||||||
(evt.item as any).placeholder = document.createComment("sort-placeholder");
|
(evt.item as any).placeholder = document.createComment("sort-placeholder");
|
||||||
evt.item.after((evt.item as any).placeholder);
|
evt.item.after((evt.item as any).placeholder);
|
||||||
};
|
};
|
||||||
|
@@ -90,7 +90,7 @@ export class HaTextField extends TextFieldBase {
|
|||||||
padding-right: var(--text-field-suffix-padding-right, 0px);
|
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||||
padding-inline-start: var(--text-field-suffix-padding-left, 12px);
|
padding-inline-start: var(--text-field-suffix-padding-left, 12px);
|
||||||
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
|
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
|
||||||
direction: var(--direction);
|
direction: ltr;
|
||||||
}
|
}
|
||||||
.mdc-text-field--with-leading-icon {
|
.mdc-text-field--with-leading-icon {
|
||||||
padding-inline-start: var(--text-field-suffix-padding-left, 0px);
|
padding-inline-start: var(--text-field-suffix-padding-left, 0px);
|
||||||
@@ -199,7 +199,6 @@ export class HaTextField extends TextFieldBase {
|
|||||||
// safari workaround - must be explicit
|
// safari workaround - must be explicit
|
||||||
mainWindow.document.dir === "rtl"
|
mainWindow.document.dir === "rtl"
|
||||||
? css`
|
? css`
|
||||||
.mdc-text-field__affix--suffix,
|
|
||||||
.mdc-text-field--with-leading-icon,
|
.mdc-text-field--with-leading-icon,
|
||||||
.mdc-text-field__icon--leading,
|
.mdc-text-field__icon--leading,
|
||||||
.mdc-floating-label,
|
.mdc-floating-label,
|
||||||
|
@@ -1,35 +1,8 @@
|
|||||||
import "@polymer/paper-toast/paper-toast";
|
|
||||||
import type { PaperToastElement } from "@polymer/paper-toast/paper-toast";
|
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import type { Constructor } from "../types";
|
import { Snackbar } from "@material/mwc-snackbar/mwc-snackbar";
|
||||||
|
|
||||||
const PaperToast = customElements.get(
|
|
||||||
"paper-toast"
|
|
||||||
) as Constructor<PaperToastElement>;
|
|
||||||
|
|
||||||
@customElement("ha-toast")
|
@customElement("ha-toast")
|
||||||
export class HaToast extends PaperToast {
|
export class HaToast extends Snackbar {}
|
||||||
private _resizeListener?: (obj: { matches: boolean }) => unknown;
|
|
||||||
|
|
||||||
private _mediaq?: MediaQueryList;
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
if (!this._resizeListener) {
|
|
||||||
this._resizeListener = (ev) =>
|
|
||||||
this.classList.toggle("fit-bottom", ev.matches);
|
|
||||||
this._mediaq = window.matchMedia("(max-width: 599px");
|
|
||||||
}
|
|
||||||
this._mediaq!.addListener(this._resizeListener);
|
|
||||||
this._resizeListener(this._mediaq!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._mediaq!.removeListener(this._resizeListener!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@@ -223,7 +223,6 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
ha-media-player-browse {
|
ha-media-player-browse {
|
||||||
--media-browser-max-height: calc(100vh - 65px);
|
--media-browser-max-height: calc(100vh - 65px);
|
||||||
direction: ltr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.opened) ha-media-player-browse {
|
:host(.opened) ha-media-player-browse {
|
||||||
|
@@ -879,6 +879,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
|
@@ -163,21 +163,22 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
|
${error
|
||||||
|
? html`<div class="error">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.error",
|
||||||
|
{
|
||||||
|
error: error,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
${result
|
${result
|
||||||
? html`${this.hass!.localize(
|
? html`${this.hass!.localize(
|
||||||
"ui.panel.config.automation.trace.path.result"
|
"ui.panel.config.automation.trace.path.result"
|
||||||
)}
|
)}
|
||||||
<pre>${dump(result)}</pre>`
|
<pre>${dump(result)}</pre>`
|
||||||
: error
|
: nothing}
|
||||||
? html`<div class="error">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.config.automation.trace.path.error",
|
|
||||||
{
|
|
||||||
error: error,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: nothing}
|
|
||||||
${Object.keys(rest).length === 0
|
${Object.keys(rest).length === 0
|
||||||
? nothing
|
? nothing
|
||||||
: html`<pre>${dump(rest)}</pre>`}
|
: html`<pre>${dump(rest)}</pre>`}
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
|
import { mdiExclamationThick } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
html,
|
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
svg,
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
|
svg,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { NODE_SIZE, SPACING } from "./hat-graph-const";
|
|
||||||
import { isSafari } from "../../util/is_safari";
|
import { isSafari } from "../../util/is_safari";
|
||||||
|
import { NODE_SIZE, SPACING } from "./hat-graph-const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @attribute active
|
* @attribute active
|
||||||
@@ -21,6 +22,8 @@ export class HatGraphNode extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public error = false;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) notEnabled = false;
|
@property({ reflect: true, type: Boolean }) notEnabled = false;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) graphStart = false;
|
@property({ reflect: true, type: Boolean }) graphStart = false;
|
||||||
@@ -65,16 +68,28 @@ export class HatGraphNode extends LitElement {
|
|||||||
`}
|
`}
|
||||||
<g class="node">
|
<g class="node">
|
||||||
<circle cx="0" cy="0" r=${NODE_SIZE / 2} />
|
<circle cx="0" cy="0" r=${NODE_SIZE / 2} />
|
||||||
|
${this.error
|
||||||
|
? svg`
|
||||||
|
<g class="error">
|
||||||
|
<circle
|
||||||
|
cx="-12"
|
||||||
|
cy=${-NODE_SIZE / 2}
|
||||||
|
r="8"
|
||||||
|
></circle>
|
||||||
|
<path transform="translate(-18 -21) scale(.5)" class="exclamation" d=${mdiExclamationThick}/>
|
||||||
|
</g>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
${this.badge
|
${this.badge
|
||||||
? svg`
|
? svg`
|
||||||
<g class="number">
|
<g class="number">
|
||||||
<circle
|
<circle
|
||||||
cx="8"
|
cx="12"
|
||||||
cy=${-NODE_SIZE / 2}
|
cy=${-NODE_SIZE / 2}
|
||||||
r="8"
|
r="8"
|
||||||
></circle>
|
></circle>
|
||||||
<text
|
<text
|
||||||
x="8"
|
x="12"
|
||||||
y=${-NODE_SIZE / 2}
|
y=${-NODE_SIZE / 2}
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
alignment-baseline="middle"
|
alignment-baseline="middle"
|
||||||
@@ -82,7 +97,7 @@ export class HatGraphNode extends LitElement {
|
|||||||
</g>
|
</g>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<g style="pointer-events: none" transform="translate(${-12} ${-12})">
|
<g style="pointer-events: none" transform="translate(-12 -12)">
|
||||||
${this.iconPath
|
${this.iconPath
|
||||||
? svg`<path class="icon" d=${this.iconPath}/>`
|
? svg`<path class="icon" d=${this.iconPath}/>`
|
||||||
: svg`<foreignObject><span class="icon"><slot name="icon"></slot></span></foreignObject>`}
|
: svg`<foreignObject><span class="icon"><slot name="icon"></slot></span></foreignObject>`}
|
||||||
@@ -143,13 +158,22 @@ export class HatGraphNode extends LitElement {
|
|||||||
fill: var(--background-clr);
|
fill: var(--background-clr);
|
||||||
stroke: var(--circle-clr, var(--stroke-clr));
|
stroke: var(--circle-clr, var(--stroke-clr));
|
||||||
}
|
}
|
||||||
|
.error circle {
|
||||||
|
fill: var(--error-color);
|
||||||
|
stroke: none;
|
||||||
|
stroke-width: 0;
|
||||||
|
}
|
||||||
|
.error .exclamation {
|
||||||
|
fill: var(--text-primary-color);
|
||||||
|
}
|
||||||
.number circle {
|
.number circle {
|
||||||
fill: var(--track-clr);
|
fill: var(--track-clr);
|
||||||
stroke: none;
|
stroke: none;
|
||||||
stroke-width: 0;
|
stroke-width: 0;
|
||||||
}
|
}
|
||||||
.number text {
|
.number text {
|
||||||
font-size: smaller;
|
font-size: 10px;
|
||||||
|
fill: var(--text-primary-color);
|
||||||
}
|
}
|
||||||
path.icon {
|
path.icon {
|
||||||
fill: var(--icon-clr);
|
fill: var(--icon-clr);
|
||||||
|
@@ -93,6 +93,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
?active=${this.selected === path}
|
?active=${this.selected === path}
|
||||||
.iconPath=${mdiAsterisk}
|
.iconPath=${mdiAsterisk}
|
||||||
.notEnabled=${config.enabled === false}
|
.notEnabled=${config.enabled === false}
|
||||||
|
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||||
tabindex=${track ? "0" : "-1"}
|
tabindex=${track ? "0" : "-1"}
|
||||||
></hat-graph-node>
|
></hat-graph-node>
|
||||||
`;
|
`;
|
||||||
@@ -171,6 +172,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
?track=${trace !== undefined}
|
?track=${trace !== undefined}
|
||||||
?active=${this.selected === path}
|
?active=${this.selected === path}
|
||||||
.notEnabled=${disabled || config.enabled === false}
|
.notEnabled=${disabled || config.enabled === false}
|
||||||
|
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||||
slot="head"
|
slot="head"
|
||||||
nofocus
|
nofocus
|
||||||
></hat-graph-node>
|
></hat-graph-node>
|
||||||
@@ -424,6 +426,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
?track=${path in this.trace.trace}
|
?track=${path in this.trace.trace}
|
||||||
?active=${this.selected === path}
|
?active=${this.selected === path}
|
||||||
.notEnabled=${disabled || node.enabled === false}
|
.notEnabled=${disabled || node.enabled === false}
|
||||||
|
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||||
>
|
>
|
||||||
${node.service
|
${node.service
|
||||||
@@ -451,6 +454,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
?track=${path in this.trace.trace}
|
?track=${path in this.trace.trace}
|
||||||
?active=${this.selected === path}
|
?active=${this.selected === path}
|
||||||
.notEnabled=${disabled || node.enabled === false}
|
.notEnabled=${disabled || node.enabled === false}
|
||||||
|
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||||
></hat-graph-node>
|
></hat-graph-node>
|
||||||
`;
|
`;
|
||||||
@@ -517,6 +521,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
?track=${path in this.trace.trace}
|
?track=${path in this.trace.trace}
|
||||||
?active=${this.selected === path}
|
?active=${this.selected === path}
|
||||||
|
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||||
.notEnabled=${disabled || node.enabled === false}
|
.notEnabled=${disabled || node.enabled === false}
|
||||||
></hat-graph-node>
|
></hat-graph-node>
|
||||||
`;
|
`;
|
||||||
|
@@ -153,7 +153,7 @@ class LogbookRenderer {
|
|||||||
|
|
||||||
const parts: TemplateResult[] = [];
|
const parts: TemplateResult[] = [];
|
||||||
|
|
||||||
let i;
|
let i: number;
|
||||||
|
|
||||||
for (
|
for (
|
||||||
i = 0;
|
i = 0;
|
||||||
@@ -232,7 +232,7 @@ class ActionRenderer {
|
|||||||
const value = this._getItem(index);
|
const value = this._getItem(index);
|
||||||
|
|
||||||
if (renderAllIterations) {
|
if (renderAllIterations) {
|
||||||
let i;
|
let i: number = 0;
|
||||||
value.forEach((item) => {
|
value.forEach((item) => {
|
||||||
i = this._renderIteration(index, item, actionType);
|
i = this._renderIteration(index, item, actionType);
|
||||||
});
|
});
|
||||||
@@ -270,7 +270,12 @@ class ActionRenderer {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
path,
|
path,
|
||||||
`Unable to extract path ${path}. Download trace and report as bug`
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.path_error",
|
||||||
|
{
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
}
|
||||||
@@ -324,20 +329,22 @@ class ActionRenderer {
|
|||||||
private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number {
|
private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number {
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
triggerStep.path,
|
triggerStep.path,
|
||||||
`${
|
this.hass.localize(
|
||||||
triggerStep.changed_variables.trigger.alias
|
"ui.panel.config.automation.trace.messages.triggered_by",
|
||||||
? `${triggerStep.changed_variables.trigger.alias} triggered`
|
{
|
||||||
: "Triggered"
|
triggeredBy: triggerStep.changed_variables.trigger?.alias
|
||||||
} ${
|
? "alias"
|
||||||
triggerStep.path === "trigger"
|
: "other",
|
||||||
? "manually"
|
alias: triggerStep.changed_variables.trigger?.alias,
|
||||||
: `by the ${this.trace.trigger}`
|
triggeredPath: triggerStep.path === "trigger" ? "manual" : "trigger",
|
||||||
} at
|
trigger: this.trace.trigger,
|
||||||
${formatDateTimeWithSeconds(
|
time: formatDateTimeWithSeconds(
|
||||||
new Date(triggerStep.timestamp),
|
new Date(triggerStep.timestamp),
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
this.hass.config
|
this.hass.config
|
||||||
)}`,
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
mdiCircle
|
mdiCircle
|
||||||
);
|
);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
@@ -367,12 +374,17 @@ class ActionRenderer {
|
|||||||
this.keys[index]
|
this.keys[index]
|
||||||
) as ChooseAction;
|
) as ChooseAction;
|
||||||
const disabled = chooseConfig.enabled === false;
|
const disabled = chooseConfig.enabled === false;
|
||||||
const name = chooseConfig.alias || "Choose";
|
const name =
|
||||||
|
chooseConfig.alias ||
|
||||||
|
this.hass.localize("ui.panel.config.automation.trace.messages.choose");
|
||||||
|
|
||||||
if (defaultExecuted) {
|
if (defaultExecuted) {
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
choosePath,
|
choosePath,
|
||||||
`${name}: Default action executed`,
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.default_action_executed",
|
||||||
|
{ name: name }
|
||||||
|
),
|
||||||
undefined,
|
undefined,
|
||||||
disabled
|
disabled
|
||||||
);
|
);
|
||||||
@@ -385,8 +397,17 @@ class ActionRenderer {
|
|||||||
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
|
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
|
||||||
) as ChooseActionChoice | undefined;
|
) as ChooseActionChoice | undefined;
|
||||||
const choiceName = choiceConfig
|
const choiceName = choiceConfig
|
||||||
? `${choiceConfig.alias || `Option ${choiceNumeric}`} executed`
|
? `${
|
||||||
: `Error: ${chooseTrace.error}`;
|
choiceConfig.alias ||
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.option_executed",
|
||||||
|
{ option: choiceNumeric }
|
||||||
|
)
|
||||||
|
}`
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.error",
|
||||||
|
{ error: chooseTrace.error }
|
||||||
|
);
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
choosePath,
|
choosePath,
|
||||||
`${name}: ${choiceName}`,
|
`${name}: ${choiceName}`,
|
||||||
@@ -396,13 +417,16 @@ class ActionRenderer {
|
|||||||
} else {
|
} else {
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
choosePath,
|
choosePath,
|
||||||
`${name}: No action taken`,
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.no_action_executed",
|
||||||
|
{ name: name }
|
||||||
|
),
|
||||||
undefined,
|
undefined,
|
||||||
disabled
|
disabled
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let i;
|
let i: number;
|
||||||
|
|
||||||
// Skip over conditions
|
// Skip over conditions
|
||||||
for (i = index + 1; i < this.keys.length; i++) {
|
for (i = index + 1; i < this.keys.length; i++) {
|
||||||
@@ -479,26 +503,38 @@ class ActionRenderer {
|
|||||||
const ifTrace = this._getItem(index)[0] as IfActionTraceStep;
|
const ifTrace = this._getItem(index)[0] as IfActionTraceStep;
|
||||||
const ifConfig = this._getDataFromPath(this.keys[index]) as IfAction;
|
const ifConfig = this._getDataFromPath(this.keys[index]) as IfAction;
|
||||||
const disabled = ifConfig.enabled === false;
|
const disabled = ifConfig.enabled === false;
|
||||||
const name = ifConfig.alias || "If";
|
const name =
|
||||||
|
ifConfig.alias ||
|
||||||
|
this.hass.localize("ui.panel.config.automation.trace.messages.if");
|
||||||
|
|
||||||
if (ifTrace.result?.choice) {
|
if (ifTrace.result?.choice) {
|
||||||
const choiceConfig = this._getDataFromPath(
|
const choiceConfig = this._getDataFromPath(
|
||||||
`${this.keys[index]}/${ifTrace.result.choice}/`
|
`${this.keys[index]}/${ifTrace.result.choice}/`
|
||||||
) as any;
|
) as any;
|
||||||
const choiceName = choiceConfig
|
const choiceName = choiceConfig
|
||||||
? `${choiceConfig.alias || `${ifTrace.result.choice} action executed`}`
|
? choiceConfig.alias ||
|
||||||
: `Error: ${ifTrace.error}`;
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.action_executed",
|
||||||
|
{ action: ifTrace.result.choice }
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.error",
|
||||||
|
{ error: ifTrace.error }
|
||||||
|
);
|
||||||
this._renderEntry(ifPath, `${name}: ${choiceName}`, undefined, disabled);
|
this._renderEntry(ifPath, `${name}: ${choiceName}`, undefined, disabled);
|
||||||
} else {
|
} else {
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
ifPath,
|
ifPath,
|
||||||
`${name}: No action taken`,
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.no_action_executed",
|
||||||
|
{ name: name }
|
||||||
|
),
|
||||||
undefined,
|
undefined,
|
||||||
disabled
|
disabled
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let i;
|
let i: number;
|
||||||
|
|
||||||
// Skip over conditions
|
// Skip over conditions
|
||||||
for (i = index + 1; i < this.keys.length; i++) {
|
for (i = index + 1; i < this.keys.length; i++) {
|
||||||
@@ -534,7 +570,11 @@ class ActionRenderer {
|
|||||||
|
|
||||||
const disabled = parallelConfig.enabled === false;
|
const disabled = parallelConfig.enabled === false;
|
||||||
|
|
||||||
const name = parallelConfig.alias || "Execute in parallel";
|
const name =
|
||||||
|
parallelConfig.alias ||
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.execute_in_parallel"
|
||||||
|
);
|
||||||
|
|
||||||
this._renderEntry(parallelPath, name, undefined, disabled);
|
this._renderEntry(parallelPath, name, undefined, disabled);
|
||||||
|
|
||||||
@@ -564,7 +604,11 @@ class ActionRenderer {
|
|||||||
this.entries.push(html`
|
this.entries.push(html`
|
||||||
<ha-timeline .icon=${icon} data-path=${path} .notEnabled=${disabled}>
|
<ha-timeline .icon=${icon} data-path=${path} .notEnabled=${disabled}>
|
||||||
${description}${disabled
|
${description}${disabled
|
||||||
? html`<span class="disabled"> (disabled)</span>`
|
? html`<span class="disabled">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.disabled"
|
||||||
|
)}</span
|
||||||
|
>`
|
||||||
: ""}
|
: ""}
|
||||||
</ha-timeline>
|
</ha-timeline>
|
||||||
`);
|
`);
|
||||||
@@ -636,13 +680,12 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
this.hass.config
|
this.hass.config
|
||||||
);
|
);
|
||||||
const renderRuntime = () => `(runtime:
|
const renderRuntime = () =>
|
||||||
${(
|
(
|
||||||
(new Date(this.trace!.timestamp.finish!).getTime() -
|
(new Date(this.trace!.timestamp.finish!).getTime() -
|
||||||
new Date(this.trace!.timestamp.start).getTime()) /
|
new Date(this.trace!.timestamp.start).getTime()) /
|
||||||
1000
|
1000
|
||||||
).toFixed(2)}
|
).toFixed(2);
|
||||||
seconds)`;
|
|
||||||
|
|
||||||
let entry: {
|
let entry: {
|
||||||
description: TemplateResult | string;
|
description: TemplateResult | string;
|
||||||
@@ -652,57 +695,90 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
|
|
||||||
if (this.trace.state === "running") {
|
if (this.trace.state === "running") {
|
||||||
entry = {
|
entry = {
|
||||||
description: "Still running",
|
description: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.still_running"
|
||||||
|
),
|
||||||
icon: mdiProgressClock,
|
icon: mdiProgressClock,
|
||||||
};
|
};
|
||||||
} else if (this.trace.state === "debugged") {
|
} else if (this.trace.state === "debugged") {
|
||||||
entry = {
|
entry = {
|
||||||
description: "Debugged",
|
description: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.debugged"
|
||||||
|
),
|
||||||
icon: mdiProgressWrench,
|
icon: mdiProgressWrench,
|
||||||
};
|
};
|
||||||
} else if (this.trace.script_execution === "finished") {
|
} else if (this.trace.script_execution === "finished") {
|
||||||
entry = {
|
entry = {
|
||||||
description: `Finished at ${renderFinishedAt()} ${renderRuntime()}`,
|
description: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.finished",
|
||||||
|
{
|
||||||
|
time: renderFinishedAt(),
|
||||||
|
executiontime: renderRuntime(),
|
||||||
|
}
|
||||||
|
),
|
||||||
icon: mdiCircle,
|
icon: mdiCircle,
|
||||||
};
|
};
|
||||||
} else if (this.trace.script_execution === "aborted") {
|
} else if (this.trace.script_execution === "aborted") {
|
||||||
entry = {
|
entry = {
|
||||||
description: `Aborted at ${renderFinishedAt()} ${renderRuntime()}`,
|
description: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.aborted",
|
||||||
|
{
|
||||||
|
time: renderFinishedAt(),
|
||||||
|
executiontime: renderRuntime(),
|
||||||
|
}
|
||||||
|
),
|
||||||
icon: mdiAlertCircle,
|
icon: mdiAlertCircle,
|
||||||
};
|
};
|
||||||
} else if (this.trace.script_execution === "cancelled") {
|
} else if (this.trace.script_execution === "cancelled") {
|
||||||
entry = {
|
entry = {
|
||||||
description: `Cancelled at ${renderFinishedAt()} ${renderRuntime()}`,
|
description: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.trace.messages.cancelled",
|
||||||
|
{
|
||||||
|
time: renderFinishedAt(),
|
||||||
|
executiontime: renderRuntime(),
|
||||||
|
}
|
||||||
|
),
|
||||||
icon: mdiAlertCircle,
|
icon: mdiAlertCircle,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let reason: string;
|
let message:
|
||||||
|
| "stopped_failed_conditions"
|
||||||
|
| "stopped_failed_single"
|
||||||
|
| "stopped_failed_max_runs"
|
||||||
|
| "stopped_error"
|
||||||
|
| "stopped_unknown_reason";
|
||||||
let isError = false;
|
let isError = false;
|
||||||
let extra: TemplateResult | undefined;
|
let extra: TemplateResult | undefined;
|
||||||
|
|
||||||
switch (this.trace.script_execution) {
|
switch (this.trace.script_execution) {
|
||||||
case "failed_conditions":
|
case "failed_conditions":
|
||||||
reason = "a condition failed";
|
message = "stopped_failed_conditions";
|
||||||
break;
|
break;
|
||||||
case "failed_single":
|
case "failed_single":
|
||||||
reason = "only a single execution is allowed";
|
message = "stopped_failed_single";
|
||||||
break;
|
break;
|
||||||
case "failed_max_runs":
|
case "failed_max_runs":
|
||||||
reason = "maximum number of parallel runs reached";
|
message = "stopped_failed_max_runs";
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
reason = "an error was encountered";
|
|
||||||
isError = true;
|
isError = true;
|
||||||
|
message = "stopped_error";
|
||||||
extra = html`<br /><br />${this.trace.error!}`;
|
extra = html`<br /><br />${this.trace.error!}`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
reason = `of unknown reason "${this.trace.script_execution}"`;
|
|
||||||
isError = true;
|
isError = true;
|
||||||
|
message = "stopped_unknown_reason";
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
description: html`Stopped because ${reason} at ${renderFinishedAt()}
|
description: html`${this.hass.localize(
|
||||||
${renderRuntime()}${extra || ""}`,
|
`ui.panel.config.automation.trace.messages.${message}`,
|
||||||
|
{
|
||||||
|
time: renderFinishedAt(),
|
||||||
|
executiontime: renderRuntime(),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
${extra || ""}`,
|
||||||
icon: mdiAlertCircle,
|
icon: mdiAlertCircle,
|
||||||
className: isError ? "error" : undefined,
|
className: isError ? "error" : undefined,
|
||||||
};
|
};
|
||||||
|
@@ -33,6 +33,7 @@ export interface DataEntryFlowStepForm {
|
|||||||
description_placeholders?: Record<string, string>;
|
description_placeholders?: Record<string, string>;
|
||||||
last_step: boolean | null;
|
last_step: boolean | null;
|
||||||
preview?: string;
|
preview?: string;
|
||||||
|
translation_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataEntryFlowStepExternal {
|
export interface DataEntryFlowStepExternal {
|
||||||
@@ -42,6 +43,7 @@ export interface DataEntryFlowStepExternal {
|
|||||||
step_id: string;
|
step_id: string;
|
||||||
url: string;
|
url: string;
|
||||||
description_placeholders: Record<string, string>;
|
description_placeholders: Record<string, string>;
|
||||||
|
translation_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataEntryFlowStepCreateEntry {
|
export interface DataEntryFlowStepCreateEntry {
|
||||||
@@ -53,6 +55,7 @@ export interface DataEntryFlowStepCreateEntry {
|
|||||||
result?: ConfigEntry;
|
result?: ConfigEntry;
|
||||||
description: string;
|
description: string;
|
||||||
description_placeholders?: Record<string, string>;
|
description_placeholders?: Record<string, string>;
|
||||||
|
translation_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataEntryFlowStepAbort {
|
export interface DataEntryFlowStepAbort {
|
||||||
@@ -61,6 +64,7 @@ export interface DataEntryFlowStepAbort {
|
|||||||
handler: string;
|
handler: string;
|
||||||
reason: string;
|
reason: string;
|
||||||
description_placeholders?: Record<string, string>;
|
description_placeholders?: Record<string, string>;
|
||||||
|
translation_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataEntryFlowStepProgress {
|
export interface DataEntryFlowStepProgress {
|
||||||
@@ -70,6 +74,7 @@ export interface DataEntryFlowStepProgress {
|
|||||||
step_id: string;
|
step_id: string;
|
||||||
progress_action: string;
|
progress_action: string;
|
||||||
description_placeholders?: Record<string, string>;
|
description_placeholders?: Record<string, string>;
|
||||||
|
translation_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataEntryFlowStepMenu {
|
export interface DataEntryFlowStepMenu {
|
||||||
@@ -80,6 +85,7 @@ export interface DataEntryFlowStepMenu {
|
|||||||
/** If array, use value to lookup translations in strings.json */
|
/** If array, use value to lookup translations in strings.json */
|
||||||
menu_options: string[] | Record<string, string>;
|
menu_options: string[] | Record<string, string>;
|
||||||
description_placeholders?: Record<string, string>;
|
description_placeholders?: Record<string, string>;
|
||||||
|
translation_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DataEntryFlowStep =
|
export type DataEntryFlowStep =
|
||||||
|
@@ -331,6 +331,9 @@ export const getReferencedStatisticIds = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!(includeTypes && !includeTypes.includes("device"))) {
|
||||||
|
statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption));
|
||||||
|
}
|
||||||
|
|
||||||
return statIDs;
|
return statIDs;
|
||||||
};
|
};
|
||||||
@@ -383,6 +386,7 @@ const getEnergyData = async (
|
|||||||
"solar",
|
"solar",
|
||||||
"battery",
|
"battery",
|
||||||
"gas",
|
"gas",
|
||||||
|
"device",
|
||||||
]);
|
]);
|
||||||
const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]);
|
const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]);
|
||||||
|
|
||||||
@@ -777,7 +781,7 @@ export const getEnergyGasUnit = (
|
|||||||
: "ft³";
|
: "ft³";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEnergyWaterUnit = (hass: HomeAssistant): string | undefined =>
|
export const getEnergyWaterUnit = (hass: HomeAssistant): string =>
|
||||||
hass.config.unit_system.length === "km" ? "L" : "gal";
|
hass.config.unit_system.length === "km" ? "L" : "gal";
|
||||||
|
|
||||||
export const energyStatisticHelpUrl =
|
export const energyStatisticHelpUrl =
|
||||||
|
@@ -81,7 +81,7 @@ export interface EntityHistoryState {
|
|||||||
/** attributes */
|
/** attributes */
|
||||||
a: { [key: string]: any };
|
a: { [key: string]: any };
|
||||||
/** last_changed; if set, also applies to lu */
|
/** last_changed; if set, also applies to lu */
|
||||||
lc: number;
|
lc?: number;
|
||||||
/** last_updated */
|
/** last_updated */
|
||||||
lu: number;
|
lu: number;
|
||||||
}
|
}
|
||||||
@@ -419,17 +419,37 @@ const BLANK_UNIT = " ";
|
|||||||
export const computeHistory = (
|
export const computeHistory = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
stateHistory: HistoryStates,
|
stateHistory: HistoryStates,
|
||||||
|
entityIds: string[],
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
sensorNumericalDeviceClasses: string[],
|
sensorNumericalDeviceClasses: string[],
|
||||||
splitDeviceClasses = false
|
splitDeviceClasses = false
|
||||||
): HistoryResult => {
|
): HistoryResult => {
|
||||||
const lineChartDevices: { [unit: string]: HistoryStates } = {};
|
const lineChartDevices: { [unit: string]: HistoryStates } = {};
|
||||||
const timelineDevices: TimelineEntity[] = [];
|
const timelineDevices: TimelineEntity[] = [];
|
||||||
if (!stateHistory) {
|
|
||||||
|
const localStateHistory: HistoryStates = {};
|
||||||
|
|
||||||
|
// Create a limited history from stateObj if entity has no recorded history.
|
||||||
|
const allEntities = new Set([...entityIds, ...Object.keys(stateHistory)]);
|
||||||
|
allEntities.forEach((entity) => {
|
||||||
|
if (entity in stateHistory) {
|
||||||
|
localStateHistory[entity] = stateHistory[entity];
|
||||||
|
} else if (hass.states[entity]) {
|
||||||
|
localStateHistory[entity] = [
|
||||||
|
{
|
||||||
|
s: hass.states[entity].state,
|
||||||
|
a: hass.states[entity].attributes,
|
||||||
|
lu: new Date(hass.states[entity].last_updated).getTime() / 1000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!localStateHistory) {
|
||||||
return { line: [], timeline: [] };
|
return { line: [], timeline: [] };
|
||||||
}
|
}
|
||||||
Object.keys(stateHistory).forEach((entityId) => {
|
Object.keys(localStateHistory).forEach((entityId) => {
|
||||||
const stateInfo = stateHistory[entityId];
|
const stateInfo = localStateHistory[entityId];
|
||||||
if (stateInfo.length === 0) {
|
if (stateInfo.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -198,8 +198,9 @@ export const entryIcon = async (
|
|||||||
if (entry.icon) {
|
if (entry.icon) {
|
||||||
return entry.icon;
|
return entry.icon;
|
||||||
}
|
}
|
||||||
|
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
||||||
const domain = computeDomain(entry.entity_id);
|
const domain = computeDomain(entry.entity_id);
|
||||||
return getEntityIcon(hass, domain, undefined, undefined, entry);
|
return getEntityIcon(hass, domain, stateObj, undefined, entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEntityIcon = async (
|
const getEntityIcon = async (
|
||||||
|
@@ -10,8 +10,10 @@ import {
|
|||||||
LovelaceCard,
|
LovelaceCard,
|
||||||
} from "../panels/lovelace/types";
|
} from "../panels/lovelace/types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { LovelaceSectionConfig } from "./lovelace/config/section";
|
||||||
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
|
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
|
||||||
import { LovelaceViewConfig } from "./lovelace/config/view";
|
import { LovelaceViewConfig } from "./lovelace/config/view";
|
||||||
|
import { HuiSection } from "../panels/lovelace/sections/hui-section";
|
||||||
|
|
||||||
export interface LovelacePanelConfig {
|
export interface LovelacePanelConfig {
|
||||||
mode: "yaml" | "storage";
|
mode: "yaml" | "storage";
|
||||||
@@ -24,10 +26,21 @@ export interface LovelaceViewElement extends HTMLElement {
|
|||||||
index?: number;
|
index?: number;
|
||||||
cards?: Array<LovelaceCard | HuiErrorCard>;
|
cards?: Array<LovelaceCard | HuiErrorCard>;
|
||||||
badges?: LovelaceBadge[];
|
badges?: LovelaceBadge[];
|
||||||
|
sections?: HuiSection[];
|
||||||
isStrategy: boolean;
|
isStrategy: boolean;
|
||||||
setConfig(config: LovelaceViewConfig): void;
|
setConfig(config: LovelaceViewConfig): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LovelaceSectionElement extends HTMLElement {
|
||||||
|
hass?: HomeAssistant;
|
||||||
|
lovelace?: Lovelace;
|
||||||
|
viewIndex?: number;
|
||||||
|
index?: number;
|
||||||
|
cards?: Array<LovelaceCard | HuiErrorCard>;
|
||||||
|
isStrategy: boolean;
|
||||||
|
setConfig(config: LovelaceSectionConfig): void;
|
||||||
|
}
|
||||||
|
|
||||||
type LovelaceUpdatedEvent = HassEventBase & {
|
type LovelaceUpdatedEvent = HassEventBase & {
|
||||||
event_type: "lovelace_updated";
|
event_type: "lovelace_updated";
|
||||||
data: {
|
data: {
|
||||||
|
26
src/data/lovelace/config/section.ts
Normal file
26
src/data/lovelace/config/section.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { LovelaceCardConfig } from "./card";
|
||||||
|
import type { LovelaceStrategyConfig } from "./strategy";
|
||||||
|
|
||||||
|
export interface LovelaceBaseSectionConfig {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
||||||
|
type?: string;
|
||||||
|
cards?: LovelaceCardConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceStrategySectionConfig
|
||||||
|
extends LovelaceBaseSectionConfig {
|
||||||
|
strategy: LovelaceStrategyConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LovelaceSectionRawConfig =
|
||||||
|
| LovelaceSectionConfig
|
||||||
|
| LovelaceStrategySectionConfig;
|
||||||
|
|
||||||
|
export function isStrategySection(
|
||||||
|
section: LovelaceSectionRawConfig
|
||||||
|
): section is LovelaceStrategySectionConfig {
|
||||||
|
return "strategy" in section;
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import type { LovelaceBadgeConfig } from "./badge";
|
import type { LovelaceBadgeConfig } from "./badge";
|
||||||
import type { LovelaceCardConfig } from "./card";
|
import type { LovelaceCardConfig } from "./card";
|
||||||
|
import type { LovelaceSectionRawConfig } from "./section";
|
||||||
import type { LovelaceStrategyConfig } from "./strategy";
|
import type { LovelaceStrategyConfig } from "./strategy";
|
||||||
|
|
||||||
export interface ShowViewConfig {
|
export interface ShowViewConfig {
|
||||||
@@ -23,6 +24,7 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
|
|||||||
type?: string;
|
type?: string;
|
||||||
badges?: Array<string | LovelaceBadgeConfig>;
|
badges?: Array<string | LovelaceBadgeConfig>;
|
||||||
cards?: LovelaceCardConfig[];
|
cards?: LovelaceCardConfig[];
|
||||||
|
sections?: LovelaceSectionRawConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {
|
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {
|
||||||
|
@@ -1,12 +1,17 @@
|
|||||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
|
import { stringCompare } from "../common/string/compare";
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
import { AreaRegistryEntry } from "./area_registry";
|
import { AreaRegistryEntry } from "./area_registry";
|
||||||
|
|
||||||
const fetchAreaRegistry = (conn: Connection) =>
|
const fetchAreaRegistry = (conn: Connection) =>
|
||||||
conn.sendMessagePromise<AreaRegistryEntry[]>({
|
conn
|
||||||
type: "config/area_registry/list",
|
.sendMessagePromise<AreaRegistryEntry[]>({
|
||||||
});
|
type: "config/area_registry/list",
|
||||||
|
})
|
||||||
|
.then((areas) =>
|
||||||
|
areas.sort((ent1, ent2) => stringCompare(ent1.name, ent2.name))
|
||||||
|
);
|
||||||
|
|
||||||
const subscribeAreaRegistryUpdates = (
|
const subscribeAreaRegistryUpdates = (
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
|
@@ -44,7 +44,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderAbortDescription(hass, step) {
|
renderAbortDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.abort.${step.reason}`,
|
`component.${step.translation_domain || step.handler}.config.abort.${step.reason}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export const showConfigFlowDialog = (
|
|||||||
renderShowFormStepHeader(hass, step) {
|
renderShowFormStepHeader(hass, step) {
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`component.${step.handler}.config.step.${step.step_id}.title`,
|
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.title`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
) || hass.localize(`component.${step.handler}.title`)
|
) || hass.localize(`component.${step.handler}.title`)
|
||||||
);
|
);
|
||||||
@@ -66,7 +66,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderShowFormStepDescription(hass, step) {
|
renderShowFormStepDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.step.${step.step_id}.description`,
|
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -84,7 +84,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderShowFormStepFieldHelper(hass, step, field) {
|
renderShowFormStepFieldHelper(hass, step, field) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.step.${step.step_id}.data_description.${field.name}`,
|
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.data_description.${field.name}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -95,7 +95,7 @@ export const showConfigFlowDialog = (
|
|||||||
renderShowFormStepFieldError(hass, step, error) {
|
renderShowFormStepFieldError(hass, step, error) {
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`component.${step.handler}.config.error.${error}`,
|
`component.${step.translation_domain || step.translation_domain || step.handler}.config.error.${error}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
) || error
|
) || error
|
||||||
);
|
);
|
||||||
@@ -131,7 +131,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderExternalStepDescription(hass, step) {
|
renderExternalStepDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.${step.step_id}.description`,
|
`component.${step.translation_domain || step.handler}.config.${step.step_id}.description`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderCreateEntryDescription(hass, step) {
|
renderCreateEntryDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.create_entry.${
|
`component.${step.translation_domain || step.handler}.config.create_entry.${
|
||||||
step.description || "default"
|
step.description || "default"
|
||||||
}`,
|
}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
@@ -190,7 +190,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderShowFormProgressDescription(hass, step) {
|
renderShowFormProgressDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.progress.${step.progress_action}`,
|
`component.${step.translation_domain || step.handler}.config.progress.${step.progress_action}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -210,7 +210,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderMenuDescription(hass, step) {
|
renderMenuDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${step.handler}.config.step.${step.step_id}.description`,
|
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -222,7 +222,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
renderMenuOption(hass, step, option) {
|
renderMenuOption(hass, step, option) {
|
||||||
return hass.localize(
|
return hass.localize(
|
||||||
`component.${step.handler}.config.step.${step.step_id}.menu_options.${option}`,
|
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.menu_options.${option}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -53,7 +53,7 @@ export const showOptionsFlowDialog = (
|
|||||||
|
|
||||||
renderAbortDescription(hass, step) {
|
renderAbortDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${configEntry.domain}.options.abort.${step.reason}`,
|
`component.${step.translation_domain || configEntry.domain}.options.abort.${step.reason}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export const showOptionsFlowDialog = (
|
|||||||
renderShowFormStepHeader(hass, step) {
|
renderShowFormStepHeader(hass, step) {
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`component.${configEntry.domain}.options.step.${step.step_id}.title`,
|
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.title`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
) || hass.localize(`ui.dialogs.options_flow.form.header`)
|
) || hass.localize(`ui.dialogs.options_flow.form.header`)
|
||||||
);
|
);
|
||||||
@@ -79,7 +79,7 @@ export const showOptionsFlowDialog = (
|
|||||||
|
|
||||||
renderShowFormStepDescription(hass, step) {
|
renderShowFormStepDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${configEntry.domain}.options.step.${step.step_id}.description`,
|
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.description`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -101,7 +101,7 @@ export const showOptionsFlowDialog = (
|
|||||||
|
|
||||||
renderShowFormStepFieldHelper(hass, step, field) {
|
renderShowFormStepFieldHelper(hass, step, field) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`,
|
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -112,7 +112,7 @@ export const showOptionsFlowDialog = (
|
|||||||
renderShowFormStepFieldError(hass, step, error) {
|
renderShowFormStepFieldError(hass, step, error) {
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`component.${configEntry.domain}.options.error.${error}`,
|
`component.${step.translation_domain || configEntry.domain}.options.error.${error}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
) || error
|
) || error
|
||||||
);
|
);
|
||||||
@@ -159,7 +159,7 @@ export const showOptionsFlowDialog = (
|
|||||||
|
|
||||||
renderShowFormProgressDescription(hass, step) {
|
renderShowFormProgressDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${configEntry.domain}.options.progress.${step.progress_action}`,
|
`component.${step.translation_domain || configEntry.domain}.options.progress.${step.progress_action}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -183,7 +183,7 @@ export const showOptionsFlowDialog = (
|
|||||||
|
|
||||||
renderMenuDescription(hass, step) {
|
renderMenuDescription(hass, step) {
|
||||||
const description = hass.localize(
|
const description = hass.localize(
|
||||||
`component.${configEntry.domain}.options.step.${step.step_id}.description`,
|
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.description`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
@@ -199,7 +199,7 @@ export const showOptionsFlowDialog = (
|
|||||||
|
|
||||||
renderMenuOption(hass, step, option) {
|
renderMenuOption(hass, step, option) {
|
||||||
return hass.localize(
|
return hass.localize(
|
||||||
`component.${configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`,
|
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`,
|
||||||
step.description_placeholders
|
step.description_placeholders
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -89,7 +89,12 @@ class DialogBox extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${confirmPrompt &&
|
${confirmPrompt &&
|
||||||
html`
|
html`
|
||||||
<mwc-button @click=${this._dismiss} slot="secondaryAction">
|
<mwc-button
|
||||||
|
@click=${this._dismiss}
|
||||||
|
slot="secondaryAction"
|
||||||
|
?dialogInitialFocus=${!this._params.prompt &&
|
||||||
|
this._params.destructive}
|
||||||
|
>
|
||||||
${this._params.dismissText
|
${this._params.dismissText
|
||||||
? this._params.dismissText
|
? this._params.dismissText
|
||||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||||
@@ -97,7 +102,8 @@ class DialogBox extends LitElement {
|
|||||||
`}
|
`}
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._confirm}
|
@click=${this._confirm}
|
||||||
?dialogInitialFocus=${!this._params.prompt}
|
?dialogInitialFocus=${!this._params.prompt &&
|
||||||
|
!this._params.destructive}
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
destructive: this._params.destructive || false,
|
destructive: this._params.destructive || false,
|
||||||
|
@@ -16,6 +16,8 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public stateOverride?: string;
|
@property({ attribute: false }) public stateOverride?: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public changedOverride?: number;
|
||||||
|
|
||||||
@state() private _absoluteTime = false;
|
@state() private _absoluteTime = false;
|
||||||
|
|
||||||
private _localizeState(): TemplateResult | string {
|
private _localizeState(): TemplateResult | string {
|
||||||
@@ -50,13 +52,13 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-absolute-time
|
<ha-absolute-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.last_changed}
|
.datetime=${this.changedOverride ?? this.stateObj.last_changed}
|
||||||
></ha-absolute-time>
|
></ha-absolute-time>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.last_changed}
|
.datetime=${this.changedOverride ?? this.stateObj.last_changed}
|
||||||
capitalize
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`}
|
`}
|
||||||
|
@@ -322,6 +322,8 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: -6px;
|
top: -6px;
|
||||||
right: -6px;
|
right: -6px;
|
||||||
|
inset-inline-end: -6px;
|
||||||
|
inset-inline-start: initial;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@@ -446,6 +446,8 @@ class LightRgbColorPicker extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
inset-inline-start: initial;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,6 +26,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
|
|||||||
"light",
|
"light",
|
||||||
"lock",
|
"lock",
|
||||||
"siren",
|
"siren",
|
||||||
|
"script",
|
||||||
"switch",
|
"switch",
|
||||||
"valve",
|
"valve",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
|
@@ -34,7 +34,6 @@ class MoreInfoPerson extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${!__DEMO__ &&
|
${!__DEMO__ &&
|
||||||
this.hass.user?.is_admin &&
|
this.hass.user?.is_admin &&
|
||||||
this.stateObj.state === "not_home" &&
|
|
||||||
this.stateObj.attributes.latitude &&
|
this.stateObj.attributes.latitude &&
|
||||||
this.stateObj.attributes.longitude
|
this.stateObj.attributes.longitude
|
||||||
? html`
|
? html`
|
||||||
|
@@ -1,51 +1,220 @@
|
|||||||
|
import { mdiPlay, mdiStop } from "@mdi/js";
|
||||||
|
import "@material/mwc-button";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import {
|
||||||
import { customElement, property } from "lit/decorators";
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-relative-time";
|
import "../../../components/ha-relative-time";
|
||||||
|
import "../../../components/ha-service-control";
|
||||||
|
import "../../../components/ha-control-button";
|
||||||
|
import "../../../components/ha-control-button-group";
|
||||||
|
import "../../../components/entity/state-info";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { canRun, ScriptEntity } from "../../../data/script";
|
||||||
|
import { isUnavailableState } from "../../../data/entity";
|
||||||
|
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||||
|
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||||
|
import "../components/ha-more-info-state-header";
|
||||||
|
|
||||||
@customElement("more-info-script")
|
@customElement("more-info-script")
|
||||||
class MoreInfoScript extends LitElement {
|
class MoreInfoScript extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: ScriptEntity;
|
||||||
|
|
||||||
|
@state() private _scriptData: Record<string, any> = {};
|
||||||
|
|
||||||
|
@state() private narrow = false;
|
||||||
|
|
||||||
|
private _unsubMediaQuery?: () => void;
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._unsubMediaQuery = listenMediaQuery(
|
||||||
|
"(max-width: 870px)",
|
||||||
|
(matches) => {
|
||||||
|
this.narrow = matches;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsubMediaQuery) {
|
||||||
|
this._unsubMediaQuery();
|
||||||
|
this._unsubMediaQuery = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
|
const fields =
|
||||||
|
this.hass.services.script[computeObjectId(this.stateObj.entity_id)]
|
||||||
|
?.fields;
|
||||||
|
|
||||||
|
const hasFields = fields && Object.keys(fields).length > 0;
|
||||||
|
|
||||||
|
const current = stateObj.attributes.current || 0;
|
||||||
|
const isQueued = stateObj.attributes.mode === "queued";
|
||||||
|
const isParallel = stateObj.attributes.mode === "parallel";
|
||||||
|
const hasQueue = isQueued && current > 1;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hr />
|
<ha-more-info-state-header
|
||||||
<div class="flex">
|
.stateObj=${stateObj}
|
||||||
<div>
|
.hass=${this.hass}
|
||||||
${this.hass.localize(
|
.stateOverride=${current > 0
|
||||||
"ui.dialogs.more_info_control.script.last_triggered"
|
? isParallel && current > 1
|
||||||
)}:
|
? this.hass.localize("ui.card.script.running_parallel", {
|
||||||
</div>
|
active: current,
|
||||||
${this.stateObj.attributes.last_triggered
|
})
|
||||||
|
: this.hass.localize("ui.card.script.running_single")
|
||||||
|
: this.hass.localize("ui.card.script.idle")}
|
||||||
|
.changedOverride=${this.stateObj.attributes.last_triggered || 0}
|
||||||
|
></ha-more-info-state-header>
|
||||||
|
|
||||||
|
<div class=${`queue ${hasQueue ? "has-queue" : ""}`}>
|
||||||
|
${hasQueue
|
||||||
? html`
|
? html`
|
||||||
<ha-relative-time
|
${this.hass.localize("ui.card.script.running_queued", {
|
||||||
.hass=${this.hass}
|
queued: current - 1,
|
||||||
.datetime=${this.stateObj.attributes.last_triggered}
|
})}
|
||||||
capitalize
|
|
||||||
></ha-relative-time>
|
|
||||||
`
|
`
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
${hasFields
|
||||||
|
? html`
|
||||||
|
<div class="fields">
|
||||||
|
<div class="title">
|
||||||
|
${this.hass.localize("ui.card.script.run_script")}
|
||||||
|
</div>
|
||||||
|
<ha-service-control
|
||||||
|
hidePicker
|
||||||
|
hideDescription
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._scriptData}
|
||||||
|
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
@value-changed=${this._scriptDataChanged}
|
||||||
|
></ha-service-control>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<ha-control-button-group>
|
||||||
|
<ha-control-button
|
||||||
|
@click=${this._cancelScript}
|
||||||
|
.disabled=${!current}
|
||||||
|
class="cancel-button"
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||||
|
${(isQueued || isParallel) && current > 1
|
||||||
|
? this.hass.localize("ui.card.script.cancel_all")
|
||||||
|
: this.hass.localize("ui.card.script.cancel")}
|
||||||
|
</ha-control-button>
|
||||||
|
<ha-control-button
|
||||||
|
class="run-button"
|
||||||
|
@click=${this._runScript}
|
||||||
|
.disabled=${isUnavailableState(stateObj.state) || !this._canRun()}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlay}></ha-svg-icon>
|
||||||
|
${this.hass!.localize("ui.card.script.run")}
|
||||||
|
</ha-control-button>
|
||||||
|
</ha-control-button-group>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
|
||||||
|
if (!changedProperties.has("stateObj")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldState = changedProperties.get("stateObj") as
|
||||||
|
| HassEntity
|
||||||
|
| undefined;
|
||||||
|
const newState = this.stateObj;
|
||||||
|
|
||||||
|
if (newState && (!oldState || oldState.entity_id !== newState.entity_id)) {
|
||||||
|
this._scriptData = { service: newState.entity_id, data: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cancelScript(ev: Event) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._callService("turn_off");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _runScript(ev: Event) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass.callService(
|
||||||
|
"script",
|
||||||
|
computeObjectId(this.stateObj!.entity_id),
|
||||||
|
this._scriptData.data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _callService(service: string): void {
|
||||||
|
this.hass.callService("script", service, {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _scriptDataChanged(ev: CustomEvent): void {
|
||||||
|
this._scriptData = { ...this._scriptData, ...ev.detail.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _canRun() {
|
||||||
|
if (
|
||||||
|
canRun(this.stateObj!) ||
|
||||||
|
// Restart can also always runs. Just cancels other run.
|
||||||
|
this.stateObj!.attributes.mode === "restart"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.flex {
|
.queue {
|
||||||
display: flex;
|
visibility: hidden;
|
||||||
justify-content: space-between;
|
color: var(--secondary-text-color);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
height: 21px;
|
||||||
}
|
}
|
||||||
hr {
|
.queue.has-queue {
|
||||||
border-color: var(--divider-color);
|
visibility: visible;
|
||||||
border-bottom: none;
|
}
|
||||||
margin: 16px 0;
|
.fields {
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.fields .title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
ha-control-button ha-svg-icon {
|
||||||
|
z-index: -1;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
ha-service-control {
|
||||||
|
--service-control-padding: 0;
|
||||||
|
--service-control-items-border-top: none;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -228,6 +228,7 @@ export class MoreInfoHistory extends LitElement {
|
|||||||
this._stateHistory = computeHistory(
|
this._stateHistory = computeHistory(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
combinedHistory,
|
combinedHistory,
|
||||||
|
[this.entityId],
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
sensorNumericDeviceClasses
|
sensorNumericDeviceClasses
|
||||||
);
|
);
|
||||||
|
@@ -341,7 +341,7 @@ class DialogRestart extends LitElement {
|
|||||||
|
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize("ui.dialogs.restart.reboot.rebooting"),
|
message: this.hass.localize("ui.dialogs.restart.reboot.rebooting"),
|
||||||
duration: 0,
|
duration: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -380,7 +380,7 @@ class DialogRestart extends LitElement {
|
|||||||
|
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize("ui.dialogs.restart.shutdown.shutting_down"),
|
message: this.hass.localize("ui.dialogs.restart.shutdown.shutting_down"),
|
||||||
duration: 0,
|
duration: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -647,6 +647,8 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
--mdc-icon-size: 16px;
|
--mdc-icon-size: 16px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
|
inset-inline-end: 5px;
|
||||||
|
inset-inline-start: initial;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -377,6 +377,8 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
inset-inline-start: initial;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
font-size: 0.65em;
|
font-size: 0.65em;
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,6 @@ import {
|
|||||||
import { customElement, eventOptions, property, state } from "lit/decorators";
|
import { customElement, eventOptions, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
|
||||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-icon-button-arrow-prev";
|
import "../components/ha-icon-button-arrow-prev";
|
||||||
@@ -19,13 +18,14 @@ import "../components/ha-svg-icon";
|
|||||||
import "../components/ha-tab";
|
import "../components/ha-tab";
|
||||||
import { HomeAssistant, Route } from "../types";
|
import { HomeAssistant, Route } from "../types";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
|
import { canShowPage } from "../common/config/can_show_page";
|
||||||
|
|
||||||
export interface PageNavigation {
|
export interface PageNavigation {
|
||||||
path: string;
|
path: string;
|
||||||
translationKey?: string;
|
translationKey?: string;
|
||||||
component?: string;
|
component?: string | string[];
|
||||||
components?: string[];
|
|
||||||
name?: string;
|
name?: string;
|
||||||
|
not_component?: string | string[];
|
||||||
core?: boolean;
|
core?: boolean;
|
||||||
advancedOnly?: boolean;
|
advancedOnly?: boolean;
|
||||||
iconPath?: string;
|
iconPath?: string;
|
||||||
@@ -66,19 +66,12 @@ class HassTabsSubpage extends LitElement {
|
|||||||
(
|
(
|
||||||
tabs: PageNavigation[],
|
tabs: PageNavigation[],
|
||||||
activeTab: PageNavigation | undefined,
|
activeTab: PageNavigation | undefined,
|
||||||
showAdvanced: boolean | undefined,
|
|
||||||
_components,
|
_components,
|
||||||
_language,
|
_language,
|
||||||
_narrow,
|
_narrow,
|
||||||
localizeFunc
|
localizeFunc
|
||||||
) => {
|
) => {
|
||||||
const shownTabs = tabs.filter(
|
const shownTabs = tabs.filter((page) => canShowPage(this.hass, page));
|
||||||
(page) =>
|
|
||||||
(!page.component ||
|
|
||||||
page.core ||
|
|
||||||
isComponentLoaded(this.hass, page.component)) &&
|
|
||||||
(!page.advancedOnly || showAdvanced)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shownTabs.length < 2) {
|
if (shownTabs.length < 2) {
|
||||||
if (shownTabs.length === 1) {
|
if (shownTabs.length === 1) {
|
||||||
@@ -127,7 +120,6 @@ class HassTabsSubpage extends LitElement {
|
|||||||
const tabs = this._getTabs(
|
const tabs = this._getTabs(
|
||||||
this.tabs,
|
this.tabs,
|
||||||
this._activeTab,
|
this._activeTab,
|
||||||
this.hass.userData?.showAdvanced,
|
|
||||||
this.hass.config.components,
|
this.hass.config.components,
|
||||||
this.hass.language,
|
this.hass.language,
|
||||||
this.narrow,
|
this.narrow,
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import "@material/mwc-button";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { property, state, query } from "lit/decorators";
|
import { property, state, query } from "lit/decorators";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import "../components/ha-toast";
|
import "../components/ha-toast";
|
||||||
import type { HaToast } from "../components/ha-toast";
|
import type { HaToast } from "../components/ha-toast";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
import "../components/ha-button";
|
||||||
|
|
||||||
export interface ShowToastParams {
|
export interface ShowToastParams {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -21,72 +22,78 @@ export interface ToastActionParams {
|
|||||||
class NotificationManager extends LitElement {
|
class NotificationManager extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _action?: ToastActionParams;
|
@state() private _parameters?: ShowToastParams;
|
||||||
|
|
||||||
@state() private _noCancelOnOutsideClick = false;
|
@query("ha-toast") private _toast!: HaToast | undefined;
|
||||||
|
|
||||||
@query("ha-toast") private _toast!: HaToast;
|
public async showDialog(parameters: ShowToastParams) {
|
||||||
|
if (this._parameters) {
|
||||||
public async showDialog({
|
this._parameters = undefined;
|
||||||
message,
|
|
||||||
action,
|
|
||||||
duration,
|
|
||||||
dismissable,
|
|
||||||
}: ShowToastParams) {
|
|
||||||
let toast = this._toast;
|
|
||||||
// Can happen on initial load
|
|
||||||
if (!toast) {
|
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
toast = this._toast;
|
|
||||||
}
|
}
|
||||||
toast.setAttribute("dir", computeRTL(this.hass) ? "rtl" : "ltr");
|
|
||||||
this._action = action || undefined;
|
if (!parameters || parameters.duration === 0) {
|
||||||
this._noCancelOnOutsideClick =
|
return;
|
||||||
dismissable === undefined ? false : !dismissable;
|
}
|
||||||
toast.hide();
|
|
||||||
toast.show({
|
this._parameters = parameters;
|
||||||
text: message,
|
|
||||||
duration: duration === undefined ? 3000 : duration,
|
if (
|
||||||
});
|
this._parameters.duration === undefined ||
|
||||||
|
(this._parameters.duration > 0 && this._parameters.duration <= 4000)
|
||||||
|
) {
|
||||||
|
this._parameters.duration = 4000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
public shouldUpdate(changedProperties) {
|
||||||
|
return !this._toast || changedProperties.has("_parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toastClosed() {
|
||||||
|
this._parameters = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._parameters) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-toast .noCancelOnOutsideClick=${this._noCancelOnOutsideClick}>
|
<ha-toast
|
||||||
${this._action
|
leading
|
||||||
|
open
|
||||||
|
dir=${computeRTL(this.hass) ? "rtl" : "ltr"}
|
||||||
|
.labelText=${this._parameters.message}
|
||||||
|
.timeoutMs=${this._parameters.duration!}
|
||||||
|
@MDCSnackbar:closed=${this._toastClosed}
|
||||||
|
>
|
||||||
|
${this._parameters?.action
|
||||||
? html`
|
? html`
|
||||||
<mwc-button
|
<ha-button
|
||||||
.label=${this._action.text}
|
slot="action"
|
||||||
|
.label=${this._parameters?.action.text}
|
||||||
@click=${this.buttonClicked}
|
@click=${this.buttonClicked}
|
||||||
></mwc-button>
|
></ha-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
|
${this._parameters?.dismissable
|
||||||
|
? html`<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.common.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
|
dialogAction="close"
|
||||||
|
slot="dismiss"
|
||||||
|
></ha-icon-button>`
|
||||||
|
: nothing}
|
||||||
</ha-toast>
|
</ha-toast>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buttonClicked() {
|
private buttonClicked() {
|
||||||
this._toast.hide();
|
this._toast?.close("action");
|
||||||
if (this._action) {
|
if (this._parameters?.action) {
|
||||||
this._action.action();
|
this._parameters?.action.action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
ha-toast {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 8px 12px;
|
|
||||||
}
|
|
||||||
mwc-button {
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-weight: bold;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("notification-manager", NotificationManager);
|
customElements.define("notification-manager", NotificationManager);
|
||||||
|
@@ -499,6 +499,8 @@ class OnboardingLocation extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
inset-inline-end: 10px;
|
||||||
|
inset-inline-start: initial;
|
||||||
--mdc-icon-button-size: 36px;
|
--mdc-icon-button-size: 36px;
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
@@ -509,6 +511,8 @@ class OnboardingLocation extends LitElement {
|
|||||||
ha-textfield > ha-circular-progress {
|
ha-textfield > ha-circular-progress {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
|
inset-inline-start: 12px;
|
||||||
|
inset-inline-end: initial;
|
||||||
}
|
}
|
||||||
ha-locations-editor {
|
ha-locations-editor {
|
||||||
display: block;
|
display: block;
|
||||||
|
@@ -188,7 +188,7 @@ export class HAFullCalendar extends LitElement {
|
|||||||
</ha-icon-button-next>
|
</ha-icon-button-next>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls buttons">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
outlined
|
outlined
|
||||||
class="today"
|
class="today"
|
||||||
@@ -480,6 +480,16 @@ export class HAFullCalendar extends LitElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons > * {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.today {
|
.today {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
margin-inline-end: 20px;
|
margin-inline-end: 20px;
|
||||||
|
@@ -568,7 +568,7 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
<a
|
<a
|
||||||
href=${ifDefined(
|
href=${ifDefined(
|
||||||
entityState.attributes.id
|
entityState.attributes.id
|
||||||
? `/config/automation/edit/${entityState.attributes.id}`
|
? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
|
||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -710,6 +710,8 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
right: 4px;
|
right: 4px;
|
||||||
|
inset-inline-end: 4px;
|
||||||
|
inset-inline-start: initial;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.img-container:hover .img-edit-btn {
|
.img-container:hover .img-edit-btn {
|
||||||
@@ -736,6 +738,11 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mwc-button > ha-svg-icon {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
margin-inline-end: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -536,6 +536,8 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
|||||||
(!this._group ||
|
(!this._group ||
|
||||||
items.find((item) => item.key === this._params!.clipboardItem))
|
items.find((item) => item.key === this._params!.clipboardItem))
|
||||||
? html`<ha-list-item-new
|
? html`<ha-list-item-new
|
||||||
|
interactive
|
||||||
|
type="button"
|
||||||
class="paste"
|
class="paste"
|
||||||
.value=${PASTE_VALUE}
|
.value=${PASTE_VALUE}
|
||||||
@click=${this._selected}
|
@click=${this._selected}
|
||||||
@@ -543,7 +545,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
|||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.${this._params.type}s.paste`
|
`ui.panel.config.automation.editor.${this._params.type}s.paste`
|
||||||
)}
|
)}
|
||||||
<span slot="secondary"
|
<span slot="supporting-text"
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
|
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
|
||||||
|
@@ -1,56 +1,23 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-blueprint-picker";
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-circular-progress";
|
|
||||||
import "../../../components/ha-markdown";
|
|
||||||
import "../../../components/ha-selector/ha-selector";
|
|
||||||
import "../../../components/ha-settings-row";
|
|
||||||
import { BlueprintAutomationConfig } from "../../../data/automation";
|
import { BlueprintAutomationConfig } from "../../../data/automation";
|
||||||
import {
|
import { fetchBlueprints } from "../../../data/blueprint";
|
||||||
BlueprintOrError,
|
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
||||||
Blueprints,
|
|
||||||
fetchBlueprints,
|
|
||||||
} from "../../../data/blueprint";
|
|
||||||
import { haStyle } from "../../../resources/styles";
|
|
||||||
import { HomeAssistant } from "../../../types";
|
|
||||||
import "../ha-config-section";
|
|
||||||
|
|
||||||
@customElement("blueprint-automation-editor")
|
@customElement("blueprint-automation-editor")
|
||||||
export class HaBlueprintAutomationEditor extends LitElement {
|
export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public isWide = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public config!: BlueprintAutomationConfig;
|
@property({ attribute: false }) public config!: BlueprintAutomationConfig;
|
||||||
|
|
||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
@state() private _blueprints?: Blueprints;
|
protected get _config(): BlueprintAutomationConfig {
|
||||||
|
return this.config;
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._getBlueprints();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _blueprint(): BlueprintOrError | undefined {
|
|
||||||
if (!this._blueprints) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return this._blueprints[this.config.use_blueprint.path];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const blueprint = this._blueprint;
|
|
||||||
return html`
|
return html`
|
||||||
${this.disabled
|
${this.disabled
|
||||||
? html`<ha-alert alert-type="warning">
|
? html`<ha-alert alert-type="warning">
|
||||||
@@ -77,167 +44,14 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||||||
${this.config.description
|
${this.config.description
|
||||||
? html`<p class="description">${this.config.description}</p>`
|
? html`<p class="description">${this.config.description}</p>`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-card
|
${this.renderCard()}
|
||||||
outlined
|
|
||||||
class="blueprint"
|
|
||||||
.header=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.header"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div class="blueprint-picker-container">
|
|
||||||
${this._blueprints
|
|
||||||
? Object.keys(this._blueprints).length
|
|
||||||
? html`
|
|
||||||
<ha-blueprint-picker
|
|
||||||
.hass=${this.hass}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
|
|
||||||
)}
|
|
||||||
.blueprints=${this._blueprints}
|
|
||||||
.value=${this.config.use_blueprint.path}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
@value-changed=${this._blueprintChanged}
|
|
||||||
></ha-blueprint-picker>
|
|
||||||
`
|
|
||||||
: this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
|
||||||
)
|
|
||||||
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this.config.use_blueprint.path
|
|
||||||
? blueprint && "error" in blueprint
|
|
||||||
? html`<p class="warning padding">
|
|
||||||
There is an error in this Blueprint: ${blueprint.error}
|
|
||||||
</p>`
|
|
||||||
: html`${blueprint?.metadata.description
|
|
||||||
? html`<ha-markdown
|
|
||||||
class="card-content"
|
|
||||||
breaks
|
|
||||||
.content=${blueprint.metadata.description}
|
|
||||||
></ha-markdown>`
|
|
||||||
: ""}
|
|
||||||
${blueprint?.metadata?.input &&
|
|
||||||
Object.keys(blueprint.metadata.input).length
|
|
||||||
? Object.entries(blueprint.metadata.input).map(
|
|
||||||
([key, value]) => {
|
|
||||||
const selector = value?.selector ?? { text: undefined };
|
|
||||||
const type = Object.keys(selector)[0];
|
|
||||||
const enhancedSelector = [
|
|
||||||
"action",
|
|
||||||
"condition",
|
|
||||||
"trigger",
|
|
||||||
].includes(type)
|
|
||||||
? {
|
|
||||||
[type]: {
|
|
||||||
...selector[type],
|
|
||||||
path: [key],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: selector;
|
|
||||||
|
|
||||||
return html`<ha-settings-row .narrow=${this.narrow}>
|
|
||||||
<span slot="heading">${value?.name || key}</span>
|
|
||||||
<ha-markdown
|
|
||||||
slot="description"
|
|
||||||
class="card-content"
|
|
||||||
breaks
|
|
||||||
.content=${value?.description}
|
|
||||||
></ha-markdown>
|
|
||||||
${html`<ha-selector
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${enhancedSelector}
|
|
||||||
.key=${key}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${value?.default === undefined}
|
|
||||||
.placeholder=${value?.default}
|
|
||||||
.value=${this.config.use_blueprint.input &&
|
|
||||||
key in this.config.use_blueprint.input
|
|
||||||
? this.config.use_blueprint.input[key]
|
|
||||||
: value?.default}
|
|
||||||
@value-changed=${this._inputChanged}
|
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
></ha-selector>`}
|
|
||||||
</ha-settings-row>`;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: html`<p class="padding">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.no_inputs"
|
|
||||||
)}
|
|
||||||
</p>`}`
|
|
||||||
: ""}
|
|
||||||
</ha-card>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getBlueprints() {
|
protected async _getBlueprints() {
|
||||||
this._blueprints = await fetchBlueprints(this.hass, "automation");
|
this._blueprints = await fetchBlueprints(this.hass, "automation");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _blueprintChanged(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (this.config.use_blueprint.path === ev.detail.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.config,
|
|
||||||
use_blueprint: {
|
|
||||||
path: ev.detail.value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _inputChanged(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const target = ev.target as any;
|
|
||||||
const key = target.key;
|
|
||||||
const value = ev.detail ? ev.detail.value : target.value;
|
|
||||||
if (
|
|
||||||
(this.config.use_blueprint.input &&
|
|
||||||
this.config.use_blueprint.input[key] === value) ||
|
|
||||||
(!this.config.use_blueprint.input && value === "")
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const input = { ...this.config.use_blueprint.input, [key]: value };
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.config,
|
|
||||||
use_blueprint: {
|
|
||||||
...this.config.use_blueprint,
|
|
||||||
input,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _itemMoved(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
|
||||||
|
|
||||||
const input = nestedArrayMove(
|
|
||||||
this.config.use_blueprint.input,
|
|
||||||
oldIndex,
|
|
||||||
newIndex,
|
|
||||||
oldPath,
|
|
||||||
newPath
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.config,
|
|
||||||
use_blueprint: {
|
|
||||||
...this.config.use_blueprint,
|
|
||||||
input,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _enable(): Promise<void> {
|
private async _enable(): Promise<void> {
|
||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return;
|
return;
|
||||||
@@ -246,69 +60,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||||||
entity_id: this.stateObj.entity_id,
|
entity_id: this.stateObj.entity_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _duplicate() {
|
|
||||||
fireEvent(this, "duplicate");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
ha-card.blueprint {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.padding {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
.link-button-row {
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
.blueprint-picker-container {
|
|
||||||
padding: 0 16px 16px;
|
|
||||||
}
|
|
||||||
ha-textfield,
|
|
||||||
ha-blueprint-picker {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
.introduction {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.introduction a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
ha-settings-row {
|
|
||||||
--paper-time-input-justify-content: flex-end;
|
|
||||||
--settings-row-content-width: 100%;
|
|
||||||
--settings-row-prefix-display: contents;
|
|
||||||
border-top: 1px solid var(--divider-color);
|
|
||||||
}
|
|
||||||
ha-alert {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
ha-alert.re-order {
|
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"blueprint-automation-editor": HaBlueprintAutomationEditor;
|
"blueprint-automation-editor": HaBlueprintAutomationEditor;
|
||||||
|
@@ -172,7 +172,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
|
||||||
${stateObj && this._config && this.narrow
|
${stateObj && this._config && this.narrow
|
||||||
? html`<a href="/config/automation/trace/${this._config.id}">
|
? html`<a
|
||||||
|
href="/config/automation/trace/${encodeURIComponent(
|
||||||
|
this._config.id!
|
||||||
|
)}"
|
||||||
|
>
|
||||||
<ha-list-item graphic="icon">
|
<ha-list-item graphic="icon">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.show_trace"
|
"ui.panel.config.automation.editor.show_trace"
|
||||||
@@ -563,7 +567,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
if (this._config?.id) {
|
if (this._config?.id) {
|
||||||
const result = await this.confirmUnsavedChanged();
|
const result = await this.confirmUnsavedChanged();
|
||||||
if (result) {
|
if (result) {
|
||||||
navigate(`/config/automation/trace/${this._config.id}`);
|
navigate(
|
||||||
|
`/config/automation/trace/${encodeURIComponent(this._config.id)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -435,7 +435,9 @@ class HaAutomationPicker extends LitElement {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate(`/config/automation/trace/${automation.attributes.id}`);
|
navigate(
|
||||||
|
`/config/automation/trace/${encodeURIComponent(automation.attributes.id)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _toggle(automation): Promise<void> {
|
private async _toggle(automation): Promise<void> {
|
||||||
@@ -530,9 +532,11 @@ class HaAutomationPicker extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (automation?.attributes.id) {
|
if (automation?.attributes.id) {
|
||||||
navigate(`/config/automation/edit/${automation.attributes.id}`);
|
navigate(
|
||||||
|
`/config/automation/edit/${encodeURIComponent(automation.attributes.id)}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
navigate(`/config/automation/show/${ev.detail.id}`);
|
navigate(`/config/automation/show/${encodeURIComponent(ev.detail.id)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -93,7 +93,7 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
|
|
||||||
let devButtons: TemplateResult | string = "";
|
let devButtons: TemplateResult | string = "";
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
devButtons = html`<div style="position: absolute; right: 0;">
|
devButtons = html`<div style="position: absolute; right: 0; z-index: 1;">
|
||||||
<button @click=${this._importTrace}>Import trace</button>
|
<button @click=${this._importTrace}>Import trace</button>
|
||||||
<button @click=${this._loadLocalStorageTrace}>Load stored trace</button>
|
<button @click=${this._loadLocalStorageTrace}>Load stored trace</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -106,7 +106,9 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<a
|
<a
|
||||||
class="trace-link"
|
class="trace-link"
|
||||||
href="/config/automation/edit/${stateObj.attributes.id}"
|
href="/config/automation/edit/${encodeURIComponent(
|
||||||
|
stateObj.attributes.id
|
||||||
|
)}"
|
||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
>
|
>
|
||||||
<mwc-button>
|
<mwc-button>
|
||||||
@@ -140,7 +142,9 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<a
|
<a
|
||||||
class="trace-link"
|
class="trace-link"
|
||||||
href="/config/automation/edit/${stateObj.attributes.id}"
|
href="/config/automation/edit/${encodeURIComponent(
|
||||||
|
stateObj.attributes.id
|
||||||
|
)}"
|
||||||
>
|
>
|
||||||
<mwc-list-item graphic="icon">
|
<mwc-list-item graphic="icon">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
|
275
src/panels/config/blueprint/blueprint-generic-editor.ts
Normal file
275
src/panels/config/blueprint/blueprint-generic-editor.ts
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { nestedArrayMove } from "../../../common/util/array-move";
|
||||||
|
import "../../../components/ha-alert";
|
||||||
|
import "../../../components/ha-blueprint-picker";
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import "../../../components/ha-circular-progress";
|
||||||
|
import "../../../components/ha-markdown";
|
||||||
|
import "../../../components/ha-selector/ha-selector";
|
||||||
|
import "../../../components/ha-settings-row";
|
||||||
|
import { BlueprintAutomationConfig } from "../../../data/automation";
|
||||||
|
import { BlueprintOrError, Blueprints } from "../../../data/blueprint";
|
||||||
|
import { BlueprintScriptConfig } from "../../../data/script";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
@customElement("blueprint-generic-editor")
|
||||||
|
export abstract class HaBlueprintGenericEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
|
@state() protected _blueprints?: Blueprints;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._getBlueprints();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get _blueprint(): BlueprintOrError | undefined {
|
||||||
|
if (!this._blueprints) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return this._blueprints[this._config.use_blueprint.path];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract get _config():
|
||||||
|
| BlueprintAutomationConfig
|
||||||
|
| BlueprintScriptConfig;
|
||||||
|
|
||||||
|
protected renderCard() {
|
||||||
|
const blueprint = this._blueprint;
|
||||||
|
return html`
|
||||||
|
<ha-card
|
||||||
|
outlined
|
||||||
|
class="blueprint"
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.blueprint.header"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="blueprint-picker-container">
|
||||||
|
${this._blueprints
|
||||||
|
? Object.keys(this._blueprints).length
|
||||||
|
? html`
|
||||||
|
<ha-blueprint-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
|
||||||
|
)}
|
||||||
|
.blueprints=${this._blueprints}
|
||||||
|
.value=${this._config.use_blueprint.path}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._blueprintChanged}
|
||||||
|
></ha-blueprint-picker>
|
||||||
|
`
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
||||||
|
)
|
||||||
|
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this._config.use_blueprint.path
|
||||||
|
? blueprint && "error" in blueprint
|
||||||
|
? html`<p class="warning padding">
|
||||||
|
There is an error in this Blueprint: ${blueprint.error}
|
||||||
|
</p>`
|
||||||
|
: html`${blueprint?.metadata.description
|
||||||
|
? html`<ha-markdown
|
||||||
|
class="card-content"
|
||||||
|
breaks
|
||||||
|
.content=${blueprint.metadata.description}
|
||||||
|
></ha-markdown>`
|
||||||
|
: ""}
|
||||||
|
${blueprint?.metadata?.input &&
|
||||||
|
Object.keys(blueprint.metadata.input).length
|
||||||
|
? Object.entries(blueprint.metadata.input).map(
|
||||||
|
([key, value]) => {
|
||||||
|
const selector = value?.selector ?? { text: undefined };
|
||||||
|
const type = Object.keys(selector)[0];
|
||||||
|
const enhancedSelector = [
|
||||||
|
"action",
|
||||||
|
"condition",
|
||||||
|
"trigger",
|
||||||
|
].includes(type)
|
||||||
|
? {
|
||||||
|
[type]: {
|
||||||
|
...selector[type],
|
||||||
|
path: [key],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: selector;
|
||||||
|
|
||||||
|
return html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
|
<span slot="heading">${value?.name || key}</span>
|
||||||
|
<ha-markdown
|
||||||
|
slot="description"
|
||||||
|
class="card-content"
|
||||||
|
breaks
|
||||||
|
.content=${value?.description}
|
||||||
|
></ha-markdown>
|
||||||
|
${html`<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${enhancedSelector}
|
||||||
|
.key=${key}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${value?.default === undefined}
|
||||||
|
.placeholder=${value?.default}
|
||||||
|
.value=${this._config.use_blueprint.input &&
|
||||||
|
key in this._config.use_blueprint.input
|
||||||
|
? this._config.use_blueprint.input[key]
|
||||||
|
: value?.default}
|
||||||
|
@value-changed=${this._inputChanged}
|
||||||
|
@item-moved=${this._itemMoved}
|
||||||
|
></ha-selector>`}
|
||||||
|
</ha-settings-row>`;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: html`<p class="padding">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.blueprint.no_inputs"
|
||||||
|
)}
|
||||||
|
</p>`}`
|
||||||
|
: ""}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract _getBlueprints();
|
||||||
|
|
||||||
|
private _blueprintChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (this._config.use_blueprint.path === ev.detail.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this._config,
|
||||||
|
use_blueprint: {
|
||||||
|
path: ev.detail.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _inputChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const target = ev.target as any;
|
||||||
|
const key = target.key;
|
||||||
|
const value = ev.detail ? ev.detail.value : target.value;
|
||||||
|
if (
|
||||||
|
(this._config.use_blueprint.input &&
|
||||||
|
this._config.use_blueprint.input[key] === value) ||
|
||||||
|
(!this._config.use_blueprint.input && value === "")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const input = { ...this._config.use_blueprint.input, [key]: value };
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this._config,
|
||||||
|
use_blueprint: {
|
||||||
|
...this._config.use_blueprint,
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _itemMoved(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
||||||
|
|
||||||
|
const input = nestedArrayMove(
|
||||||
|
this._config.use_blueprint.input,
|
||||||
|
oldIndex,
|
||||||
|
newIndex,
|
||||||
|
oldPath,
|
||||||
|
newPath
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this._config,
|
||||||
|
use_blueprint: {
|
||||||
|
...this._config.use_blueprint,
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _duplicate() {
|
||||||
|
fireEvent(this, "duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-card.blueprint {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.padding {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.link-button-row {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
.blueprint-picker-container {
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
}
|
||||||
|
ha-textfield,
|
||||||
|
ha-blueprint-picker {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
.introduction {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.introduction a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
--paper-time-input-justify-content: flex-end;
|
||||||
|
--settings-row-content-width: 100%;
|
||||||
|
--settings-row-prefix-display: contents;
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
ha-alert {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-alert.re-order {
|
||||||
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"blueprint-generic-editor": HaBlueprintGenericEditor;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,16 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { mdiContentCopy, mdiHelpCircle } from "@mdi/js";
|
import { mdiContentCopy, mdiHelpCircle } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||||
import "../../../../components/ha-alert";
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-expansion-panel";
|
||||||
|
import "../../../../components/ha-settings-row";
|
||||||
import "../../../../components/ha-switch";
|
import "../../../../components/ha-switch";
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
import { formatDate } from "../../../../common/datetime/format_date";
|
||||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||||
import {
|
import {
|
||||||
CloudStatusLoggedIn,
|
CloudStatusLoggedIn,
|
||||||
@@ -125,22 +128,60 @@ export class CloudRemotePref extends LitElement {
|
|||||||
>.
|
>.
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.url=${`https://${remote_domain}`}
|
.url=${`https://${remote_domain}`}
|
||||||
.path=${mdiContentCopy}
|
|
||||||
@click=${this._copyURL}
|
@click=${this._copyURL}
|
||||||
|
.path=${mdiContentCopy}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
<ha-formfield .label=${"Allow external activation"}>
|
<ha-expansion-panel
|
||||||
<ha-switch
|
outlined
|
||||||
.checked=${remote_allow_remote_enable}
|
.header=${this.hass.localize(
|
||||||
@change=${this._toggleAllowRemoteEnabledChanged}
|
"ui.panel.config.cloud.account.remote.advanced_options"
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button @click=${this._openCertInfo}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.cloud.account.remote.certificate_info"
|
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
>
|
||||||
|
<ha-settings-row>
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.remote.external_activation"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.remote.external_activation_secondary"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${remote_allow_remote_enable}
|
||||||
|
@change=${this._toggleAllowRemoteEnabledChanged}
|
||||||
|
></ha-switch>
|
||||||
|
</ha-settings-row>
|
||||||
|
<ha-settings-row>
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.remote.certificate_info"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.cloudStatus!.remote_certificate
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.remote.certificate_expire",
|
||||||
|
{
|
||||||
|
date: formatDate(
|
||||||
|
new Date(
|
||||||
|
this.cloudStatus.remote_certificate.expire_date
|
||||||
|
),
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: nothing}</span
|
||||||
|
>
|
||||||
|
<ha-button @click=${this._openCertInfo}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.remote.more_info"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
</ha-settings-row>
|
||||||
|
</ha-expansion-panel>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
@@ -226,6 +267,8 @@ export class CloudRemotePref extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
top: 24px;
|
top: 24px;
|
||||||
|
inset-inline-end: 24px;
|
||||||
|
inset-inline-start: initial;
|
||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -241,6 +284,9 @@ export class CloudRemotePref extends LitElement {
|
|||||||
ha-formfield {
|
ha-formfield {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
ha-expansion-panel {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,10 @@ import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities
|
|||||||
import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types";
|
import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types";
|
||||||
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
|
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
|
||||||
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
||||||
import { computeCards } from "../../../lovelace/common/generate-lovelace-config";
|
import {
|
||||||
|
computeCards,
|
||||||
|
computeSection,
|
||||||
|
} from "../../../lovelace/common/generate-lovelace-config";
|
||||||
|
|
||||||
@customElement("ha-device-entities-card")
|
@customElement("ha-device-entities-card")
|
||||||
export class HaDeviceEntitiesCard extends LitElement {
|
export class HaDeviceEntitiesCard extends LitElement {
|
||||||
@@ -235,6 +238,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
computeCards(this.hass.states, entities, {
|
computeCards(this.hass.states, entities, {
|
||||||
title: this.deviceName,
|
title: this.deviceName,
|
||||||
}),
|
}),
|
||||||
|
computeSection(entities, {
|
||||||
|
title: this.deviceName,
|
||||||
|
}),
|
||||||
entities
|
entities
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -431,19 +431,18 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
<a
|
<a
|
||||||
href=${ifDefined(
|
href=${ifDefined(
|
||||||
entityState.attributes.id
|
entityState.attributes.id
|
||||||
? `/config/automation/edit/${entityState.attributes.id}`
|
? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
|
||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<paper-item
|
<ha-list-item
|
||||||
|
hasMeta
|
||||||
.automation=${entityState}
|
.automation=${entityState}
|
||||||
.disabled=${!entityState.attributes.id}
|
.disabled=${!entityState.attributes.id}
|
||||||
>
|
>
|
||||||
<paper-item-body>
|
${computeStateName(entityState)}
|
||||||
${computeStateName(entityState)}
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
</paper-item-body>
|
</ha-list-item>
|
||||||
<ha-icon-next></ha-icon-next>
|
|
||||||
</paper-item>
|
|
||||||
</a>
|
</a>
|
||||||
${!entityState.attributes.id
|
${!entityState.attributes.id
|
||||||
? html`
|
? html`
|
||||||
@@ -528,15 +527,14 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<paper-item
|
<ha-list-item
|
||||||
|
hasMeta
|
||||||
.scene=${entityState}
|
.scene=${entityState}
|
||||||
.disabled=${!entityState.attributes.id}
|
.disabled=${!entityState.attributes.id}
|
||||||
>
|
>
|
||||||
<paper-item-body>
|
${computeStateName(entityState)}
|
||||||
${computeStateName(entityState)}
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
</paper-item-body>
|
</ha-list-item>
|
||||||
<ha-icon-next></ha-icon-next>
|
|
||||||
</paper-item>
|
|
||||||
</a>
|
</a>
|
||||||
${!entityState.attributes.id
|
${!entityState.attributes.id
|
||||||
? html`
|
? html`
|
||||||
@@ -623,12 +621,10 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
return entityState
|
return entityState
|
||||||
? html`
|
? html`
|
||||||
<a href=${url}>
|
<a href=${url}>
|
||||||
<paper-item .script=${script}>
|
<ha-list-item hasMeta .script=${script}>
|
||||||
<paper-item-body>
|
${computeStateName(entityState)}
|
||||||
${computeStateName(entityState)}
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
</paper-item-body>
|
</ha-list-item>
|
||||||
<ha-icon-next></ha-icon-next>
|
|
||||||
</paper-item>
|
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
@@ -1518,11 +1514,6 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: var(--paper-font-body1_-_font-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
|
@@ -939,9 +939,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked=${!this._disabledBy && !this._hiddenBy}
|
.checked=${!this._disabledBy && !this._hiddenBy}
|
||||||
.disabled=${this.disabled ||
|
.disabled=${this.disabled || this._disabledBy}
|
||||||
this._disabledBy ||
|
|
||||||
(this._hiddenBy && this._hiddenBy !== "user")}
|
|
||||||
@change=${this._hiddenChanged}
|
@change=${this._hiddenChanged}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
|
@@ -74,7 +74,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
translationKey: "areas",
|
translationKey: "areas",
|
||||||
iconPath: mdiSofa,
|
iconPath: mdiSofa,
|
||||||
iconColor: "#E48629",
|
iconColor: "#E48629",
|
||||||
components: ["zone"],
|
component: "zone",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/hassio",
|
path: "/hassio",
|
||||||
@@ -108,7 +108,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
translationKey: "people",
|
translationKey: "people",
|
||||||
iconPath: mdiAccount,
|
iconPath: mdiAccount,
|
||||||
iconColor: "#5A87FA",
|
iconColor: "#5A87FA",
|
||||||
components: ["person", "users"],
|
component: ["person", "users"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "#external-app-configuration",
|
path: "#external-app-configuration",
|
||||||
@@ -309,6 +309,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
iconPath: mdiBackupRestore,
|
iconPath: mdiBackupRestore,
|
||||||
iconColor: "#0D47A1",
|
iconColor: "#0D47A1",
|
||||||
component: "backup",
|
component: "backup",
|
||||||
|
not_component: "hassio",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/hassio/backups",
|
path: "/hassio/backups",
|
||||||
@@ -341,7 +342,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
translationKey: "hardware",
|
translationKey: "hardware",
|
||||||
iconPath: mdiMemory,
|
iconPath: mdiMemory,
|
||||||
iconColor: "#301A8E",
|
iconColor: "#301A8E",
|
||||||
components: ["hassio", "hardware"],
|
component: ["hassio", "hardware"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
about: [
|
about: [
|
||||||
|
@@ -171,12 +171,18 @@ class DialogHardwareAvailable extends LitElement implements HassDialog {
|
|||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
|
inset-inline-end: 16px;
|
||||||
|
inset-inline-start: initial;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
inset-inline-end: 16px;
|
||||||
|
inset-inline-start: initial;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
margin: 18px 42px 0 18px;
|
margin: 18px 42px 0 18px;
|
||||||
|
margin-inline-start: 18px;
|
||||||
|
margin-inline-end: 42px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
ha-expansion-panel {
|
ha-expansion-panel {
|
||||||
|
@@ -177,8 +177,13 @@ class HaInputSelectForm extends LitElement {
|
|||||||
const index = (ev.target as any).index;
|
const index = (ev.target as any).index;
|
||||||
if (
|
if (
|
||||||
!(await showConfirmationDialog(this, {
|
!(await showConfirmationDialog(this, {
|
||||||
title: "Delete this item?",
|
title: this.hass.localize(
|
||||||
text: "Are you sure you want to delete this item?",
|
"ui.dialogs.helper_settings.input_select.confirm_delete.delete"
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.helper_settings.input_select.confirm_delete.prompt"
|
||||||
|
),
|
||||||
|
destructive: true,
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
@@ -877,6 +877,8 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
|
inset-inline-end: 0px;
|
||||||
|
inset-inline-start: initial;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
font-size: 0.65em;
|
font-size: 0.65em;
|
||||||
}
|
}
|
||||||
@@ -884,7 +886,10 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
margin: 8px 0 0 16px;
|
margin-top: 8px;
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-inline-start: 16px;
|
||||||
|
margin-inline-end: initial;
|
||||||
}
|
}
|
||||||
ha-button-menu {
|
ha-button-menu {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
|
@@ -90,6 +90,8 @@ export class HaIntegrationActionCard extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
inset-inline-end: 8px;
|
||||||
|
inset-inline-start: initial;
|
||||||
}
|
}
|
||||||
.filler {
|
.filler {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@@ -137,6 +137,7 @@ class DialogMatterPingNode extends LitElement {
|
|||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this.device_id = undefined;
|
this.device_id = undefined;
|
||||||
this._status = undefined;
|
this._status = undefined;
|
||||||
|
this._pingResult = undefined;
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -54,6 +54,7 @@ import { haStyle } from "../../../../../resources/styles";
|
|||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { brandsUrl } from "../../../../../util/brands-url";
|
import { brandsUrl } from "../../../../../util/brands-url";
|
||||||
import { fileDownload } from "../../../../../util/file_download";
|
import { fileDownload } from "../../../../../util/file_download";
|
||||||
|
import { documentationUrl } from "../../../../../util/documentation-url";
|
||||||
|
|
||||||
interface ThreadNetwork {
|
interface ThreadNetwork {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -123,11 +124,16 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
|
||||||
<mwc-button @click=${this._addOTBR}
|
<a
|
||||||
>${this.hass.localize(
|
href=${documentationUrl(this.hass, `/integrations/thread`)}
|
||||||
"ui.panel.config.thread.add_open_thread_border_router"
|
target="_blank"
|
||||||
)}</mwc-button
|
|
||||||
>
|
>
|
||||||
|
<mwc-button
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.thread.more_info"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>`}
|
</ha-card>`}
|
||||||
${networks.networks.length
|
${networks.networks.length
|
||||||
|
@@ -269,6 +269,8 @@ class ZHAAddDevicesPage extends LitElement {
|
|||||||
margin-inline-start: initial;
|
margin-inline-start: initial;
|
||||||
top: -6px;
|
top: -6px;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
inset-inline-start: initial;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
.search-button {
|
.search-button {
|
||||||
|
@@ -241,6 +241,8 @@ export class ZHAClusterCommands extends LitElement {
|
|||||||
float: right;
|
float: right;
|
||||||
top: -6px;
|
top: -6px;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
inset-inline-start: initial;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
padding-inline-end: 0px;
|
padding-inline-end: 0px;
|
||||||
padding-inline-start: initial;
|
padding-inline-start: initial;
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiDelete } from "@mdi/js";
|
import { mdiDelete } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
LitElement,
|
LitElement,
|
||||||
@@ -32,6 +31,8 @@ import "../../../ha-config-section";
|
|||||||
import { formatAsPaddedHex } from "./functions";
|
import { formatAsPaddedHex } from "./functions";
|
||||||
import "./zha-device-endpoint-data-table";
|
import "./zha-device-endpoint-data-table";
|
||||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||||
|
import "@material/mwc-list/mwc-list";
|
||||||
|
import "../../../../../components/ha-list-item";
|
||||||
|
|
||||||
@customElement("zha-group-page")
|
@customElement("zha-group-page")
|
||||||
export class ZHAGroupPage extends LitElement {
|
export class ZHAGroupPage extends LitElement {
|
||||||
@@ -131,20 +132,24 @@ export class ZHAGroupPage extends LitElement {
|
|||||||
${this.hass.localize("ui.panel.config.zha.groups.members")}
|
${this.hass.localize("ui.panel.config.zha.groups.members")}
|
||||||
</div>
|
</div>
|
||||||
<ha-card>
|
<ha-card>
|
||||||
${this.group.members.length
|
<mwc-list>
|
||||||
? this.group.members.map(
|
${this.group.members.length
|
||||||
(member) =>
|
? this.group.members.map(
|
||||||
html`<a
|
(member) =>
|
||||||
href="/config/devices/device/${member.device
|
html`<a
|
||||||
.device_reg_id}"
|
href="/config/devices/device/${member.device
|
||||||
>
|
.device_reg_id}"
|
||||||
<paper-item
|
|
||||||
>${member.device.user_given_name ||
|
|
||||||
member.device.name}</paper-item
|
|
||||||
>
|
>
|
||||||
</a>`
|
<ha-list-item
|
||||||
)
|
>${member.device.user_given_name ||
|
||||||
: html` <paper-item> This group has no members </paper-item> `}
|
member.device.name}</ha-list-item
|
||||||
|
>
|
||||||
|
</a>`
|
||||||
|
)
|
||||||
|
: html`
|
||||||
|
<ha-list-item> This group has no members </ha-list-item>
|
||||||
|
`}
|
||||||
|
</mwc-list>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
${this.group.members.length
|
${this.group.members.length
|
||||||
? html`
|
? html`
|
||||||
|
@@ -8,13 +8,20 @@ import { HomeAssistant } from "../../../types";
|
|||||||
|
|
||||||
export const lovelaceTabs = [
|
export const lovelaceTabs = [
|
||||||
{
|
{
|
||||||
component: "lovelace",
|
|
||||||
path: "/config/lovelace/dashboards",
|
path: "/config/lovelace/dashboards",
|
||||||
translationKey: "ui.panel.config.lovelace.dashboards.caption",
|
translationKey: "ui.panel.config.lovelace.dashboards.caption",
|
||||||
iconPath: mdiViewDashboard,
|
iconPath: mdiViewDashboard,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const lovelaceResourcesTabs = [
|
||||||
|
{
|
||||||
|
path: "/config/lovelace/resources",
|
||||||
|
translationKey: "ui.panel.config.lovelace.resources.caption",
|
||||||
|
iconPath: mdiViewDashboard,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@customElement("ha-config-lovelace")
|
@customElement("ha-config-lovelace")
|
||||||
class HaConfigLovelace extends HassRouterPage {
|
class HaConfigLovelace extends HassRouterPage {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
@@ -34,7 +34,7 @@ import "../../../../layouts/hass-tabs-subpage-data-table";
|
|||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../../types";
|
import { HomeAssistant, Route } from "../../../../types";
|
||||||
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
|
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
|
||||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
import { lovelaceResourcesTabs } from "../ha-config-lovelace";
|
||||||
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
|
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
|
||||||
|
|
||||||
@customElement("ha-config-lovelace-resources")
|
@customElement("ha-config-lovelace-resources")
|
||||||
@@ -117,7 +117,7 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${lovelaceTabs}
|
.tabs=${lovelaceResourcesTabs}
|
||||||
.columns=${this._columns(this.hass.language)}
|
.columns=${this._columns(this.hass.language)}
|
||||||
.data=${this._resources}
|
.data=${this._resources}
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
|
@@ -1,52 +1,19 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-blueprint-picker";
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-circular-progress";
|
|
||||||
import "../../../components/ha-markdown";
|
|
||||||
import "../../../components/ha-selector/ha-selector";
|
|
||||||
import "../../../components/ha-settings-row";
|
|
||||||
import {
|
|
||||||
BlueprintOrError,
|
|
||||||
Blueprints,
|
|
||||||
fetchBlueprints,
|
|
||||||
} from "../../../data/blueprint";
|
|
||||||
import { BlueprintScriptConfig } from "../../../data/script";
|
import { BlueprintScriptConfig } from "../../../data/script";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { fetchBlueprints } from "../../../data/blueprint";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
||||||
import "../ha-config-section";
|
|
||||||
|
|
||||||
@customElement("blueprint-script-editor")
|
@customElement("blueprint-script-editor")
|
||||||
export class HaBlueprintScriptEditor extends LitElement {
|
export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public isWide = false;
|
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) public narrow = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public config!: BlueprintScriptConfig;
|
@property({ attribute: false }) public config!: BlueprintScriptConfig;
|
||||||
|
|
||||||
@state() private _blueprints?: Blueprints;
|
protected get _config(): BlueprintScriptConfig {
|
||||||
|
return this.config;
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._getBlueprints();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _blueprint(): BlueprintOrError | undefined {
|
|
||||||
if (!this._blueprints) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return this._blueprints[this.config.use_blueprint.path];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const blueprint = this._blueprint;
|
|
||||||
return html`
|
return html`
|
||||||
${this.disabled
|
${this.disabled
|
||||||
? html`<ha-alert alert-type="warning">
|
? html`<ha-alert alert-type="warning">
|
||||||
@@ -56,228 +23,14 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||||||
</mwc-button>
|
</mwc-button>
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-card
|
${this.renderCard()}
|
||||||
outlined
|
|
||||||
class="blueprint"
|
|
||||||
.header=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.header"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div class="blueprint-picker-container">
|
|
||||||
${this._blueprints
|
|
||||||
? Object.keys(this._blueprints).length
|
|
||||||
? html`
|
|
||||||
<ha-blueprint-picker
|
|
||||||
.hass=${this.hass}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
|
|
||||||
)}
|
|
||||||
.blueprints=${this._blueprints}
|
|
||||||
.value=${this.config.use_blueprint.path}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
@value-changed=${this._blueprintChanged}
|
|
||||||
></ha-blueprint-picker>
|
|
||||||
`
|
|
||||||
: this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
|
||||||
)
|
|
||||||
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
|
|
||||||
</div>
|
|
||||||
${this.config.use_blueprint.path
|
|
||||||
? blueprint && "error" in blueprint
|
|
||||||
? html`<p class="warning padding">
|
|
||||||
There is an error in this Blueprint: ${blueprint.error}
|
|
||||||
</p>`
|
|
||||||
: html`${blueprint?.metadata.description
|
|
||||||
? html`<ha-markdown
|
|
||||||
class="card-content"
|
|
||||||
breaks
|
|
||||||
.content=${blueprint.metadata.description}
|
|
||||||
></ha-markdown>`
|
|
||||||
: ""}
|
|
||||||
${blueprint?.metadata?.input &&
|
|
||||||
Object.keys(blueprint.metadata.input).length
|
|
||||||
? Object.entries(blueprint.metadata.input).map(
|
|
||||||
([key, value]) => {
|
|
||||||
const selector = value?.selector ?? { text: undefined };
|
|
||||||
const type = Object.keys(selector)[0];
|
|
||||||
const enhancedSelector = [
|
|
||||||
"action",
|
|
||||||
"condition",
|
|
||||||
"trigger",
|
|
||||||
].includes(type)
|
|
||||||
? {
|
|
||||||
[type]: {
|
|
||||||
...selector[type],
|
|
||||||
path: [key],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: selector;
|
|
||||||
|
|
||||||
return html`<ha-settings-row .narrow=${this.narrow}>
|
|
||||||
<span slot="heading">${value?.name || key}</span>
|
|
||||||
<ha-markdown
|
|
||||||
slot="description"
|
|
||||||
class="card-content"
|
|
||||||
breaks
|
|
||||||
.content=${value?.description}
|
|
||||||
></ha-markdown>
|
|
||||||
${html`<ha-selector
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${enhancedSelector}
|
|
||||||
.key=${key}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${value?.default === undefined}
|
|
||||||
.placeholder=${value?.default}
|
|
||||||
.value=${this.config.use_blueprint.input &&
|
|
||||||
key in this.config.use_blueprint.input
|
|
||||||
? this.config.use_blueprint.input[key]
|
|
||||||
: value?.default}
|
|
||||||
@value-changed=${this._inputChanged}
|
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
></ha-selector>`}
|
|
||||||
</ha-settings-row>`;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: html`<p class="padding">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.blueprint.no_inputs"
|
|
||||||
)}
|
|
||||||
</p>`}`
|
|
||||||
: ""}
|
|
||||||
</ha-card>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getBlueprints() {
|
protected async _getBlueprints() {
|
||||||
this._blueprints = await fetchBlueprints(this.hass, "script");
|
this._blueprints = await fetchBlueprints(this.hass, "script");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _blueprintChanged(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (this.config.use_blueprint.path === ev.detail.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.config,
|
|
||||||
use_blueprint: {
|
|
||||||
path: ev.detail.value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _inputChanged(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const target = ev.target as any;
|
|
||||||
const key = target.key;
|
|
||||||
const value = ev.detail ? ev.detail.value : target.value;
|
|
||||||
if (
|
|
||||||
(this.config.use_blueprint.input &&
|
|
||||||
this.config.use_blueprint.input[key] === value) ||
|
|
||||||
(!this.config.use_blueprint.input && value === "")
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const input = { ...this.config.use_blueprint.input, [key]: value };
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.config,
|
|
||||||
use_blueprint: {
|
|
||||||
...this.config.use_blueprint,
|
|
||||||
input,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _itemMoved(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
|
||||||
|
|
||||||
const input = nestedArrayMove(
|
|
||||||
this.config.use_blueprint.input,
|
|
||||||
oldIndex,
|
|
||||||
newIndex,
|
|
||||||
oldPath,
|
|
||||||
newPath
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
...this.config,
|
|
||||||
use_blueprint: {
|
|
||||||
...this.config.use_blueprint,
|
|
||||||
input,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _duplicate() {
|
|
||||||
fireEvent(this, "duplicate");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
ha-card.blueprint {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.padding {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
.link-button-row {
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
.blueprint-picker-container {
|
|
||||||
padding: 0 16px 16px;
|
|
||||||
}
|
|
||||||
ha-textfield,
|
|
||||||
ha-blueprint-picker {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
.introduction {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.introduction a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
ha-settings-row {
|
|
||||||
--paper-time-input-justify-content: flex-end;
|
|
||||||
--settings-row-content-width: 100%;
|
|
||||||
--settings-row-prefix-display: contents;
|
|
||||||
border-top: 1px solid var(--divider-color);
|
|
||||||
}
|
|
||||||
ha-alert {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
ha-alert.re-order {
|
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"blueprint-script-editor": HaBlueprintScriptEditor;
|
"blueprint-script-editor": HaBlueprintScriptEditor;
|
||||||
|
@@ -811,7 +811,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
"ui.panel.config.script.editor.id_already_exists_save_error"
|
"ui.panel.config.script.editor.id_already_exists_save_error"
|
||||||
),
|
),
|
||||||
dismissable: false,
|
dismissable: false,
|
||||||
duration: 0,
|
duration: -1,
|
||||||
action: {
|
action: {
|
||||||
action: () => {},
|
action: () => {},
|
||||||
text: this.hass.localize("ui.dialogs.generic.ok"),
|
text: this.hass.localize("ui.dialogs.generic.ok"),
|
||||||
|
@@ -334,6 +334,8 @@ class HaConfigSectionStorage extends LitElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
inset-inline-end: 10px;
|
||||||
|
inset-inline-start: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-mounts {
|
.no-mounts {
|
||||||
|
@@ -93,6 +93,8 @@ export class VoiceAssistantExposeAssistantIcon extends LitElement {
|
|||||||
--mdc-icon-size: 16px;
|
--mdc-icon-size: 16px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: -7px;
|
top: -7px;
|
||||||
|
inset-inline-end: 10px;
|
||||||
|
inset-inline-start: initial;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -58,6 +58,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
|
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
@state() private _storageItems?: Zone[];
|
@state() private _storageItems?: Zone[];
|
||||||
|
|
||||||
@state() private _stateItems?: HassEntity[];
|
@state() private _stateItems?: HassEntity[];
|
||||||
@@ -219,7 +221,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
back-path="/config"
|
.backPath=${this._searchParms.has("historyBack")
|
||||||
|
? undefined
|
||||||
|
: "/config"}
|
||||||
.tabs=${configSections.areas}
|
.tabs=${configSections.areas}
|
||||||
>
|
>
|
||||||
${this.narrow
|
${this.narrow
|
||||||
|
@@ -34,6 +34,11 @@ import { HomeAssistant } from "../../../types";
|
|||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import type { DialogStatisticsAdjustSumParams } from "./show-dialog-statistics-adjust-sum";
|
import type { DialogStatisticsAdjustSumParams } from "./show-dialog-statistics-adjust-sum";
|
||||||
|
|
||||||
|
interface CombinedStat {
|
||||||
|
hour: StatisticValue | null;
|
||||||
|
fiveMin: StatisticValue[];
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("dialog-statistics-adjust-sum")
|
@customElement("dialog-statistics-adjust-sum")
|
||||||
export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -196,6 +201,13 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
|||||||
@value-changed=${this._dateTimeSelectorChanged}
|
@value-changed=${this._dateTimeSelectorChanged}
|
||||||
></ha-selector-datetime>
|
></ha-selector-datetime>
|
||||||
<div class="stat-list">${stats}</div>
|
<div class="stat-list">${stats}</div>
|
||||||
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.statistics.fix_issue.adjust_sum.outliers"
|
||||||
|
)}
|
||||||
|
@click=${this._fetchOutliers}
|
||||||
|
></mwc-button>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
dialogAction="cancel"
|
dialogAction="cancel"
|
||||||
@@ -349,6 +361,101 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
|||||||
statId in stats5MinData ? stats5MinData[statId].slice(0, 5) : [];
|
statId in stats5MinData ? stats5MinData[statId].slice(0, 5) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _fetchOutliers(): Promise<void> {
|
||||||
|
this._stats5min = undefined;
|
||||||
|
this._statsHour = undefined;
|
||||||
|
const statId = this._params!.statistic.statistic_id;
|
||||||
|
|
||||||
|
// Get all the data
|
||||||
|
const start = new Date(0);
|
||||||
|
const end = new Date();
|
||||||
|
|
||||||
|
const statsHourData = await fetchStatistics(
|
||||||
|
this.hass,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
[statId],
|
||||||
|
"hour"
|
||||||
|
);
|
||||||
|
|
||||||
|
const statsHour = statId in statsHourData ? statsHourData[statId] : [];
|
||||||
|
if (statsHour.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats5MinData = await fetchStatistics(
|
||||||
|
this.hass,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
[statId],
|
||||||
|
"5minute"
|
||||||
|
);
|
||||||
|
|
||||||
|
const stats5Min = statId in stats5MinData ? stats5MinData[statId] : [];
|
||||||
|
// First datapoint of 5 minute data in the history is always junk since it counts the entire sum
|
||||||
|
// as the change, which we don't want here.
|
||||||
|
stats5Min.shift();
|
||||||
|
|
||||||
|
const combinedStatsData: CombinedStat[] = [];
|
||||||
|
statsHour.forEach((s) => {
|
||||||
|
combinedStatsData.push({ hour: s, fiveMin: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
const lasthour: CombinedStat = { hour: null, fiveMin: [] };
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
stats5Min.forEach((s) => {
|
||||||
|
let matched = false;
|
||||||
|
for (i; i < combinedStatsData.length; i++) {
|
||||||
|
const hour = combinedStatsData[i].hour;
|
||||||
|
if (hour && s.start >= hour.start && s.end <= hour.end) {
|
||||||
|
combinedStatsData[i].fiveMin.push(s);
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matched) {
|
||||||
|
lasthour.fiveMin.push(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
combinedStatsData.push(lasthour);
|
||||||
|
|
||||||
|
let statsOutliers: StatisticValue[] = [];
|
||||||
|
let min = 0;
|
||||||
|
const numOutliers = 10;
|
||||||
|
|
||||||
|
// Track the top 10 values.
|
||||||
|
const addOutlier = (s) => {
|
||||||
|
const val = Math.abs(s.change ?? 0);
|
||||||
|
if (statsOutliers.length < numOutliers || val > min) {
|
||||||
|
statsOutliers.push(s);
|
||||||
|
statsOutliers = statsOutliers.sort(
|
||||||
|
(a, b) => Math.abs(b.change ?? 0) - Math.abs(a.change ?? 0)
|
||||||
|
);
|
||||||
|
statsOutliers = statsOutliers.slice(0, numOutliers);
|
||||||
|
min = statsOutliers[statsOutliers.length - 1].change ?? 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If an hour has no five minute data, add the hour value
|
||||||
|
// Otherwise, add the 5 minute values and ignore the hour value
|
||||||
|
combinedStatsData.forEach((c) => {
|
||||||
|
if (c.fiveMin.length === 0 && c.hour) {
|
||||||
|
addOutlier(c.hour);
|
||||||
|
} else {
|
||||||
|
c.fiveMin.forEach((s) => {
|
||||||
|
addOutlier(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Outliers are a possible mix of hour/5minute data, but the distinction
|
||||||
|
// is not relevant here, as long as only one array is populated.
|
||||||
|
this._statsHour = statsOutliers;
|
||||||
|
this._stats5min = [];
|
||||||
|
}
|
||||||
|
|
||||||
private async _fixIssue(): Promise<void> {
|
private async _fixIssue(): Promise<void> {
|
||||||
const unit = getDisplayUnit(
|
const unit = getDisplayUnit(
|
||||||
this.hass,
|
this.hass,
|
||||||
|
@@ -290,6 +290,8 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
inset-inline-end: 8px;
|
||||||
|
inset-inline-start: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-alert {
|
ha-alert {
|
||||||
|
@@ -7,7 +7,7 @@ import {
|
|||||||
html,
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { mdiPencil } from "@mdi/js";
|
import { mdiPencil, mdiDownload } from "@mdi/js";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../components/ha-list-item";
|
import "../../components/ha-list-item";
|
||||||
@@ -19,6 +19,18 @@ import "../lovelace/components/hui-energy-period-selector";
|
|||||||
import { Lovelace } from "../lovelace/types";
|
import { Lovelace } from "../lovelace/types";
|
||||||
import "../lovelace/views/hui-view";
|
import "../lovelace/views/hui-view";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
|
import {
|
||||||
|
getEnergyDataCollection,
|
||||||
|
getEnergyGasUnit,
|
||||||
|
getEnergyWaterUnit,
|
||||||
|
GridSourceTypeEnergyPreference,
|
||||||
|
SolarSourceTypeEnergyPreference,
|
||||||
|
BatterySourceTypeEnergyPreference,
|
||||||
|
GasSourceTypeEnergyPreference,
|
||||||
|
WaterSourceTypeEnergyPreference,
|
||||||
|
DeviceConsumptionEnergyPreference,
|
||||||
|
} from "../../data/energy";
|
||||||
|
import { fileDownload } from "../../util/file_download";
|
||||||
|
|
||||||
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
||||||
views: [
|
views: [
|
||||||
@@ -86,6 +98,15 @@ class PanelEnergy extends LitElement {
|
|||||||
</ha-svg-icon>
|
</ha-svg-icon>
|
||||||
${this.hass!.localize("ui.panel.energy.configure")}
|
${this.hass!.localize("ui.panel.energy.configure")}
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
<ha-list-item
|
||||||
|
slot="overflow-menu"
|
||||||
|
graphic="icon"
|
||||||
|
@request-selected=${this._dumpCSV}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="graphic" .path=${mdiDownload}>
|
||||||
|
</ha-svg-icon>
|
||||||
|
${this.hass!.localize("ui.panel.energy.download_data")}
|
||||||
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
</hui-energy-period-selector>
|
</hui-energy-period-selector>
|
||||||
@@ -122,6 +143,182 @@ class PanelEnergy extends LitElement {
|
|||||||
navigate("/config/energy?historyBack=1");
|
navigate("/config/energy?historyBack=1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _dumpCSV(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const energyData = getEnergyDataCollection(this.hass, {
|
||||||
|
key: "energy_dashboard",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!energyData.prefs || !energyData.state.stats) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gasUnit =
|
||||||
|
getEnergyGasUnit(
|
||||||
|
this.hass,
|
||||||
|
energyData.prefs,
|
||||||
|
energyData.state.statsMetadata
|
||||||
|
) || "";
|
||||||
|
const waterUnit = getEnergyWaterUnit(this.hass);
|
||||||
|
const electricUnit = "kWh";
|
||||||
|
|
||||||
|
const energy_sources = energyData.prefs.energy_sources;
|
||||||
|
const device_consumption = energyData.prefs.device_consumption;
|
||||||
|
const stats = energyData.state.stats;
|
||||||
|
|
||||||
|
const timeSet = new Set<number>();
|
||||||
|
Object.values(stats).forEach((stat) => {
|
||||||
|
stat.forEach((datapoint) => {
|
||||||
|
timeSet.add(datapoint.start);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const times = Array.from(timeSet).sort();
|
||||||
|
|
||||||
|
const headers =
|
||||||
|
"entity_id,type,unit," +
|
||||||
|
times.map((t) => new Date(t).toISOString()).join(",") +
|
||||||
|
"\n";
|
||||||
|
const csv: string[] = [];
|
||||||
|
csv[0] = headers;
|
||||||
|
|
||||||
|
const processStat = function (stat: string, type: string, unit: string) {
|
||||||
|
let n = 0;
|
||||||
|
const row: string[] = [];
|
||||||
|
if (!stats[stat]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row.push(stat);
|
||||||
|
row.push(type);
|
||||||
|
row.push(unit.normalize("NFKD"));
|
||||||
|
times.forEach((t) => {
|
||||||
|
if (stats[stat][n].start > t) {
|
||||||
|
row.push("");
|
||||||
|
} else if (n < stats[stat].length && stats[stat][n].start === t) {
|
||||||
|
row.push((stats[stat][n].change ?? "").toString());
|
||||||
|
n++;
|
||||||
|
} else {
|
||||||
|
row.push("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
csv.push(row.join(",") + "\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
const currency = this.hass.config.currency;
|
||||||
|
|
||||||
|
const printCategory = function (
|
||||||
|
type: string,
|
||||||
|
statIds: string[],
|
||||||
|
unit: string,
|
||||||
|
costType?: string
|
||||||
|
) {
|
||||||
|
if (statIds.length) {
|
||||||
|
statIds.forEach((stat) => processStat(stat, type, unit));
|
||||||
|
if (costType) {
|
||||||
|
statIds.forEach((stat) => {
|
||||||
|
const costStat = energyData.state.info.cost_sensors[stat];
|
||||||
|
if (energyData.state.info.cost_sensors[stat]) {
|
||||||
|
processStat(costStat, costType, currency);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const grid_consumptions: string[] = [];
|
||||||
|
const grid_productions: string[] = [];
|
||||||
|
energy_sources
|
||||||
|
.filter((s) => s.type === "grid")
|
||||||
|
.forEach((source) => {
|
||||||
|
source = source as GridSourceTypeEnergyPreference;
|
||||||
|
source.flow_from.forEach((flowFrom) => {
|
||||||
|
grid_consumptions.push(flowFrom.stat_energy_from);
|
||||||
|
});
|
||||||
|
source.flow_to.forEach((flowTo) => {
|
||||||
|
grid_productions.push(flowTo.stat_energy_to);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
printCategory(
|
||||||
|
"grid_consumption",
|
||||||
|
grid_consumptions,
|
||||||
|
electricUnit,
|
||||||
|
"grid_consumption_cost"
|
||||||
|
);
|
||||||
|
printCategory(
|
||||||
|
"grid_return",
|
||||||
|
grid_productions,
|
||||||
|
electricUnit,
|
||||||
|
"grid_return_compensation"
|
||||||
|
);
|
||||||
|
|
||||||
|
const battery_ins: string[] = [];
|
||||||
|
const battery_outs: string[] = [];
|
||||||
|
energy_sources
|
||||||
|
.filter((s) => s.type === "battery")
|
||||||
|
.forEach((source) => {
|
||||||
|
source = source as BatterySourceTypeEnergyPreference;
|
||||||
|
battery_ins.push(source.stat_energy_to);
|
||||||
|
battery_outs.push(source.stat_energy_from);
|
||||||
|
});
|
||||||
|
|
||||||
|
printCategory("battery_in", battery_ins, electricUnit);
|
||||||
|
printCategory("battery_out", battery_outs, electricUnit);
|
||||||
|
|
||||||
|
const solar_productions: string[] = [];
|
||||||
|
energy_sources
|
||||||
|
.filter((s) => s.type === "solar")
|
||||||
|
.forEach((source) => {
|
||||||
|
source = source as SolarSourceTypeEnergyPreference;
|
||||||
|
solar_productions.push(source.stat_energy_from);
|
||||||
|
});
|
||||||
|
|
||||||
|
printCategory("solar_production", solar_productions, electricUnit);
|
||||||
|
|
||||||
|
const gas_consumptions: string[] = [];
|
||||||
|
energy_sources
|
||||||
|
.filter((s) => s.type === "gas")
|
||||||
|
.forEach((source) => {
|
||||||
|
source = source as GasSourceTypeEnergyPreference;
|
||||||
|
gas_consumptions.push(source.stat_energy_from);
|
||||||
|
});
|
||||||
|
|
||||||
|
printCategory(
|
||||||
|
"gas_consumption",
|
||||||
|
gas_consumptions,
|
||||||
|
gasUnit,
|
||||||
|
"gas_consumption_cost"
|
||||||
|
);
|
||||||
|
|
||||||
|
const water_consumptions: string[] = [];
|
||||||
|
energy_sources
|
||||||
|
.filter((s) => s.type === "water")
|
||||||
|
.forEach((source) => {
|
||||||
|
source = source as WaterSourceTypeEnergyPreference;
|
||||||
|
water_consumptions.push(source.stat_energy_from);
|
||||||
|
});
|
||||||
|
|
||||||
|
printCategory(
|
||||||
|
"water_consumption",
|
||||||
|
water_consumptions,
|
||||||
|
waterUnit,
|
||||||
|
"water_consumption_cost"
|
||||||
|
);
|
||||||
|
|
||||||
|
const devices: string[] = [];
|
||||||
|
device_consumption.forEach((source) => {
|
||||||
|
source = source as DeviceConsumptionEnergyPreference;
|
||||||
|
devices.push(source.stat_consumption);
|
||||||
|
});
|
||||||
|
|
||||||
|
printCategory("device_consumption", devices, electricUnit);
|
||||||
|
|
||||||
|
const blob = new Blob(csv, {
|
||||||
|
type: "text/csv",
|
||||||
|
});
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
fileDownload(url, "energy.csv");
|
||||||
|
}
|
||||||
|
|
||||||
private _reloadView() {
|
private _reloadView() {
|
||||||
// Force strategy to be re-run by make a copy of the view
|
// Force strategy to be re-run by make a copy of the view
|
||||||
const config = this._lovelace!.config;
|
const config = this._lovelace!.config;
|
||||||
|
@@ -154,6 +154,13 @@ export class EnergyViewStrategy extends ReactiveElement {
|
|||||||
|
|
||||||
// Only include if we have at least 1 device in the config.
|
// Only include if we have at least 1 device in the config.
|
||||||
if (prefs.device_consumption.length) {
|
if (prefs.device_consumption.length) {
|
||||||
|
view.cards!.push({
|
||||||
|
title: hass.localize(
|
||||||
|
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
||||||
|
),
|
||||||
|
type: "energy-devices-detail-graph",
|
||||||
|
collection_key: "energy_dashboard",
|
||||||
|
});
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: hass.localize(
|
title: hass.localize(
|
||||||
"ui.panel.energy.cards.energy_devices_graph_title"
|
"ui.panel.energy.cards.energy_devices_graph_title"
|
||||||
|
@@ -433,6 +433,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
this._statisticsHistory = computeHistory(
|
this._statisticsHistory = computeHistory(
|
||||||
this.hass,
|
this.hass,
|
||||||
statsHistoryStates,
|
statsHistoryStates,
|
||||||
|
[],
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
sensorNumericDeviceClasses,
|
sensorNumericDeviceClasses,
|
||||||
true
|
true
|
||||||
@@ -472,6 +473,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
this._stateHistory = computeHistory(
|
this._stateHistory = computeHistory(
|
||||||
this.hass,
|
this.hass,
|
||||||
history,
|
history,
|
||||||
|
entityIds,
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
sensorNumericDeviceClasses,
|
sensorNumericDeviceClasses,
|
||||||
true
|
true
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user