mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-02 22:27:23 +00:00
Compare commits
151 Commits
shortcut-n
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fff12acb6b | ||
|
|
3d327ed628 | ||
|
|
0d51648de1 | ||
|
|
c5642c15b8 | ||
|
|
7f885010de | ||
|
|
356d51f974 | ||
|
|
38a907e51e | ||
|
|
87c0b1d887 | ||
|
|
0f195015b7 | ||
|
|
17a976af67 | ||
|
|
a41a7e822a | ||
|
|
5473bf56c6 | ||
|
|
029eba7ab8 | ||
|
|
824a3f288d | ||
|
|
fdd89c05d3 | ||
|
|
c33cb7fff9 | ||
|
|
de53ad8dce | ||
|
|
2dec7490b6 | ||
|
|
6ed4bd5ce8 | ||
|
|
4e899c56ed | ||
|
|
53c20a0493 | ||
|
|
be2d6e9212 | ||
|
|
915442c571 | ||
|
|
de6ebc2d0a | ||
|
|
bcb12fa062 | ||
|
|
078915743d | ||
|
|
54c524127f | ||
|
|
334e1c35e1 | ||
|
|
528c7727e2 | ||
|
|
d1043e33df | ||
|
|
b088f2c0e5 | ||
|
|
b33f407493 | ||
|
|
502d76b316 | ||
|
|
1ddc07c215 | ||
|
|
a611a5fc4e | ||
|
|
c13bece6d0 | ||
|
|
28a89ff9e6 | ||
|
|
81b5ddec9d | ||
|
|
ce86aabe32 | ||
|
|
a8910bcbe4 | ||
|
|
0e4cf9f62d | ||
|
|
506635d649 | ||
|
|
27e24ee49b | ||
|
|
bcd712b48c | ||
|
|
461ef9b916 | ||
|
|
b794989daa | ||
|
|
6727ffaae0 | ||
|
|
4df8501b20 | ||
|
|
539d0e443f | ||
|
|
9c9d274b5c | ||
|
|
d6e6bc0e80 | ||
|
|
530a70b168 | ||
|
|
23137500f8 | ||
|
|
63e7ed21a4 | ||
|
|
92611f46f4 | ||
|
|
9bc896241d | ||
|
|
2baafe620c | ||
|
|
ce52bbaf8c | ||
|
|
0b4b8d9082 | ||
|
|
bddbb773b7 | ||
|
|
d52e1e8835 | ||
|
|
0a9dccfd19 | ||
|
|
bfd78670cc | ||
|
|
11276af1a0 | ||
|
|
d7be46c00b | ||
|
|
94f32ce242 | ||
|
|
ef3e8186bc | ||
|
|
50fcf622aa | ||
|
|
77c2444be8 | ||
|
|
e5cb26cd3d | ||
|
|
2896519bfd | ||
|
|
0b6e35eb53 | ||
|
|
e80a855f87 | ||
|
|
7c88cf4e30 | ||
|
|
9001cd3e65 | ||
|
|
ca8923d8f4 | ||
|
|
e7e4407a09 | ||
|
|
3f0c9538bd | ||
|
|
5c3ccbfdad | ||
|
|
9710142c47 | ||
|
|
57640d17cd | ||
|
|
b5d93e7515 | ||
|
|
ca9b29d82a | ||
|
|
51efa4f61f | ||
|
|
a6c71719d1 | ||
|
|
ccc48d158a | ||
|
|
b11e787f09 | ||
|
|
cdb6562de8 | ||
|
|
d8e8c9aa02 | ||
|
|
be392be1e6 | ||
|
|
10dc432445 | ||
|
|
19187f887d | ||
|
|
dc76a42aaa | ||
|
|
1f2b8047a6 | ||
|
|
e8c9ed0528 | ||
|
|
c7ae78c02f | ||
|
|
dc8f1211e6 | ||
|
|
5c25a63ea5 | ||
|
|
c1787ab994 | ||
|
|
6fea535fdc | ||
|
|
e8cee84380 | ||
|
|
b4613edeb7 | ||
|
|
a8b6e5aa3d | ||
|
|
e842193cd6 | ||
|
|
bb0813333d | ||
|
|
ab4c6f80f4 | ||
|
|
89796e425a | ||
|
|
9c42c8bbc4 | ||
|
|
616237caee | ||
|
|
2d36a0d37f | ||
|
|
1ec432a20f | ||
|
|
afd91b2261 | ||
|
|
cdfb7f914f | ||
|
|
33b0897522 | ||
|
|
5f0cf1b522 | ||
|
|
afb2ad95a4 | ||
|
|
27beab3133 | ||
|
|
435c82489b | ||
|
|
3ba6bf272e | ||
|
|
eec99b2fa3 | ||
|
|
d23e45e410 | ||
|
|
3c82d12609 | ||
|
|
15d67997e7 | ||
|
|
a6dfcb3100 | ||
|
|
26c2369228 | ||
|
|
2eed446492 | ||
|
|
7ebdeab6b2 | ||
|
|
0c35278f51 | ||
|
|
561122f03d | ||
|
|
95311be034 | ||
|
|
1eda44a102 | ||
|
|
d76781eb91 | ||
|
|
82d44e051f | ||
|
|
fdc9f5a3b7 | ||
|
|
ee6c82aba9 | ||
|
|
11d3f5c2ba | ||
|
|
feb68ce373 | ||
|
|
7f9a9de157 | ||
|
|
8e1b6a3d3b | ||
|
|
6e6e5a53e2 | ||
|
|
0408734ec5 | ||
|
|
317519fc08 | ||
|
|
843d79eab4 | ||
|
|
165a757f06 | ||
|
|
ea8b730142 | ||
|
|
e88c97d625 | ||
|
|
7560988b76 | ||
|
|
eecd8077b6 | ||
|
|
cbab5c3f7b | ||
|
|
a5d27c8bb8 | ||
|
|
a6a340b5db |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -57,4 +57,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||||
|
|||||||
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Send bundle stats and build information to RelativeCI
|
- name: Send bundle stats and build information to RelativeCI
|
||||||
uses: relative-ci/agent-action@feb19ddc698445db27401f1490f6ac182da0816f # v3.2.0
|
uses: relative-ci/agent-action@c45aaa919ef85620af54242a241ac17a8fa35983 # v3.2.1
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|||||||
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
|
|
||||||
# home-assistant/wheels doesn't support SHA pinning
|
# home-assistant/wheels doesn't support SHA pinning
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2025.10.0
|
uses: home-assistant/wheels@2025.11.0
|
||||||
with:
|
with:
|
||||||
abi: cp313
|
abi: cp313
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||||
with:
|
with:
|
||||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|
||||||
@@ -137,6 +137,6 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||||
with:
|
with:
|
||||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|||||||
@@ -305,9 +305,8 @@ export class HcMain extends HassElement {
|
|||||||
await llColl.refresh();
|
await llColl.refresh();
|
||||||
this._unsubLovelace = llColl.subscribe(async (rawConfig) => {
|
this._unsubLovelace = llColl.subscribe(async (rawConfig) => {
|
||||||
if (isStrategyDashboard(rawConfig)) {
|
if (isStrategyDashboard(rawConfig)) {
|
||||||
const { generateLovelaceDashboardStrategy } = await import(
|
const { generateLovelaceDashboardStrategy } =
|
||||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
await import("../../../../src/panels/lovelace/strategies/get-strategy");
|
||||||
);
|
|
||||||
const config = await generateLovelaceDashboardStrategy(
|
const config = await generateLovelaceDashboardStrategy(
|
||||||
rawConfig,
|
rawConfig,
|
||||||
this.hass!
|
this.hass!
|
||||||
@@ -347,9 +346,8 @@ export class HcMain extends HassElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _generateDefaultLovelaceConfig() {
|
private async _generateDefaultLovelaceConfig() {
|
||||||
const { generateLovelaceDashboardStrategy } = await import(
|
const { generateLovelaceDashboardStrategy } =
|
||||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
await import("../../../../src/panels/lovelace/strategies/get-strategy");
|
||||||
);
|
|
||||||
this._handleNewLovelaceConfig(
|
this._handleNewLovelaceConfig(
|
||||||
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { HaEventTrigger } from "../../../../src/panels/config/automation/trigger
|
|||||||
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
||||||
import { HaHassTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
|
import { HaHassTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
|
||||||
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
|
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
|
||||||
import { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
|
||||||
import { HaNumericStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
|
import { HaNumericStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
|
||||||
import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification";
|
import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification";
|
||||||
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
||||||
@@ -38,11 +37,6 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
|||||||
triggers: [{ ...HaStateTrigger.defaultConfig }],
|
triggers: [{ ...HaStateTrigger.defaultConfig }],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
name: "MQTT",
|
|
||||||
triggers: [{ ...HaMQTTTrigger.defaultConfig }],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "GeoLocation",
|
name: "GeoLocation",
|
||||||
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],
|
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
<ha-button
|
<ha-button
|
||||||
?disabled=${Boolean(
|
?disabled=${Boolean(
|
||||||
!this._input.registry ||
|
!this._input.registry ||
|
||||||
!this._input.username ||
|
!this._input.username ||
|
||||||
!this._input.password
|
!this._input.password
|
||||||
)}
|
)}
|
||||||
@click=${this._addNewRegistry}
|
@click=${this._addNewRegistry}
|
||||||
appearance="filled"
|
appearance="filled"
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -89,8 +89,8 @@
|
|||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@tsparticles/engine": "3.9.1",
|
"@tsparticles/engine": "3.9.1",
|
||||||
"@tsparticles/preset-links": "3.2.0",
|
"@tsparticles/preset-links": "3.2.0",
|
||||||
"@vaadin/combo-box": "24.9.5",
|
"@vaadin/combo-box": "24.9.6",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.9.5",
|
"@vaadin/vaadin-themable-mixin": "24.9.6",
|
||||||
"@vibrant/color": "4.0.0",
|
"@vibrant/color": "4.0.0",
|
||||||
"@vue/web-component-wrapper": "1.3.0",
|
"@vue/web-component-wrapper": "1.3.0",
|
||||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||||
@@ -152,13 +152,13 @@
|
|||||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||||
"@babel/plugin-transform-runtime": "7.28.5",
|
"@babel/plugin-transform-runtime": "7.28.5",
|
||||||
"@babel/preset-env": "7.28.5",
|
"@babel/preset-env": "7.28.5",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.21.6",
|
"@bundle-stats/plugin-webpack-filter": "4.21.7",
|
||||||
"@lokalise/node-api": "15.4.0",
|
"@lokalise/node-api": "15.4.0",
|
||||||
"@octokit/auth-oauth-device": "8.0.3",
|
"@octokit/auth-oauth-device": "8.0.3",
|
||||||
"@octokit/plugin-retry": "8.0.3",
|
"@octokit/plugin-retry": "8.0.3",
|
||||||
"@octokit/rest": "22.0.1",
|
"@octokit/rest": "22.0.1",
|
||||||
"@rsdoctor/rspack-plugin": "1.3.11",
|
"@rsdoctor/rspack-plugin": "1.3.11",
|
||||||
"@rspack/core": "1.6.4",
|
"@rspack/core": "1.6.5",
|
||||||
"@rspack/dev-server": "1.1.4",
|
"@rspack/dev-server": "1.1.4",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.22",
|
"@types/chromecast-caf-receiver": "6.0.22",
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
"@types/tar": "6.1.13",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@vitest/coverage-v8": "4.0.13",
|
"@vitest/coverage-v8": "4.0.14",
|
||||||
"babel-loader": "10.0.0",
|
"babel-loader": "10.0.0",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"browserslist-useragent-regexp": "4.1.3",
|
"browserslist-useragent-regexp": "4.1.3",
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
"eslint-plugin-wc": "3.0.2",
|
"eslint-plugin-wc": "3.0.2",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.3.2",
|
"fs-extra": "11.3.2",
|
||||||
"glob": "12.0.0",
|
"glob": "13.0.0",
|
||||||
"gulp": "5.0.1",
|
"gulp": "5.0.1",
|
||||||
"gulp-brotli": "3.0.0",
|
"gulp-brotli": "3.0.0",
|
||||||
"gulp-json-transform": "0.5.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.7.2",
|
||||||
"rspack-manifest-plugin": "5.2.0",
|
"rspack-manifest-plugin": "5.2.0",
|
||||||
"serve": "14.2.5",
|
"serve": "14.2.5",
|
||||||
"sinon": "21.0.0",
|
"sinon": "21.0.0",
|
||||||
@@ -217,9 +217,9 @@
|
|||||||
"terser-webpack-plugin": "5.3.14",
|
"terser-webpack-plugin": "5.3.14",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.47.0",
|
"typescript-eslint": "8.48.0",
|
||||||
"vite-tsconfig-paths": "5.1.4",
|
"vite-tsconfig-paths": "5.1.4",
|
||||||
"vitest": "4.0.13",
|
"vitest": "4.0.14",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "7.0.0",
|
"webpackbar": "7.0.0",
|
||||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { AuthData } from "home-assistant-js-websocket";
|
import type { AuthData } from "home-assistant-js-websocket";
|
||||||
import { extractSearchParam } from "../url/search-params";
|
import { extractSearchParam } from "../url/search-params";
|
||||||
|
import { hassUrl } from "../../data/auth";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -30,7 +31,11 @@ export function askWrite() {
|
|||||||
export function saveTokens(tokens: AuthData | null) {
|
export function saveTokens(tokens: AuthData | null) {
|
||||||
tokenCache.tokens = tokens;
|
tokenCache.tokens = tokens;
|
||||||
|
|
||||||
if (!tokenCache.writeEnabled && extractSearchParam("storeToken") === "true") {
|
if (
|
||||||
|
!tokenCache.writeEnabled &&
|
||||||
|
(extractSearchParam("storeToken") === "true" ||
|
||||||
|
hassUrl !== `${location.protocol}//${location.host}`)
|
||||||
|
) {
|
||||||
tokenCache.writeEnabled = true;
|
tokenCache.writeEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ export const computeFormatFunctions = async (
|
|||||||
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
||||||
formatEntityName: FormatEntityNameFunc;
|
formatEntityName: FormatEntityNameFunc;
|
||||||
}> => {
|
}> => {
|
||||||
const { computeStateDisplay } = await import(
|
const { computeStateDisplay } =
|
||||||
"../entity/compute_state_display"
|
await import("../entity/compute_state_display");
|
||||||
);
|
|
||||||
const { computeAttributeValueDisplay, computeAttributeNameDisplay } =
|
const { computeAttributeValueDisplay, computeAttributeNameDisplay } =
|
||||||
await import("../entity/compute_attribute_display");
|
await import("../entity/compute_attribute_display");
|
||||||
|
|
||||||
|
|||||||
@@ -593,6 +593,7 @@ export class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
const options = {
|
const options = {
|
||||||
animation: !this._reducedMotion,
|
animation: !this._reducedMotion,
|
||||||
|
animationDuration: 500,
|
||||||
darkMode: this._themes.darkMode ?? false,
|
darkMode: this._themes.darkMode ?? false,
|
||||||
aria: { show: true },
|
aria: { show: true },
|
||||||
dataZoom: this._getDataZoomConfig(),
|
dataZoom: this._getDataZoomConfig(),
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ export class HaSankeyChart extends LitElement {
|
|||||||
curveness: 0.5,
|
curveness: 0.5,
|
||||||
},
|
},
|
||||||
layoutIterations: 0,
|
layoutIterations: 0,
|
||||||
|
animationDuration: 500,
|
||||||
label: {
|
label: {
|
||||||
formatter: (params) =>
|
formatter: (params) =>
|
||||||
data.nodes.find((node) => node.id === (params.data as Node).id)
|
data.nodes.find((node) => node.id === (params.data as Node).id)
|
||||||
@@ -279,6 +280,7 @@ export class HaSankeyChart extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
max-width: 100%;
|
||||||
background: var(--ha-card-background, var(--card-background-color));
|
background: var(--ha-card-background, var(--card-background-color));
|
||||||
}
|
}
|
||||||
ha-chart-base {
|
ha-chart-base {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import type { VisualMapComponentOption } from "echarts/components";
|
import type { VisualMapComponentOption } from "echarts/components";
|
||||||
import type { LineSeriesOption } from "echarts/charts";
|
import type { LineSeriesOption } from "echarts/charts";
|
||||||
import type { YAXisOption } from "echarts/types/dist/shared";
|
import type { YAXisOption } from "echarts/types/dist/shared";
|
||||||
@@ -27,6 +27,7 @@ const safeParseFloat = (value) => {
|
|||||||
return isFinite(parsed) ? parsed : null;
|
return isFinite(parsed) ? parsed : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@customElement("state-history-chart-line")
|
||||||
export class StateHistoryChartLine extends LitElement {
|
export class StateHistoryChartLine extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -795,7 +796,6 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
return Math.abs(value) < 1 ? value : roundingFn(value);
|
return Math.abs(value) < 1 ? value : roundingFn(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
|||||||
@@ -838,10 +838,10 @@ export class HaDataTable extends LitElement {
|
|||||||
} else if (this.sortDirection === "asc") {
|
} else if (this.sortDirection === "asc") {
|
||||||
this.sortDirection = "desc";
|
this.sortDirection = "desc";
|
||||||
} else {
|
} else {
|
||||||
this.sortDirection = null;
|
this.sortDirection = "asc";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortColumn = this.sortDirection === null ? undefined : columnId;
|
this.sortColumn = columnId;
|
||||||
|
|
||||||
fireEvent(this, "sorting-changed", {
|
fireEvent(this, "sorting-changed", {
|
||||||
column: columnId,
|
column: columnId,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { mdiAlert } from "@mdi/js";
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
@@ -17,6 +17,7 @@ import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
|
|||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-state-icon";
|
import "../ha-state-icon";
|
||||||
|
|
||||||
|
@customElement("state-badge")
|
||||||
export class StateBadge extends LitElement {
|
export class StateBadge extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
@@ -265,5 +266,3 @@ declare global {
|
|||||||
"state-badge": StateBadge;
|
"state-badge": StateBadge;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("state-badge", StateBadge);
|
|
||||||
|
|||||||
@@ -659,6 +659,7 @@ export class HaAssistChat extends LitElement {
|
|||||||
--markdown-table-border-color: var(--divider-color);
|
--markdown-table-border-color: var(--divider-color);
|
||||||
--markdown-code-background-color: var(--primary-background-color);
|
--markdown-code-background-color: var(--primary-background-color);
|
||||||
--markdown-code-text-color: var(--primary-text-color);
|
--markdown-code-text-color: var(--primary-text-color);
|
||||||
|
--markdown-list-indent: 1rem;
|
||||||
&:not(:has(ha-markdown-element)) {
|
&:not(:has(ha-markdown-element)) {
|
||||||
min-height: 1lh;
|
min-height: 1lh;
|
||||||
min-width: 1lh;
|
min-width: 1lh;
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export class HaBottomSheet extends LitElement {
|
|||||||
|
|
||||||
private _isDragging = false;
|
private _isDragging = false;
|
||||||
|
|
||||||
private _handleAfterHide() {
|
private _handleAfterHide(afterHideEvent: Event) {
|
||||||
|
afterHideEvent.stopPropagation();
|
||||||
this.open = false;
|
this.open = false;
|
||||||
const ev = new Event("closed", {
|
const ev = new Event("closed", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ export class HaControlSelect extends LitElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
border-radius: var(--control-select-border-radius);
|
||||||
}
|
}
|
||||||
:host([vertical]) {
|
:host([vertical]) {
|
||||||
width: var(--control-select-thickness);
|
width: var(--control-select-thickness);
|
||||||
@@ -211,7 +212,6 @@ export class HaControlSelect extends LitElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: var(--control-select-border-radius);
|
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export class HaGenericPicker extends LitElement {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private _hidePicker(ev) {
|
private _hidePicker(ev: Event) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (this._newValue) {
|
if (this._newValue) {
|
||||||
fireEvent(this, "value-changed", { value: this._newValue });
|
fireEvent(this, "value-changed", { value: this._newValue });
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
@property({ attribute: "native-name", type: Boolean })
|
@property({ attribute: "native-name", type: Boolean })
|
||||||
public nativeName = false;
|
public nativeName = false;
|
||||||
|
|
||||||
@@ -135,6 +137,7 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
.value=${value}
|
.value=${value}
|
||||||
.valueRenderer=${this._valueRenderer}
|
.valueRenderer=${this._valueRenderer}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.helper=${this.helper}
|
||||||
.getItems=${this._getItems}
|
.getItems=${this._getItems}
|
||||||
@value-changed=${this._changed}
|
@value-changed=${this._changed}
|
||||||
hide-clear-icon
|
hide-clear-icon
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class HaMarkdownElement extends ReactiveElement {
|
|||||||
if (!this.innerHTML && this.cache) {
|
if (!this.innerHTML && this.cache) {
|
||||||
const key = this._computeCacheKey();
|
const key = this._computeCacheKey();
|
||||||
if (markdownCache.has(key)) {
|
if (markdownCache.has(key)) {
|
||||||
render(markdownCache.get(key)!, this.renderRoot);
|
render(h(unsafeHTML(markdownCache.get(key))), this.renderRoot);
|
||||||
this._resize();
|
this._resize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,13 +71,11 @@ export class HaMarkdown extends LitElement {
|
|||||||
color: var(--markdown-link-color, var(--primary-color));
|
color: var(--markdown-link-color, var(--primary-color));
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
background-color: rgba(10, 10, 10, 0.15);
|
background-color: var(--markdown-image-background-color);
|
||||||
border-radius: var(--markdown-image-border-radius);
|
border-radius: var(--markdown-image-border-radius);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-height: 2lh;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
text-indent: 4px;
|
|
||||||
transition: height 0.2s ease-in-out;
|
transition: height 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
p:first-child > img:first-child {
|
p:first-child > img:first-child {
|
||||||
@@ -86,10 +84,9 @@ export class HaMarkdown extends LitElement {
|
|||||||
p:first-child > img:last-child {
|
p:first-child > img:last-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
ol,
|
:host > ul,
|
||||||
ul {
|
:host > ol {
|
||||||
list-style-position: inside;
|
padding-inline-start: var(--markdown-list-indent, revert);
|
||||||
padding-inline-start: 0;
|
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
&:has(input[type="checkbox"]) {
|
&:has(input[type="checkbox"]) {
|
||||||
@@ -140,16 +137,19 @@ export class HaMarkdown extends LitElement {
|
|||||||
margin: var(--ha-space-4) 0;
|
margin: var(--ha-space-4) 0;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: var(--markdown-table-border-collapse, collapse);
|
||||||
display: block;
|
}
|
||||||
overflow-x: auto;
|
div:has(> table) {
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border: 1px solid var(--markdown-table-border-color, transparent);
|
border-width: var(--markdown-table-border-width, 1px);
|
||||||
|
border-style: var(--markdown-table-border-style, solid);
|
||||||
|
border-color: var(--markdown-table-border-color, var(--divider-color));
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
}
|
}
|
||||||
blockquote {
|
blockquote {
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ export class HaPickerField extends LitElement {
|
|||||||
--md-list-item-two-line-container-height: 56px;
|
--md-list-item-two-line-container-height: 56px;
|
||||||
--md-list-item-top-space: 0px;
|
--md-list-item-top-space: 0px;
|
||||||
--md-list-item-bottom-space: 0px;
|
--md-list-item-bottom-space: 0px;
|
||||||
--md-list-item-leading-space: 8px;
|
--md-list-item-leading-space: var(--ha-space-4);
|
||||||
--md-list-item-trailing-space: 8px;
|
--md-list-item-trailing-space: var(--ha-space-2);
|
||||||
--ha-md-list-item-gap: var(--ha-space-2);
|
--ha-md-list-item-gap: var(--ha-space-2);
|
||||||
/* Remove the default focus ring */
|
/* Remove the default focus ring */
|
||||||
--md-focus-ring-width: 0px;
|
--md-focus-ring-width: 0px;
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
|
|
||||||
const hasOptional = Boolean(
|
const hasOptional = Boolean(
|
||||||
!shouldRenderServiceDataYaml &&
|
!shouldRenderServiceDataYaml &&
|
||||||
serviceData?.flatFields.some((field) => showOptionalToggle(field))
|
serviceData?.flatFields.some((field) => showOptionalToggle(field))
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetEntities = this._getTargetedEntities(
|
const targetEntities = this._getTargetedEntities(
|
||||||
@@ -467,7 +467,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
|
|
||||||
const descriptionPlaceholders =
|
const descriptionPlaceholders =
|
||||||
domain && serviceName
|
domain && serviceName
|
||||||
? this.hass.services[domain][serviceName].description_placeholders
|
? this.hass.services[domain]?.[serviceName]?.description_placeholders
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _mouseLeaveTimeout?: number;
|
private _mouseLeaveTimeout?: number;
|
||||||
|
|
||||||
|
private _touchendTimeout?: number;
|
||||||
|
|
||||||
private _tooltipHideTimeout?: number;
|
private _tooltipHideTimeout?: number;
|
||||||
|
|
||||||
private _recentKeydownActiveUntil = 0;
|
private _recentKeydownActiveUntil = 0;
|
||||||
@@ -237,6 +239,18 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
// clear timeouts
|
||||||
|
clearTimeout(this._mouseLeaveTimeout);
|
||||||
|
clearTimeout(this._tooltipHideTimeout);
|
||||||
|
clearTimeout(this._touchendTimeout);
|
||||||
|
// set undefined values
|
||||||
|
this._mouseLeaveTimeout = undefined;
|
||||||
|
this._tooltipHideTimeout = undefined;
|
||||||
|
this._touchendTimeout = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -406,6 +420,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
class="ha-scrollbar"
|
class="ha-scrollbar"
|
||||||
@focusin=${this._listboxFocusIn}
|
@focusin=${this._listboxFocusIn}
|
||||||
@focusout=${this._listboxFocusOut}
|
@focusout=${this._listboxFocusOut}
|
||||||
|
@touchend=${this._listboxTouchend}
|
||||||
@scroll=${this._listboxScroll}
|
@scroll=${this._listboxScroll}
|
||||||
@keydown=${this._listboxKeydown}
|
@keydown=${this._listboxKeydown}
|
||||||
>
|
>
|
||||||
@@ -620,6 +635,14 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
this._hideTooltip();
|
this._hideTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _listboxTouchend() {
|
||||||
|
clearTimeout(this._touchendTimeout);
|
||||||
|
this._touchendTimeout = window.setTimeout(() => {
|
||||||
|
// Allow 1 second for users to read the tooltip on touch devices
|
||||||
|
this._hideTooltip();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
@eventOptions({
|
@eventOptions({
|
||||||
passive: true,
|
passive: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
mdiDevices,
|
mdiDevices,
|
||||||
mdiFormatListBulleted,
|
mdiFormatListBulleted,
|
||||||
mdiGestureDoubleTap,
|
mdiGestureDoubleTap,
|
||||||
mdiHomeAssistant,
|
|
||||||
mdiMapMarker,
|
mdiMapMarker,
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMessageAlert,
|
mdiMessageAlert,
|
||||||
@@ -23,6 +22,7 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { until } from "lit/directives/until";
|
import { until } from "lit/directives/until";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { FALLBACK_DOMAIN_ICONS, triggerIcon } from "../data/icons";
|
import { FALLBACK_DOMAIN_ICONS, triggerIcon } from "../data/icons";
|
||||||
|
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
@@ -7,8 +9,6 @@ import {
|
|||||||
state,
|
state,
|
||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { mdiClose } from "@mdi/js";
|
|
||||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
@@ -53,6 +53,7 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
|
|||||||
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
|
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
|
||||||
*
|
*
|
||||||
* @attr {boolean} open - Controls the dialog open state.
|
* @attr {boolean} open - Controls the dialog open state.
|
||||||
|
* @attr {("alert"|"standard")} type - Dialog type. Defaults to "standard".
|
||||||
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
|
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
|
||||||
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
|
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
|
||||||
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
||||||
@@ -84,6 +85,9 @@ export class HaWaDialog extends LitElement {
|
|||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public open = false;
|
public open = false;
|
||||||
|
|
||||||
|
@property({ reflect: true })
|
||||||
|
public type: "alert" | "standard" = "standard";
|
||||||
|
|
||||||
@property({ type: String, reflect: true, attribute: "width" })
|
@property({ type: String, reflect: true, attribute: "width" })
|
||||||
public width: DialogWidth = "medium";
|
public width: DialogWidth = "medium";
|
||||||
|
|
||||||
@@ -172,7 +176,9 @@ export class HaWaDialog extends LitElement {
|
|||||||
|
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
|
||||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
requestAnimationFrame(() => {
|
||||||
|
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private _handleAfterShow = () => {
|
private _handleAfterShow = () => {
|
||||||
@@ -198,18 +204,7 @@ export class HaWaDialog extends LitElement {
|
|||||||
haStyleScrollbar,
|
haStyleScrollbar,
|
||||||
css`
|
css`
|
||||||
wa-dialog {
|
wa-dialog {
|
||||||
--full-width: var(
|
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
|
||||||
--ha-dialog-width-full,
|
|
||||||
min(
|
|
||||||
95vw,
|
|
||||||
calc(
|
|
||||||
100vw - var(--safe-area-inset-left, var(--ha-space-0)) - var(
|
|
||||||
--safe-area-inset-right,
|
|
||||||
var(--ha-space-0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||||
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
||||||
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
||||||
@@ -226,8 +221,7 @@ export class HaWaDialog extends LitElement {
|
|||||||
--ha-dialog-border-radius,
|
--ha-dialog-border-radius,
|
||||||
var(--ha-border-radius-3xl)
|
var(--ha-border-radius-3xl)
|
||||||
);
|
);
|
||||||
max-width: var(--ha-dialog-max-width, 100vw);
|
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
||||||
max-width: var(--ha-dialog-max-width, 100svw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([width="small"]) wa-dialog {
|
:host([width="small"]) wa-dialog {
|
||||||
@@ -247,34 +241,57 @@ export class HaWaDialog extends LitElement {
|
|||||||
max-width: var(--width, var(--full-width));
|
max-width: var(--width, var(--full-width));
|
||||||
max-height: var(
|
max-height: var(
|
||||||
--ha-dialog-max-height,
|
--ha-dialog-max-height,
|
||||||
calc(100% - var(--ha-space-20))
|
calc(var(--safe-height) - var(--ha-space-20))
|
||||||
);
|
);
|
||||||
min-height: var(--ha-dialog-min-height);
|
min-height: var(--ha-dialog-min-height);
|
||||||
position: var(--dialog-surface-position, relative);
|
position: var(--dialog-surface-position, relative);
|
||||||
margin-top: var(--dialog-surface-margin-top, auto);
|
margin-top: var(--dialog-surface-margin-top, auto);
|
||||||
|
/* Used to offset the dialog from the safe areas when space is limited */
|
||||||
|
transform: translate(
|
||||||
|
calc(
|
||||||
|
var(--safe-area-offset-left, var(--ha-space-0)) - var(
|
||||||
|
--safe-area-offset-right,
|
||||||
|
var(--ha-space-0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
calc(
|
||||||
|
var(--safe-area-offset-top, var(--ha-space-0)) - var(
|
||||||
|
--safe-area-offset-bottom,
|
||||||
|
var(--ha-space-0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
:host {
|
:host([type="standard"]) {
|
||||||
--ha-dialog-border-radius: var(--ha-space-0);
|
--ha-dialog-border-radius: var(--ha-space-0);
|
||||||
}
|
|
||||||
|
|
||||||
wa-dialog {
|
wa-dialog {
|
||||||
--full-width: var(--ha-dialog-width-full, 100vw);
|
/* Make the container fill the whole screen width and not the safe width */
|
||||||
}
|
--full-width: var(--ha-dialog-width-full, 100vw);
|
||||||
|
--width: var(--full-width);
|
||||||
|
}
|
||||||
|
|
||||||
wa-dialog::part(dialog) {
|
wa-dialog::part(dialog) {
|
||||||
min-height: var(--ha-dialog-min-height, 100vh);
|
/* Make the dialog fill the whole screen height and not the safe height */
|
||||||
min-height: var(--ha-dialog-min-height, 100svh);
|
min-height: var(--ha-dialog-min-height, 100vh);
|
||||||
max-height: var(--ha-dialog-max-height, 100vh);
|
min-height: var(--ha-dialog-min-height, 100dvh);
|
||||||
max-height: var(--ha-dialog-max-height, 100svh);
|
max-height: var(--ha-dialog-max-height, 100vh);
|
||||||
padding-top: var(--safe-area-inset-top, var(--ha-space-0));
|
max-height: var(--ha-dialog-max-height, 100dvh);
|
||||||
padding-bottom: var(--safe-area-inset-bottom, var(--ha-space-0));
|
margin-top: 0;
|
||||||
padding-left: var(--safe-area-inset-left, var(--ha-space-0));
|
margin-bottom: 0;
|
||||||
padding-right: var(--safe-area-inset-right, var(--ha-space-0));
|
/* Use safe area as padding instead of the container size */
|
||||||
|
padding-top: var(--safe-area-inset-top);
|
||||||
|
padding-bottom: var(--safe-area-inset-bottom);
|
||||||
|
padding-left: var(--safe-area-inset-left);
|
||||||
|
padding-right: var(--safe-area-inset-right);
|
||||||
|
/* Reset the transform to center the dialog */
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-state-icon";
|
import "../ha-state-icon";
|
||||||
|
|
||||||
|
@customElement("ha-entity-marker")
|
||||||
class HaEntityMarker extends LitElement {
|
class HaEntityMarker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -89,8 +90,6 @@ class HaEntityMarker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-entity-marker", HaEntityMarker);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-entity-marker": HaEntityMarker;
|
"ha-entity-marker": HaEntityMarker;
|
||||||
|
|||||||
@@ -75,17 +75,11 @@ export const reorderAreaRegistryEntries = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getAreaEntityLookup = (
|
export const getAreaEntityLookup = (
|
||||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||||
filterHidden = false
|
|
||||||
): AreaEntityLookup => {
|
): AreaEntityLookup => {
|
||||||
const areaEntityLookup: AreaEntityLookup = {};
|
const areaEntityLookup: AreaEntityLookup = {};
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (
|
if (!entity.area_id) {
|
||||||
!entity.area_id ||
|
|
||||||
(filterHidden &&
|
|
||||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
|
||||||
(entity as EntityRegistryEntry).hidden_by))
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!(entity.area_id in areaEntityLookup)) {
|
if (!(entity.area_id in areaEntityLookup)) {
|
||||||
|
|||||||
@@ -114,12 +114,6 @@ export interface StateTrigger extends BaseTrigger {
|
|||||||
for?: string | number | ForDict;
|
for?: string | number | ForDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MqttTrigger extends BaseTrigger {
|
|
||||||
trigger: "mqtt";
|
|
||||||
topic: string;
|
|
||||||
payload?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GeoLocationTrigger extends BaseTrigger {
|
export interface GeoLocationTrigger extends BaseTrigger {
|
||||||
trigger: "geo_location";
|
trigger: "geo_location";
|
||||||
source: string;
|
source: string;
|
||||||
@@ -127,6 +121,12 @@ export interface GeoLocationTrigger extends BaseTrigger {
|
|||||||
event: "enter" | "leave";
|
event: "enter" | "leave";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MqttTrigger extends BaseTrigger {
|
||||||
|
trigger: "mqtt";
|
||||||
|
topic: string;
|
||||||
|
payload?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HassTrigger extends BaseTrigger {
|
export interface HassTrigger extends BaseTrigger {
|
||||||
trigger: "homeassistant";
|
trigger: "homeassistant";
|
||||||
event: "start" | "shutdown";
|
event: "start" | "shutdown";
|
||||||
|
|||||||
@@ -144,9 +144,7 @@ const tryDescribeTrigger = (
|
|||||||
const type = getTriggerObjectId(trigger.trigger);
|
const type = getTriggerObjectId(trigger.trigger);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(`component.${domain}.triggers.${type}.name`) ||
|
||||||
`component.${domain}.triggers.${type}.description_configured`
|
|
||||||
) ||
|
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`ui.panel.config.automation.editor.triggers.type.${triggerType as LegacyTrigger["trigger"]}.label`
|
`ui.panel.config.automation.editor.triggers.type.${triggerType as LegacyTrigger["trigger"]}.label`
|
||||||
) ||
|
) ||
|
||||||
@@ -919,9 +917,7 @@ const tryDescribeCondition = (
|
|||||||
const type = getConditionObjectId(condition.condition);
|
const type = getConditionObjectId(condition.condition);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hass.localize(
|
hass.localize(`component.${domain}.conditions.${type}.name`) ||
|
||||||
`component.${domain}.conditions.${type}.description_configured`
|
|
||||||
) ||
|
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
||||||
) ||
|
) ||
|
||||||
|
|||||||
@@ -111,17 +111,11 @@ export const sortDeviceRegistryByName = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const getDeviceEntityLookup = (
|
export const getDeviceEntityLookup = (
|
||||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||||
filterHidden = false
|
|
||||||
): DeviceEntityLookup => {
|
): DeviceEntityLookup => {
|
||||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (
|
if (!entity.device_id) {
|
||||||
!entity.device_id ||
|
|
||||||
(filterHidden &&
|
|
||||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
|
||||||
(entity as EntityRegistryEntry).hidden_by))
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!(entity.device_id in deviceEntityLookup)) {
|
if (!(entity.device_id in deviceEntityLookup)) {
|
||||||
|
|||||||
14
src/data/esphome.ts
Normal file
14
src/data/esphome.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface ESPHomeEncryptionKey {
|
||||||
|
encryption_key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchESPHomeEncryptionKey = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry_id: string
|
||||||
|
): Promise<ESPHomeEncryptionKey> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "esphome/get_encryption_key",
|
||||||
|
entry_id,
|
||||||
|
});
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { stringCompare } from "../common/string/compare";
|
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { AreaRegistryEntry } from "./area_registry";
|
import type { AreaRegistryEntry } from "./area_registry";
|
||||||
import type { RegistryEntry } from "./registry";
|
import type { RegistryEntry } from "./registry";
|
||||||
@@ -75,27 +74,3 @@ export const getFloorAreaLookup = (
|
|||||||
}
|
}
|
||||||
return floorAreaLookup;
|
return floorAreaLookup;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const floorCompare =
|
|
||||||
(entries?: HomeAssistant["floors"], order?: string[]) =>
|
|
||||||
(a: string, b: string) => {
|
|
||||||
const indexA = order ? order.indexOf(a) : -1;
|
|
||||||
const indexB = order ? order.indexOf(b) : -1;
|
|
||||||
if (indexA === -1 && indexB === -1) {
|
|
||||||
const floorA = entries?.[a];
|
|
||||||
const floorB = entries?.[b];
|
|
||||||
if (floorA && floorB && floorA.level !== floorB.level) {
|
|
||||||
return (floorB.level ?? -9999) - (floorA.level ?? -9999);
|
|
||||||
}
|
|
||||||
const nameA = floorA?.name ?? a;
|
|
||||||
const nameB = floorB?.name ?? b;
|
|
||||||
return stringCompare(nameA, nameB);
|
|
||||||
}
|
|
||||||
if (indexA === -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (indexB === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return indexA - indexB;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ export interface HassioFullBackupCreateParams {
|
|||||||
confirm_password?: string;
|
confirm_password?: string;
|
||||||
background?: boolean;
|
background?: boolean;
|
||||||
}
|
}
|
||||||
export interface HassioPartialBackupCreateParams
|
export interface HassioPartialBackupCreateParams extends HassioFullBackupCreateParams {
|
||||||
extends HassioFullBackupCreateParams {
|
|
||||||
folders?: string[];
|
folders?: string[];
|
||||||
addons?: string[];
|
addons?: string[];
|
||||||
homeassistant?: boolean;
|
homeassistant?: boolean;
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ export const enum LawnMowerEntityFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface LawnMowerEntityAttributes
|
interface LawnMowerEntityAttributes
|
||||||
extends HassEntityAttributeBase,
|
extends HassEntityAttributeBase, Record<string, any> {}
|
||||||
Record<string, any> {}
|
|
||||||
|
|
||||||
export interface LawnMowerEntity extends HassEntityBase {
|
export interface LawnMowerEntity extends HassEntityBase {
|
||||||
attributes: LawnMowerEntityAttributes;
|
attributes: LawnMowerEntityAttributes;
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
|||||||
cards?: LovelaceCardConfig[];
|
cards?: LovelaceCardConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceStrategySectionConfig
|
export interface LovelaceStrategySectionConfig extends LovelaceBaseSectionConfig {
|
||||||
extends LovelaceBaseSectionConfig {
|
|
||||||
strategy: LovelaceStrategyConfig;
|
strategy: LovelaceStrategyConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
|
|||||||
views: LovelaceViewRawConfig[];
|
views: LovelaceViewRawConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceDashboardStrategyConfig
|
export interface LovelaceDashboardStrategyConfig extends LovelaceDashboardBaseConfig {
|
||||||
extends LovelaceDashboardBaseConfig {
|
|
||||||
strategy: LovelaceStrategyConfig;
|
strategy: LovelaceStrategyConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ export interface LovelaceDashboardMutableParams {
|
|||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceDashboardCreateParams
|
export interface LovelaceDashboardCreateParams extends LovelaceDashboardMutableParams {
|
||||||
extends LovelaceDashboardMutableParams {
|
|
||||||
url_path: string;
|
url_path: string;
|
||||||
mode: "storage";
|
mode: "storage";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,8 +106,7 @@ export interface AutomationTrace extends BaseTrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationTraceExtended
|
export interface AutomationTraceExtended
|
||||||
extends AutomationTrace,
|
extends AutomationTrace, BaseTraceExtended {
|
||||||
BaseTraceExtended {
|
|
||||||
config: ManualAutomationConfig;
|
config: ManualAutomationConfig;
|
||||||
blueprint_inputs?: BlueprintAutomationConfig;
|
blueprint_inputs?: BlueprintAutomationConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
|||||||
event: {},
|
event: {},
|
||||||
geo_location: {},
|
geo_location: {},
|
||||||
homeassistant: {},
|
homeassistant: {},
|
||||||
mqtt: {},
|
|
||||||
conversation: {},
|
conversation: {},
|
||||||
tag: {},
|
tag: {},
|
||||||
template: {},
|
template: {},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { mdiAlertOutline, mdiClose } from "@mdi/js";
|
import { mdiAlertOutline, mdiClose } from "@mdi/js";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-button";
|
import "../../components/ha-button";
|
||||||
@@ -64,6 +65,7 @@ class DialogBox extends LitElement {
|
|||||||
<ha-wa-dialog
|
<ha-wa-dialog
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.open=${this._open}
|
.open=${this._open}
|
||||||
|
type=${confirmPrompt ? "alert" : "standard"}
|
||||||
?prevent-scrim-close=${confirmPrompt}
|
?prevent-scrim-close=${confirmPrompt}
|
||||||
@closed=${this._dialogClosed}
|
@closed=${this._dialogClosed}
|
||||||
aria-labelledby="dialog-box-title"
|
aria-labelledby="dialog-box-title"
|
||||||
@@ -79,7 +81,11 @@ class DialogBox extends LitElement {
|
|||||||
></ha-icon-button
|
></ha-icon-button
|
||||||
></slot>`
|
></slot>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<span slot="title" id="dialog-box-title">
|
<span
|
||||||
|
class=${classMap({ title: true, alert: confirmPrompt })}
|
||||||
|
slot="title"
|
||||||
|
id="dialog-box-title"
|
||||||
|
>
|
||||||
${this._params.warning
|
${this._params.warning
|
||||||
? html`<ha-svg-icon
|
? html`<ha-svg-icon
|
||||||
.path=${mdiAlertOutline}
|
.path=${mdiAlertOutline}
|
||||||
@@ -199,6 +205,14 @@ class DialogBox extends LitElement {
|
|||||||
ha-textfield {
|
ha-textfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.title.alert {
|
||||||
|
padding: 0 var(--ha-space-2);
|
||||||
|
}
|
||||||
|
@media all and (min-width: 450px) and (min-height: 500px) {
|
||||||
|
.title.alert {
|
||||||
|
padding: 0 var(--ha-space-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogBoxParams
|
export interface DialogBoxParams
|
||||||
extends ConfirmationDialogParams,
|
extends ConfirmationDialogParams, PromptDialogParams {
|
||||||
PromptDialogParams {
|
|
||||||
confirm?: (out?: string) => void;
|
confirm?: (out?: string) => void;
|
||||||
confirmation?: boolean;
|
confirmation?: boolean;
|
||||||
prompt?: boolean;
|
prompt?: boolean;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
|
||||||
import { mainWindow } from "../common/dom/get_main_window";
|
|
||||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
|
||||||
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
||||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||||
|
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||||
|
import { mainWindow } from "../common/dom/get_main_window";
|
||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
|
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@@ -19,10 +19,11 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]>
|
export interface HassDialog<
|
||||||
extends HTMLElement {
|
T = HASSDomEvents[ValidHassDomEvent],
|
||||||
|
> extends HTMLElement {
|
||||||
showDialog(params: T);
|
showDialog(params: T);
|
||||||
closeDialog?: () => boolean;
|
closeDialog?: (historyState?: any) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShowDialogParams<T> {
|
interface ShowDialogParams<T> {
|
||||||
@@ -143,27 +144,32 @@ export const showDialog = async (
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const closeDialog = async (dialogTag: string): Promise<boolean> => {
|
export const closeDialog = async (
|
||||||
|
dialogTag: string,
|
||||||
|
historyState?: any
|
||||||
|
): Promise<boolean> => {
|
||||||
if (!(dialogTag in LOADED)) {
|
if (!(dialogTag in LOADED)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const dialogElement = await LOADED[dialogTag].element;
|
const dialogElement = await LOADED[dialogTag].element;
|
||||||
if (dialogElement.closeDialog) {
|
if (dialogElement.closeDialog) {
|
||||||
return dialogElement.closeDialog() !== false;
|
return dialogElement.closeDialog(historyState) !== false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// called on back()
|
// called on back()
|
||||||
export const closeLastDialog = async () => {
|
export const closeLastDialog = async (historyState?: any) => {
|
||||||
if (OPEN_DIALOG_STACK.length) {
|
if (OPEN_DIALOG_STACK.length) {
|
||||||
const lastDialog = OPEN_DIALOG_STACK.pop();
|
const lastDialog = OPEN_DIALOG_STACK.pop() as DialogState;
|
||||||
const closed = await closeDialog(lastDialog!.dialogTag);
|
const closed = await closeDialog(lastDialog.dialogTag, historyState);
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
// if the dialog was not closed, put it back on the stack
|
// if the dialog was not closed, put it back on the stack
|
||||||
OPEN_DIALOG_STACK.push(lastDialog!);
|
OPEN_DIALOG_STACK.push(lastDialog);
|
||||||
}
|
} else if (
|
||||||
if (OPEN_DIALOG_STACK.length && mainWindow.history.state?.opensDialog) {
|
OPEN_DIALOG_STACK.length &&
|
||||||
|
mainWindow.history.state?.opensDialog
|
||||||
|
) {
|
||||||
// if there are more dialogs open, push a new state so back() will close the next top dialog
|
// if there are more dialogs open, push a new state so back() will close the next top dialog
|
||||||
mainWindow.history.pushState(
|
mainWindow.history.pushState(
|
||||||
{ dialog: OPEN_DIALOG_STACK[OPEN_DIALOG_STACK.length - 1].dialogTag },
|
{ dialog: OPEN_DIALOG_STACK[OPEN_DIALOG_STACK.length - 1].dialogTag },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { slugify } from "../../../common/string/slugify";
|
import { slugify } from "../../../common/string/slugify";
|
||||||
import "../../../components/buttons/ha-progress-button";
|
import "../../../components/buttons/ha-progress-button";
|
||||||
import "../../../components/ha-camera-stream";
|
import "../../../components/ha-camera-stream";
|
||||||
@@ -9,6 +9,7 @@ import type { HomeAssistant } from "../../../types";
|
|||||||
import { fileDownload } from "../../../util/file_download";
|
import { fileDownload } from "../../../util/file_download";
|
||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
|
|
||||||
|
@customElement("more-info-camera")
|
||||||
class MoreInfoCamera extends LitElement {
|
class MoreInfoCamera extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -112,8 +113,6 @@ class MoreInfoCamera extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("more-info-camera", MoreInfoCamera);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"more-info-camera": MoreInfoCamera;
|
"more-info-camera": MoreInfoCamera;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import "../../../components/ha-attribute-icon";
|
import "../../../components/ha-attribute-icon";
|
||||||
@@ -32,6 +32,7 @@ import { moreInfoControlStyle } from "../components/more-info-control-style";
|
|||||||
|
|
||||||
type MainControl = "temperature" | "humidity";
|
type MainControl = "temperature" | "humidity";
|
||||||
|
|
||||||
|
@customElement("more-info-climate")
|
||||||
class MoreInfoClimate extends LitElement {
|
class MoreInfoClimate extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -567,8 +568,6 @@ class MoreInfoClimate extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("more-info-climate", MoreInfoClimate);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"more-info-climate": MoreInfoClimate;
|
"more-info-climate": MoreInfoClimate;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||||
import type { GroupEntity } from "../../../data/group";
|
import type { GroupEntity } from "../../../data/group";
|
||||||
import { computeGroupDomain } from "../../../data/group";
|
import { computeGroupDomain } from "../../../data/group";
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
importMoreInfoControl,
|
importMoreInfoControl,
|
||||||
} from "../state_more_info_control";
|
} from "../state_more_info_control";
|
||||||
|
|
||||||
|
@customElement("more-info-group")
|
||||||
class MoreInfoGroup extends LitElement {
|
class MoreInfoGroup extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -106,8 +107,6 @@ class MoreInfoGroup extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("more-info-group", MoreInfoGroup);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"more-info-group": MoreInfoGroup;
|
"more-info-group": MoreInfoGroup;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mdiPower, mdiTuneVariant } from "@mdi/js";
|
import { mdiPower, mdiTuneVariant } from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import "../../../components/ha-control-select-menu";
|
import "../../../components/ha-control-select-menu";
|
||||||
@@ -15,6 +15,7 @@ import type { HomeAssistant } from "../../../types";
|
|||||||
import "../components/ha-more-info-control-select-container";
|
import "../components/ha-more-info-control-select-container";
|
||||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||||
|
|
||||||
|
@customElement("more-info-humidifier")
|
||||||
class MoreInfoHumidifier extends LitElement {
|
class MoreInfoHumidifier extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -249,8 +250,6 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("more-info-humidifier", MoreInfoHumidifier);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"more-info-humidifier": MoreInfoHumidifier;
|
"more-info-humidifier": MoreInfoHumidifier;
|
||||||
|
|||||||
@@ -63,11 +63,6 @@ const _SHORTCUTS: Section[] = [
|
|||||||
descriptionTranslationKey:
|
descriptionTranslationKey:
|
||||||
"ui.dialogs.shortcuts.searching.search_in_table",
|
"ui.dialogs.shortcuts.searching.search_in_table",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
shortcut: ["N"],
|
|
||||||
descriptionTranslationKey:
|
|
||||||
"ui.dialogs.shortcuts.searching.new_in_table",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../components/ha-spinner";
|
import "../components/ha-spinner";
|
||||||
import "../components/ha-button";
|
import "../components/ha-button";
|
||||||
|
|
||||||
|
@customElement("ha-init-page")
|
||||||
class HaInitPage extends LitElement {
|
class HaInitPage extends LitElement {
|
||||||
@property({ type: Boolean }) public error = false;
|
@property({ type: Boolean }) public error = false;
|
||||||
|
|
||||||
@@ -120,8 +121,6 @@ class HaInitPage extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-init-page", HaInitPage);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-init-page": HaInitPage;
|
"ha-init-page": HaInitPage;
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ class HassSubpage extends LitElement {
|
|||||||
ha-menu-button,
|
ha-menu-button,
|
||||||
ha-icon-button-arrow-prev,
|
ha-icon-button-arrow-prev,
|
||||||
::slotted([slot="toolbar-icon"]) {
|
::slotted([slot="toolbar-icon"]) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
color: var(--sidebar-icon-color);
|
color: var(--sidebar-icon-color);
|
||||||
}
|
}
|
||||||
@@ -143,7 +145,6 @@ class HassSubpage extends LitElement {
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding-bottom: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -190,21 +190,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
|
||||||
if (this.hasFab) {
|
|
||||||
return {
|
|
||||||
n: () => {
|
|
||||||
const fab = this.querySelector<HTMLElement>('[slot="fab"]');
|
|
||||||
if (fab) {
|
|
||||||
fab.click();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _showPaneController = new ResizeController(this, {
|
private _showPaneController = new ResizeController(this, {
|
||||||
callback: (entries) => entries[0]?.contentRect.width > 750,
|
callback: (entries) => entries[0]?.contentRect.width > 750,
|
||||||
});
|
});
|
||||||
@@ -636,9 +621,9 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
|||||||
} else if (this._sortDirection === "asc") {
|
} else if (this._sortDirection === "asc") {
|
||||||
this._sortDirection = "desc";
|
this._sortDirection = "desc";
|
||||||
} else {
|
} else {
|
||||||
this._sortDirection = null;
|
this._sortDirection = "asc";
|
||||||
}
|
}
|
||||||
this._sortColumn = this._sortDirection === null ? undefined : columnId;
|
this._sortColumn = columnId;
|
||||||
|
|
||||||
fireEvent(this, "sorting-changed", {
|
fireEvent(this, "sorting-changed", {
|
||||||
column: columnId,
|
column: columnId,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose } from "@mdi/js";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import type { LocalizeKeys } from "../common/translations/localize";
|
import type { LocalizeKeys } from "../common/translations/localize";
|
||||||
import "../components/ha-button";
|
import "../components/ha-button";
|
||||||
import "../components/ha-icon-button";
|
import "../components/ha-icon-button";
|
||||||
@@ -26,6 +26,7 @@ export interface ToastActionParams {
|
|||||||
| { translationKey: LocalizeKeys; args?: Record<string, string> };
|
| { translationKey: LocalizeKeys; args?: Record<string, string> };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("notification-manager")
|
||||||
class NotificationManager extends LitElement {
|
class NotificationManager extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -115,8 +116,6 @@ class NotificationManager extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("notification-manager", NotificationManager);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"notification-manager": NotificationManager;
|
"notification-manager": NotificationManager;
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ class OnboardingRestoreBackupCloudLogin extends LitElement {
|
|||||||
this._email = this._cloudLoginElement.emailField.value;
|
this._email = this._cloudLoginElement.emailField.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await import(
|
await import("../../panels/config/cloud/forgot-password/cloud-forgot-password-card");
|
||||||
"../../panels/config/cloud/forgot-password/cloud-forgot-password-card"
|
|
||||||
);
|
|
||||||
this._view = "forgot-password";
|
this._view = "forgot-password";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { TZDate } from "@date-fns/tz";
|
|||||||
import { addDays, isSameDay } from "date-fns";
|
import { addDays, isSameDay } from "date-fns";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { formatDate } from "../../common/datetime/format_date";
|
import { formatDate } from "../../common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||||
import { formatTime } from "../../common/datetime/format_time";
|
import { formatTime } from "../../common/datetime/format_time";
|
||||||
@@ -26,6 +26,7 @@ import type { CalendarEventDetailDialogParams } from "./show-dialog-calendar-eve
|
|||||||
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
|
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
|
||||||
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
|
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
|
||||||
|
|
||||||
|
@customElement("dialog-calendar-event-detail")
|
||||||
class DialogCalendarEventDetail extends LitElement {
|
class DialogCalendarEventDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -271,8 +272,3 @@ declare global {
|
|||||||
"dialog-calendar-event-detail": DialogCalendarEventDetail;
|
"dialog-calendar-event-detail": DialogCalendarEventDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(
|
|
||||||
"dialog-calendar-event-detail",
|
|
||||||
DialogCalendarEventDetail
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strateg
|
|||||||
import type { Lovelace } from "../lovelace/types";
|
import type { Lovelace } from "../lovelace/types";
|
||||||
import "../lovelace/views/hui-view";
|
import "../lovelace/views/hui-view";
|
||||||
import "../lovelace/views/hui-view-container";
|
import "../lovelace/views/hui-view-container";
|
||||||
|
import "../lovelace/views/hui-view-background";
|
||||||
|
|
||||||
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||||
strategy: {
|
strategy: {
|
||||||
@@ -115,6 +116,7 @@ class PanelClimate extends LitElement {
|
|||||||
this._lovelace
|
this._lovelace
|
||||||
? html`
|
? html`
|
||||||
<hui-view-container .hass=${this.hass}>
|
<hui-view-container .hass=${this.hass}>
|
||||||
|
<hui-view-background .hass=${this.hass}> </hui-view-background>
|
||||||
<hui-view
|
<hui-view
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/entity/ha-entity-picker";
|
import "../../../components/entity/ha-entity-picker";
|
||||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||||
@@ -40,6 +40,7 @@ const SENSOR_DOMAINS = ["sensor"];
|
|||||||
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
|
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
|
||||||
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
|
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
|
||||||
|
|
||||||
|
@customElement("dialog-area-registry-detail")
|
||||||
class DialogAreaDetail extends LitElement {
|
class DialogAreaDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -426,5 +427,3 @@ declare global {
|
|||||||
"dialog-area-registry-detail": DialogAreaDetail;
|
"dialog-area-registry-detail": DialogAreaDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-area-registry-detail", DialogAreaDetail);
|
|
||||||
|
|||||||
494
src/panels/config/areas/dialog-areas-floors-order.ts
Normal file
494
src/panels/config/areas/dialog-areas-floors-order.ts
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
import { mdiClose, mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import {
|
||||||
|
type AreasFloorHierarchy,
|
||||||
|
getAreasFloorHierarchy,
|
||||||
|
getAreasOrder,
|
||||||
|
getFloorOrder,
|
||||||
|
} from "../../../common/areas/areas-floor-hierarchy";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import "../../../components/ha-button";
|
||||||
|
import "../../../components/ha-dialog-header";
|
||||||
|
import "../../../components/ha-floor-icon";
|
||||||
|
import "../../../components/ha-icon";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-md-dialog";
|
||||||
|
import type { HaMdDialog } from "../../../components/ha-md-dialog";
|
||||||
|
import "../../../components/ha-md-list";
|
||||||
|
import "../../../components/ha-md-list-item";
|
||||||
|
import "../../../components/ha-sortable";
|
||||||
|
import "../../../components/ha-svg-icon";
|
||||||
|
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
|
import {
|
||||||
|
reorderAreaRegistryEntries,
|
||||||
|
updateAreaRegistryEntry,
|
||||||
|
} from "../../../data/area_registry";
|
||||||
|
import { reorderFloorRegistryEntries } from "../../../data/floor_registry";
|
||||||
|
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
import { showToast } from "../../../util/toast";
|
||||||
|
import type { AreasFloorsOrderDialogParams } from "./show-dialog-areas-floors-order";
|
||||||
|
|
||||||
|
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||||
|
|
||||||
|
interface FloorChange {
|
||||||
|
areaId: string;
|
||||||
|
floorId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("dialog-areas-floors-order")
|
||||||
|
class DialogAreasFloorsOrder extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _open = false;
|
||||||
|
|
||||||
|
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||||
|
|
||||||
|
@state() private _saving = false;
|
||||||
|
|
||||||
|
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
_params: AreasFloorsOrderDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._open = true;
|
||||||
|
this._computeHierarchy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeHierarchy(): void {
|
||||||
|
this._hierarchy = getAreasFloorHierarchy(
|
||||||
|
Object.values(this.hass.floors),
|
||||||
|
Object.values(this.hass.areas)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._dialog?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dialogClosed(): void {
|
||||||
|
this._open = false;
|
||||||
|
this._hierarchy = undefined;
|
||||||
|
this._saving = false;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._open || !this._hierarchy) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogTitle = this.hass.localize(
|
||||||
|
"ui.panel.config.areas.dialog.reorder_title"
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||||
|
<ha-dialog-header slot="headline">
|
||||||
|
<ha-icon-button
|
||||||
|
slot="navigationIcon"
|
||||||
|
.label=${this.hass.localize("ui.common.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
></ha-icon-button>
|
||||||
|
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
||||||
|
</ha-dialog-header>
|
||||||
|
<div slot="content" class="content">
|
||||||
|
<ha-sortable
|
||||||
|
handle-selector=".floor-handle"
|
||||||
|
draggable-selector=".floor"
|
||||||
|
@item-moved=${this._floorMoved}
|
||||||
|
invert-swap
|
||||||
|
>
|
||||||
|
<div class="floors">
|
||||||
|
${repeat(
|
||||||
|
this._hierarchy.floors,
|
||||||
|
(floor) => floor.id,
|
||||||
|
(floor) => this._renderFloor(floor)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-sortable>
|
||||||
|
${this._renderUnassignedAreas()}
|
||||||
|
</div>
|
||||||
|
<div slot="actions">
|
||||||
|
<ha-button @click=${this.closeDialog} appearance="plain">
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</ha-button>
|
||||||
|
<ha-button @click=${this._save} .disabled=${this._saving}>
|
||||||
|
${this.hass.localize("ui.common.save")}
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</ha-md-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderFloor(floor: { id: string; areas: string[] }) {
|
||||||
|
const floorEntry = this.hass.floors[floor.id];
|
||||||
|
if (!floorEntry) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="floor">
|
||||||
|
<div class="floor-header">
|
||||||
|
<ha-floor-icon .floor=${floorEntry}></ha-floor-icon>
|
||||||
|
<span class="floor-name">${floorEntry.name}</span>
|
||||||
|
<ha-svg-icon
|
||||||
|
class="floor-handle"
|
||||||
|
.path=${mdiDragHorizontalVariant}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
<ha-sortable
|
||||||
|
handle-selector=".area-handle"
|
||||||
|
draggable-selector="ha-md-list-item"
|
||||||
|
@item-moved=${this._areaMoved}
|
||||||
|
@item-added=${this._areaAdded}
|
||||||
|
group="areas"
|
||||||
|
.floor=${floor.id}
|
||||||
|
>
|
||||||
|
<ha-md-list>
|
||||||
|
${floor.areas.length > 0
|
||||||
|
? floor.areas.map((areaId) => this._renderArea(areaId))
|
||||||
|
: html`<p class="empty">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.dialog.empty_floor"
|
||||||
|
)}
|
||||||
|
</p>`}
|
||||||
|
</ha-md-list>
|
||||||
|
</ha-sortable>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderUnassignedAreas() {
|
||||||
|
const hasFloors = this._hierarchy!.floors.length > 0;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="floor unassigned">
|
||||||
|
${hasFloors
|
||||||
|
? html`<div class="floor-header">
|
||||||
|
<span class="floor-name">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.dialog.unassigned_areas"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
<ha-sortable
|
||||||
|
handle-selector=".area-handle"
|
||||||
|
draggable-selector="ha-md-list-item"
|
||||||
|
@item-moved=${this._areaMoved}
|
||||||
|
@item-added=${this._areaAdded}
|
||||||
|
group="areas"
|
||||||
|
.floor=${UNASSIGNED_FLOOR}
|
||||||
|
>
|
||||||
|
<ha-md-list>
|
||||||
|
${this._hierarchy!.areas.length > 0
|
||||||
|
? this._hierarchy!.areas.map((areaId) => this._renderArea(areaId))
|
||||||
|
: html`<p class="empty">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.areas.dialog.empty_unassigned"
|
||||||
|
)}
|
||||||
|
</p>`}
|
||||||
|
</ha-md-list>
|
||||||
|
</ha-sortable>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderArea(areaId: string) {
|
||||||
|
const area = this.hass.areas[areaId];
|
||||||
|
if (!area) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-md-list-item .sortableData=${area}>
|
||||||
|
${area.icon
|
||||||
|
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
|
||||||
|
: html`<ha-svg-icon
|
||||||
|
slot="start"
|
||||||
|
.path=${mdiTextureBox}
|
||||||
|
></ha-svg-icon>`}
|
||||||
|
<span slot="headline">${area.name}</span>
|
||||||
|
<ha-svg-icon
|
||||||
|
class="area-handle"
|
||||||
|
slot="end"
|
||||||
|
.path=${mdiDragHorizontalVariant}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-md-list-item>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _floorMoved(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._hierarchy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
const newFloors = [...this._hierarchy.floors];
|
||||||
|
const [movedFloor] = newFloors.splice(oldIndex, 1);
|
||||||
|
newFloors.splice(newIndex, 0, movedFloor);
|
||||||
|
|
||||||
|
this._hierarchy = {
|
||||||
|
...this._hierarchy,
|
||||||
|
floors: newFloors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _areaMoved(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._hierarchy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { floor } = ev.currentTarget as HTMLElement & { floor: string };
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
const floorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
||||||
|
|
||||||
|
if (floorId === null) {
|
||||||
|
// Reorder unassigned areas
|
||||||
|
const newAreas = [...this._hierarchy.areas];
|
||||||
|
const [movedArea] = newAreas.splice(oldIndex, 1);
|
||||||
|
newAreas.splice(newIndex, 0, movedArea);
|
||||||
|
|
||||||
|
this._hierarchy = {
|
||||||
|
...this._hierarchy,
|
||||||
|
areas: newAreas,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Reorder areas within a floor
|
||||||
|
this._hierarchy = {
|
||||||
|
...this._hierarchy,
|
||||||
|
floors: this._hierarchy.floors.map((f) => {
|
||||||
|
if (f.id === floorId) {
|
||||||
|
const newAreas = [...f.areas];
|
||||||
|
const [movedArea] = newAreas.splice(oldIndex, 1);
|
||||||
|
newAreas.splice(newIndex, 0, movedArea);
|
||||||
|
return { ...f, areas: newAreas };
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _areaAdded(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._hierarchy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { floor } = ev.currentTarget as HTMLElement & { floor: string };
|
||||||
|
const { data: area, index } = ev.detail as {
|
||||||
|
data: AreaRegistryEntry;
|
||||||
|
index: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const newFloorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
||||||
|
|
||||||
|
// Update hierarchy
|
||||||
|
const newUnassignedAreas = this._hierarchy.areas.filter(
|
||||||
|
(id) => id !== area.area_id
|
||||||
|
);
|
||||||
|
if (newFloorId === null) {
|
||||||
|
// Add to unassigned at the specified index
|
||||||
|
newUnassignedAreas.splice(index, 0, area.area_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._hierarchy = {
|
||||||
|
...this._hierarchy,
|
||||||
|
floors: this._hierarchy.floors.map((f) => {
|
||||||
|
if (f.id === newFloorId) {
|
||||||
|
// Add to new floor at the specified index
|
||||||
|
const newAreas = [...f.areas];
|
||||||
|
newAreas.splice(index, 0, area.area_id);
|
||||||
|
return { ...f, areas: newAreas };
|
||||||
|
}
|
||||||
|
// Remove from old floor
|
||||||
|
return {
|
||||||
|
...f,
|
||||||
|
areas: f.areas.filter((id) => id !== area.area_id),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
areas: newUnassignedAreas,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeFloorChanges(): FloorChange[] {
|
||||||
|
if (!this._hierarchy) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const changes: FloorChange[] = [];
|
||||||
|
|
||||||
|
// Check areas assigned to floors
|
||||||
|
for (const floor of this._hierarchy.floors) {
|
||||||
|
for (const areaId of floor.areas) {
|
||||||
|
const originalFloorId = this.hass.areas[areaId]?.floor_id ?? null;
|
||||||
|
if (floor.id !== originalFloorId) {
|
||||||
|
changes.push({ areaId, floorId: floor.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check unassigned areas
|
||||||
|
for (const areaId of this._hierarchy.areas) {
|
||||||
|
const originalFloorId = this.hass.areas[areaId]?.floor_id ?? null;
|
||||||
|
if (originalFloorId !== null) {
|
||||||
|
changes.push({ areaId, floorId: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save(): Promise<void> {
|
||||||
|
if (!this._hierarchy || this._saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._saving = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const areaOrder = getAreasOrder(this._hierarchy);
|
||||||
|
const floorOrder = getFloorOrder(this._hierarchy);
|
||||||
|
|
||||||
|
// Update floor assignments for areas that changed floors
|
||||||
|
const floorChanges = this._computeFloorChanges();
|
||||||
|
const floorChangePromises = floorChanges.map(({ areaId, floorId }) =>
|
||||||
|
updateAreaRegistryEntry(this.hass, areaId, {
|
||||||
|
floor_id: floorId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(floorChangePromises);
|
||||||
|
|
||||||
|
// Reorder areas and floors
|
||||||
|
await reorderAreaRegistryEntries(this.hass, areaOrder);
|
||||||
|
await reorderFloorRegistryEntries(this.hass, floorOrder);
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
} catch (err: any) {
|
||||||
|
showToast(this, {
|
||||||
|
message:
|
||||||
|
err.message ||
|
||||||
|
this.hass.localize("ui.panel.config.areas.dialog.reorder_failed"),
|
||||||
|
});
|
||||||
|
this._saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-md-dialog {
|
||||||
|
min-width: 600px;
|
||||||
|
max-height: 90%;
|
||||||
|
--dialog-content-padding: 8px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 600px), all and (max-height: 500px) {
|
||||||
|
ha-md-dialog {
|
||||||
|
--md-dialog-container-shape: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floors {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: var(
|
||||||
|
--ha-card-border-radius,
|
||||||
|
var(--ha-border-radius-lg)
|
||||||
|
);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor.unassigned {
|
||||||
|
border-style: dashed;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-name {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: var(--ha-font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-handle {
|
||||||
|
cursor: grab;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-md-list {
|
||||||
|
padding: 0;
|
||||||
|
--md-list-item-leading-space: 16px;
|
||||||
|
--md-list-item-trailing-space: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-md-list-item {
|
||||||
|
--md-list-item-one-line-container-height: 48px;
|
||||||
|
--md-list-item-container-shape: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-md-list-item.sortable-ghost {
|
||||||
|
border-radius: calc(
|
||||||
|
var(--ha-card-border-radius, var(--ha-border-radius-lg)) - 1px
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 2px var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.area-handle {
|
||||||
|
cursor: grab;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-style: italic;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-md-list:has(ha-md-list-item) .empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-areas-floors-order": DialogAreasFloorsOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mdiTextureBox } from "@mdi/js";
|
import { mdiTextureBox } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
@@ -27,6 +27,7 @@ import type { HomeAssistant } from "../../../types";
|
|||||||
import { showAreaRegistryDetailDialog } from "./show-dialog-area-registry-detail";
|
import { showAreaRegistryDetailDialog } from "./show-dialog-area-registry-detail";
|
||||||
import type { FloorRegistryDetailDialogParams } from "./show-dialog-floor-registry-detail";
|
import type { FloorRegistryDetailDialogParams } from "./show-dialog-floor-registry-detail";
|
||||||
|
|
||||||
|
@customElement("dialog-floor-registry-detail")
|
||||||
class DialogFloorDetail extends LitElement {
|
class DialogFloorDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -144,6 +145,10 @@ class DialogFloorDetail extends LitElement {
|
|||||||
"ui.panel.config.floors.editor.level"
|
"ui.panel.config.floors.editor.level"
|
||||||
)}
|
)}
|
||||||
type="number"
|
type="number"
|
||||||
|
.helper=${this.hass.localize(
|
||||||
|
"ui.panel.config.floors.editor.level_helper"
|
||||||
|
)}
|
||||||
|
helperPersistent
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
|
|
||||||
<ha-icon-picker
|
<ha-icon-picker
|
||||||
@@ -357,5 +362,3 @@ declare global {
|
|||||||
"dialog-floor-registry-detail": DialogFloorDetail;
|
"dialog-floor-registry-detail": DialogFloorDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-floor-registry-detail", DialogFloorDetail);
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { ActionDetail } from "@material/mwc-list";
|
|||||||
import {
|
import {
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiDragHorizontalVariant,
|
|
||||||
mdiHelpCircle,
|
mdiHelpCircle,
|
||||||
mdiPencil,
|
mdiPencil,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
|
mdiSort,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -21,7 +21,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import {
|
import {
|
||||||
getAreasFloorHierarchy,
|
getAreasFloorHierarchy,
|
||||||
getAreasOrder,
|
getAreasOrder,
|
||||||
getFloorOrder,
|
|
||||||
type AreasFloorHierarchy,
|
type AreasFloorHierarchy,
|
||||||
} from "../../../common/areas/areas-floor-hierarchy";
|
} from "../../../common/areas/areas-floor-hierarchy";
|
||||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||||
@@ -42,7 +41,6 @@ import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
|||||||
import {
|
import {
|
||||||
createFloorRegistryEntry,
|
createFloorRegistryEntry,
|
||||||
deleteFloorRegistryEntry,
|
deleteFloorRegistryEntry,
|
||||||
reorderFloorRegistryEntries,
|
|
||||||
updateFloorRegistryEntry,
|
updateFloorRegistryEntry,
|
||||||
} from "../../../data/floor_registry";
|
} from "../../../data/floor_registry";
|
||||||
import {
|
import {
|
||||||
@@ -58,6 +56,7 @@ import {
|
|||||||
loadAreaRegistryDetailDialog,
|
loadAreaRegistryDetailDialog,
|
||||||
showAreaRegistryDetailDialog,
|
showAreaRegistryDetailDialog,
|
||||||
} from "./show-dialog-area-registry-detail";
|
} from "./show-dialog-area-registry-detail";
|
||||||
|
import { showAreasFloorsOrderDialog } from "./show-dialog-areas-floors-order";
|
||||||
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
||||||
|
|
||||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||||
@@ -84,6 +83,8 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
|
private _searchParms = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||||
|
|
||||||
private _blockHierarchyUpdate = false;
|
private _blockHierarchyUpdate = false;
|
||||||
@@ -167,99 +168,97 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.isWide=${this.isWide}
|
.isWide=${this.isWide}
|
||||||
back-path="/config"
|
.backPath=${this._searchParms.has("historyBack")
|
||||||
|
? undefined
|
||||||
|
: "/config"}
|
||||||
.tabs=${configSections.areas}
|
.tabs=${configSections.areas}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
has-fab
|
has-fab
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<ha-button-menu slot="toolbar-icon">
|
||||||
slot="toolbar-icon"
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.common.help")}
|
slot="trigger"
|
||||||
.path=${mdiHelpCircle}
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
@click=${this._showHelp}
|
.path=${mdiDotsVertical}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
<ha-list-item graphic="icon" @click=${this._showReorderDialog}>
|
||||||
|
<ha-svg-icon .path=${mdiSort} slot="graphic"></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.panel.config.areas.picker.reorder")}
|
||||||
|
</ha-list-item>
|
||||||
|
<ha-list-item graphic="icon" @click=${this._showHelp}>
|
||||||
|
<ha-svg-icon .path=${mdiHelpCircle} slot="graphic"></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.common.help")}
|
||||||
|
</ha-list-item>
|
||||||
|
</ha-button-menu>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<ha-sortable
|
<div class="floors">
|
||||||
handle-selector=".handle"
|
${this._hierarchy.floors.map(({ areas, id }) => {
|
||||||
draggable-selector=".floor"
|
const floor = this.hass.floors[id];
|
||||||
@item-moved=${this._floorMoved}
|
if (!floor) {
|
||||||
.options=${SORT_OPTIONS}
|
return nothing;
|
||||||
group="floors"
|
}
|
||||||
invert-swap
|
return html`
|
||||||
>
|
<div class="floor">
|
||||||
<div class="floors">
|
<div class="header">
|
||||||
${this._hierarchy.floors.map(({ areas, id }) => {
|
<h2>
|
||||||
const floor = this.hass.floors[id];
|
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
||||||
if (!floor) {
|
${floor.name}
|
||||||
return nothing;
|
</h2>
|
||||||
}
|
<div class="actions">
|
||||||
return html`
|
<ha-button-menu
|
||||||
<div class="floor">
|
.floor=${floor}
|
||||||
<div class="header">
|
@action=${this._handleFloorAction}
|
||||||
<h2>
|
>
|
||||||
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
<ha-icon-button
|
||||||
${floor.name}
|
slot="trigger"
|
||||||
</h2>
|
.path=${mdiDotsVertical}
|
||||||
<div class="actions">
|
></ha-icon-button>
|
||||||
<ha-svg-icon
|
<ha-list-item graphic="icon"
|
||||||
class="handle"
|
><ha-svg-icon
|
||||||
.path=${mdiDragHorizontalVariant}
|
.path=${mdiPencil}
|
||||||
></ha-svg-icon>
|
slot="graphic"
|
||||||
<ha-button-menu
|
></ha-svg-icon
|
||||||
.floor=${floor}
|
>${this.hass.localize(
|
||||||
@action=${this._handleFloorAction}
|
"ui.panel.config.areas.picker.floor.edit_floor"
|
||||||
|
)}</ha-list-item
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<ha-list-item class="warning" graphic="icon"
|
||||||
slot="trigger"
|
><ha-svg-icon
|
||||||
.path=${mdiDotsVertical}
|
class="warning"
|
||||||
></ha-icon-button>
|
.path=${mdiDelete}
|
||||||
<ha-list-item graphic="icon"
|
slot="graphic"
|
||||||
><ha-svg-icon
|
></ha-svg-icon
|
||||||
.path=${mdiPencil}
|
>${this.hass.localize(
|
||||||
slot="graphic"
|
"ui.panel.config.areas.picker.floor.delete_floor"
|
||||||
></ha-svg-icon
|
)}</ha-list-item
|
||||||
>${this.hass.localize(
|
>
|
||||||
"ui.panel.config.areas.picker.floor.edit_floor"
|
</ha-button-menu>
|
||||||
)}</ha-list-item
|
|
||||||
>
|
|
||||||
<ha-list-item class="warning" graphic="icon"
|
|
||||||
><ha-svg-icon
|
|
||||||
class="warning"
|
|
||||||
.path=${mdiDelete}
|
|
||||||
slot="graphic"
|
|
||||||
></ha-svg-icon
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.areas.picker.floor.delete_floor"
|
|
||||||
)}</ha-list-item
|
|
||||||
>
|
|
||||||
</ha-button-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ha-sortable
|
|
||||||
handle-selector="a"
|
|
||||||
draggable-selector="a"
|
|
||||||
@item-added=${this._areaAdded}
|
|
||||||
@item-moved=${this._areaMoved}
|
|
||||||
group="areas"
|
|
||||||
.options=${SORT_OPTIONS}
|
|
||||||
.floor=${floor.floor_id}
|
|
||||||
>
|
|
||||||
<div class="areas">
|
|
||||||
${areas.map((areaId) => {
|
|
||||||
const area = this.hass.areas[areaId];
|
|
||||||
if (!area) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
const stats = areasStats.get(area.area_id);
|
|
||||||
return this._renderArea(area, stats);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</ha-sortable>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
<ha-sortable
|
||||||
})}
|
handle-selector="a"
|
||||||
</div>
|
draggable-selector="a"
|
||||||
</ha-sortable>
|
@item-added=${this._areaAdded}
|
||||||
|
@item-moved=${this._areaMoved}
|
||||||
|
group="areas"
|
||||||
|
.options=${SORT_OPTIONS}
|
||||||
|
.floor=${floor.floor_id}
|
||||||
|
>
|
||||||
|
<div class="areas">
|
||||||
|
${areas.map((areaId) => {
|
||||||
|
const area = this.hass.areas[areaId];
|
||||||
|
if (!area) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const stats = areasStats.get(area.area_id);
|
||||||
|
return this._renderArea(area, stats);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ha-sortable>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
${this._hierarchy.areas.length
|
${this._hierarchy.areas.length
|
||||||
? html`
|
? html`
|
||||||
@@ -391,51 +390,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _floorMoved(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (!this.hass || !this._hierarchy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { oldIndex, newIndex } = ev.detail;
|
|
||||||
|
|
||||||
const reorderFloors = (
|
|
||||||
floors: AreasFloorHierarchy["floors"],
|
|
||||||
oldIdx: number,
|
|
||||||
newIdx: number
|
|
||||||
) => {
|
|
||||||
const newFloors = [...floors];
|
|
||||||
const [movedFloor] = newFloors.splice(oldIdx, 1);
|
|
||||||
newFloors.splice(newIdx, 0, movedFloor);
|
|
||||||
return newFloors;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Optimistically update UI
|
|
||||||
this._hierarchy = {
|
|
||||||
...this._hierarchy,
|
|
||||||
floors: reorderFloors(this._hierarchy.floors, oldIndex, newIndex),
|
|
||||||
};
|
|
||||||
|
|
||||||
const areaOrder = getAreasOrder(this._hierarchy);
|
|
||||||
const floorOrder = getFloorOrder(this._hierarchy);
|
|
||||||
|
|
||||||
// Block hierarchy updates for 500ms to avoid flickering
|
|
||||||
// because of multiple async updates
|
|
||||||
this._blockHierarchyUpdateFor(500);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await reorderAreaRegistryEntries(this.hass, areaOrder);
|
|
||||||
await reorderFloorRegistryEntries(this.hass, floorOrder);
|
|
||||||
} catch {
|
|
||||||
showToast(this, {
|
|
||||||
message: this.hass.localize(
|
|
||||||
"ui.panel.config.areas.picker.floor_reorder_failed"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
// Revert on error
|
|
||||||
this._computeHierarchy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _areaMoved(ev) {
|
private async _areaMoved(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (!this.hass || !this._hierarchy) {
|
if (!this.hass || !this._hierarchy) {
|
||||||
@@ -598,6 +552,10 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
this._openAreaDialog();
|
this._openAreaDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _showReorderDialog() {
|
||||||
|
showAreasFloorsOrderDialog(this, {});
|
||||||
|
}
|
||||||
|
|
||||||
private _showHelp() {
|
private _showHelp() {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: this.hass.localize("ui.panel.config.areas.caption"),
|
title: this.hass.localize("ui.panel.config.areas.caption"),
|
||||||
|
|||||||
17
src/panels/config/areas/show-dialog-areas-floors-order.ts
Normal file
17
src/panels/config/areas/show-dialog-areas-floors-order.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface AreasFloorsOrderDialogParams {}
|
||||||
|
|
||||||
|
export const loadAreasFloorsOrderDialog = () =>
|
||||||
|
import("./dialog-areas-floors-order");
|
||||||
|
|
||||||
|
export const showAreasFloorsOrderDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
params: AreasFloorsOrderDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-areas-floors-order",
|
||||||
|
dialogImport: loadAreasFloorsOrderDialog,
|
||||||
|
dialogParams: params,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ import { classMap } from "lit/directives/class-map";
|
|||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { mainWindow } from "../../../common/dom/get_main_window";
|
||||||
import { computeAreaName } from "../../../common/entity/compute_area_name";
|
import { computeAreaName } from "../../../common/entity/compute_area_name";
|
||||||
import { computeDeviceName } from "../../../common/entity/compute_device_name";
|
import { computeDeviceName } from "../../../common/entity/compute_device_name";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
@@ -114,12 +115,10 @@ import {
|
|||||||
} from "../../../data/trigger";
|
} from "../../../data/trigger";
|
||||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { isMac } from "../../../util/is_mac";
|
import { isMac } from "../../../util/is_mac";
|
||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import "./add-automation-element/ha-automation-add-from-target";
|
import "./add-automation-element/ha-automation-add-from-target";
|
||||||
import type HaAutomationAddFromTarget from "./add-automation-element/ha-automation-add-from-target";
|
|
||||||
import "./add-automation-element/ha-automation-add-items";
|
import "./add-automation-element/ha-automation-add-items";
|
||||||
import "./add-automation-element/ha-automation-add-search";
|
import "./add-automation-element/ha-automation-add-search";
|
||||||
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
|
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
|
||||||
@@ -168,7 +167,7 @@ const DYNAMIC_KEYWORDS = ["dynamicGroups", "helpers", "other"];
|
|||||||
|
|
||||||
@customElement("add-automation-element-dialog")
|
@customElement("add-automation-element-dialog")
|
||||||
class DialogAddAutomationElement
|
class DialogAddAutomationElement
|
||||||
extends KeyboardShortcutMixin(SubscribeMixin(LitElement))
|
extends KeyboardShortcutMixin(LitElement)
|
||||||
implements HassDialog
|
implements HassDialog
|
||||||
{
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -217,10 +216,6 @@ class DialogAddAutomationElement
|
|||||||
// #endregion state
|
// #endregion state
|
||||||
|
|
||||||
// #region queries
|
// #region queries
|
||||||
|
|
||||||
@query("ha-automation-add-from-target")
|
|
||||||
private _targetPickerElement?: HaAutomationAddFromTarget;
|
|
||||||
|
|
||||||
@query("ha-automation-add-items")
|
@query("ha-automation-add-items")
|
||||||
private _itemsListElement?: HTMLDivElement;
|
private _itemsListElement?: HTMLDivElement;
|
||||||
|
|
||||||
@@ -233,6 +228,8 @@ class DialogAddAutomationElement
|
|||||||
|
|
||||||
private _unsub?: Promise<UnsubscribeFunc>;
|
private _unsub?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
|
private _unsubscribeLabFeatures?: UnsubscribeFunc;
|
||||||
|
|
||||||
private _configEntryLookup: Record<string, ConfigEntry> = {};
|
private _configEntryLookup: Record<string, ConfigEntry> = {};
|
||||||
|
|
||||||
// #endregion variables
|
// #endregion variables
|
||||||
@@ -246,33 +243,36 @@ class DialogAddAutomationElement
|
|||||||
) {
|
) {
|
||||||
this._calculateUsedDomains();
|
this._calculateUsedDomains();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("_newTriggersAndConditions")) {
|
||||||
|
this._subscribeDescriptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public hassSubscribe() {
|
private _subscribeDescriptions() {
|
||||||
return [
|
this._unsubscribe();
|
||||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
if (this._params?.type === "trigger") {
|
||||||
this._newTriggersAndConditions =
|
this._triggerDescriptions = {};
|
||||||
features.find(
|
this._unsub = subscribeTriggers(this.hass, (triggers) => {
|
||||||
(feature) =>
|
this._triggerDescriptions = {
|
||||||
feature.domain === "automation" &&
|
...this._triggerDescriptions,
|
||||||
feature.preview_feature === "new_triggers_conditions"
|
...triggers,
|
||||||
)?.enabled ?? false;
|
};
|
||||||
this._tab =
|
});
|
||||||
this._newTriggersAndConditions && this._params?.type !== "condition"
|
} else if (this._params?.type === "condition") {
|
||||||
? "targets"
|
this._conditionDescriptions = {};
|
||||||
: "groups";
|
this._unsub = subscribeConditions(this.hass, (conditions) => {
|
||||||
}),
|
this._conditionDescriptions = {
|
||||||
];
|
...this._conditionDescriptions,
|
||||||
|
...conditions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public showDialog(params): void {
|
public showDialog(params): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
|
|
||||||
this._tab =
|
|
||||||
this._newTriggersAndConditions && this._params?.type !== "condition"
|
|
||||||
? "targets"
|
|
||||||
: "groups";
|
|
||||||
|
|
||||||
this.addKeyboardShortcuts();
|
this.addKeyboardShortcuts();
|
||||||
|
|
||||||
this._loadConfigEntries();
|
this._loadConfigEntries();
|
||||||
@@ -281,27 +281,38 @@ class DialogAddAutomationElement
|
|||||||
this._fetchManifests();
|
this._fetchManifests();
|
||||||
this._calculateUsedDomains();
|
this._calculateUsedDomains();
|
||||||
|
|
||||||
|
this._unsubscribeLabFeatures = subscribeLabFeatures(
|
||||||
|
this.hass.connection,
|
||||||
|
(features) => {
|
||||||
|
this._newTriggersAndConditions =
|
||||||
|
features.find(
|
||||||
|
(feature) =>
|
||||||
|
feature.domain === "automation" &&
|
||||||
|
feature.preview_feature === "new_triggers_conditions"
|
||||||
|
)?.enabled ?? false;
|
||||||
|
this._tab = this._newTriggersAndConditions ? "targets" : "groups";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// add initial dialog view state to history
|
||||||
|
mainWindow.history.pushState(
|
||||||
|
{
|
||||||
|
dialogData: {},
|
||||||
|
},
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
if (this._params?.type === "action") {
|
if (this._params?.type === "action") {
|
||||||
this.hass.loadBackendTranslation("services");
|
this.hass.loadBackendTranslation("services");
|
||||||
getServiceIcons(this.hass);
|
getServiceIcons(this.hass);
|
||||||
} else if (this._params?.type === "trigger") {
|
} else if (this._params?.type === "trigger") {
|
||||||
this.hass.loadBackendTranslation("triggers");
|
this.hass.loadBackendTranslation("triggers");
|
||||||
getTriggerIcons(this.hass);
|
getTriggerIcons(this.hass);
|
||||||
this._unsub = subscribeTriggers(this.hass, (triggers) => {
|
this._subscribeDescriptions();
|
||||||
this._triggerDescriptions = {
|
|
||||||
...this._triggerDescriptions,
|
|
||||||
...triggers,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else if (this._params?.type === "condition") {
|
} else if (this._params?.type === "condition") {
|
||||||
this.hass.loadBackendTranslation("conditions");
|
this.hass.loadBackendTranslation("conditions");
|
||||||
getConditionIcons(this.hass);
|
getConditionIcons(this.hass);
|
||||||
this._unsub = subscribeConditions(this.hass, (conditions) => {
|
this._subscribeDescriptions();
|
||||||
this._conditionDescriptions = {
|
|
||||||
...this._conditionDescriptions,
|
|
||||||
...conditions,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", this._updateNarrow);
|
window.addEventListener("resize", this._updateNarrow);
|
||||||
@@ -311,7 +322,41 @@ class DialogAddAutomationElement
|
|||||||
this._bottomSheetMode = this._narrow;
|
this._bottomSheetMode = this._narrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog() {
|
public closeDialog(historyState?: any) {
|
||||||
|
// prevent closing when come from popstate event and root level isn't active
|
||||||
|
if (
|
||||||
|
this._open &&
|
||||||
|
historyState &&
|
||||||
|
(this._selectedTarget || this._selectedGroup)
|
||||||
|
) {
|
||||||
|
if (historyState.dialogData?.target) {
|
||||||
|
this._selectedTarget = historyState.dialogData.target;
|
||||||
|
this._getItemsByTarget();
|
||||||
|
this._tab = "targets";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (historyState.dialogData?.group) {
|
||||||
|
this._selectedCollectionIndex = historyState.dialogData.collectionIndex;
|
||||||
|
this._selectedGroup = historyState.dialogData.group;
|
||||||
|
this._tab = "groups";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return to home on mobile
|
||||||
|
if (this._narrow) {
|
||||||
|
this._selectedTarget = undefined;
|
||||||
|
this._selectedGroup = undefined;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if dialog is closed, but root level isn't active, clean up history state
|
||||||
|
if (mainWindow.history.state?.dialogData) {
|
||||||
|
this._open = false;
|
||||||
|
mainWindow.history.back();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.removeKeyboardShortcuts();
|
this.removeKeyboardShortcuts();
|
||||||
this._unsubscribe();
|
this._unsubscribe();
|
||||||
if (this._params) {
|
if (this._params) {
|
||||||
@@ -379,6 +424,10 @@ class DialogAddAutomationElement
|
|||||||
this._unsub.then((unsub) => unsub());
|
this._unsub.then((unsub) => unsub());
|
||||||
this._unsub = undefined;
|
this._unsub = undefined;
|
||||||
}
|
}
|
||||||
|
if (this._unsubscribeLabFeatures) {
|
||||||
|
this._unsubscribeLabFeatures();
|
||||||
|
this._unsubscribeLabFeatures = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion lifecycle
|
// #endregion lifecycle
|
||||||
@@ -394,7 +443,7 @@ class DialogAddAutomationElement
|
|||||||
return html`
|
return html`
|
||||||
<ha-bottom-sheet
|
<ha-bottom-sheet
|
||||||
.open=${this._open}
|
.open=${this._open}
|
||||||
@closed=${this.closeDialog}
|
@closed=${this._handleClosed}
|
||||||
flexcontent
|
flexcontent
|
||||||
>
|
>
|
||||||
${this._renderContent()}
|
${this._renderContent()}
|
||||||
@@ -406,7 +455,7 @@ class DialogAddAutomationElement
|
|||||||
<ha-wa-dialog
|
<ha-wa-dialog
|
||||||
width="large"
|
width="large"
|
||||||
.open=${this._open}
|
.open=${this._open}
|
||||||
@closed=${this.closeDialog}
|
@closed=${this._handleClosed}
|
||||||
flexcontent
|
flexcontent
|
||||||
>
|
>
|
||||||
${this._renderContent()}
|
${this._renderContent()}
|
||||||
@@ -426,10 +475,7 @@ class DialogAddAutomationElement
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (this._newTriggersAndConditions) {
|
||||||
this._newTriggersAndConditions &&
|
|
||||||
automationElementType !== "condition"
|
|
||||||
) {
|
|
||||||
tabButtons.unshift({
|
tabButtons.unshift({
|
||||||
label: this.hass.localize(`ui.panel.config.automation.editor.targets`),
|
label: this.hass.localize(`ui.panel.config.automation.editor.targets`),
|
||||||
value: "targets",
|
value: "targets",
|
||||||
@@ -519,8 +565,7 @@ class DialogAddAutomationElement
|
|||||||
this._manifests
|
this._manifests
|
||||||
)}
|
)}
|
||||||
.convertToItem=${this._convertToItem}
|
.convertToItem=${this._convertToItem}
|
||||||
.newTriggersAndConditions=${this._newTriggersAndConditions &&
|
.newTriggersAndConditions=${this._newTriggersAndConditions}
|
||||||
automationElementType !== "condition"}
|
|
||||||
@search-element-picked=${this._searchItemSelected}
|
@search-element-picked=${this._searchItemSelected}
|
||||||
>
|
>
|
||||||
</ha-automation-add-search>`
|
</ha-automation-add-search>`
|
||||||
@@ -548,8 +593,7 @@ class DialogAddAutomationElement
|
|||||||
interactive
|
interactive
|
||||||
type="button"
|
type="button"
|
||||||
class="paste"
|
class="paste"
|
||||||
.value=${PASTE_VALUE}
|
@click=${this._paste}
|
||||||
@click=${this._selected}
|
|
||||||
>
|
>
|
||||||
<div class="shortcut-label">
|
<div class="shortcut-label">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
@@ -599,7 +643,7 @@ class DialogAddAutomationElement
|
|||||||
: nothing}
|
: nothing}
|
||||||
${collections.map(
|
${collections.map(
|
||||||
(collection, index) => html`
|
(collection, index) => html`
|
||||||
${collection.titleKey
|
${collection.titleKey && collection.groups.length
|
||||||
? html`<ha-section-title>
|
? html`<ha-section-title>
|
||||||
${this.hass.localize(collection.titleKey)}
|
${this.hass.localize(collection.titleKey)}
|
||||||
</ha-section-title>`
|
</ha-section-title>`
|
||||||
@@ -720,15 +764,26 @@ class DialogAddAutomationElement
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (targetId) {
|
if (targetId) {
|
||||||
if (targetType === "area" && this.hass.areas[targetId]?.floor_id) {
|
if (targetType === "area") {
|
||||||
const floorId = this.hass.areas[targetId].floor_id;
|
const floorId = this.hass.areas[targetId]?.floor_id;
|
||||||
subtitle = computeFloorName(this.hass.floors[floorId]) || floorId;
|
if (floorId) {
|
||||||
}
|
subtitle = computeFloorName(this.hass.floors[floorId]) || floorId;
|
||||||
if (targetType === "device" && this.hass.devices[targetId]?.area_id) {
|
} else {
|
||||||
const areaId = this.hass.devices[targetId].area_id;
|
subtitle = this.hass.localize(
|
||||||
subtitle = computeAreaName(this.hass.areas[areaId]) || areaId;
|
"ui.panel.config.automation.editor.other_areas"
|
||||||
}
|
);
|
||||||
if (targetType === "entity" && this.hass.states[targetId]) {
|
}
|
||||||
|
} else if (targetType === "device") {
|
||||||
|
const areaId = this.hass.devices[targetId]?.area_id;
|
||||||
|
if (areaId) {
|
||||||
|
subtitle = computeAreaName(this.hass.areas[areaId]) || areaId;
|
||||||
|
} else {
|
||||||
|
const device = this.hass.devices[targetId];
|
||||||
|
subtitle = this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.${device?.entry_type === "service" ? "services" : "unassigned_devices"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (targetType === "entity" && this.hass.states[targetId]) {
|
||||||
const entity = this.hass.entities[targetId];
|
const entity = this.hass.entities[targetId];
|
||||||
if (entity && !entity.device_id && !entity.area_id) {
|
if (entity && !entity.device_id && !entity.area_id) {
|
||||||
const domain = targetId.split(".", 2)[0];
|
const domain = targetId.split(".", 2)[0];
|
||||||
@@ -753,10 +808,10 @@ class DialogAddAutomationElement
|
|||||||
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
|
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (subtitle) {
|
if (subtitle) {
|
||||||
return html`<span slot="subtitle">${subtitle}</span>`;
|
return html`<span slot="subtitle">${subtitle}</span>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1343,6 +1398,61 @@ class DialogAddAutomationElement
|
|||||||
this._labelRegistry?.find(({ label_id }) => label_id === labelId)
|
this._labelRegistry?.find(({ label_id }) => label_id === labelId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private _getDomainType(domain: string) {
|
||||||
|
return ENTITY_DOMAINS_MAIN.has(domain) ||
|
||||||
|
(this._manifests?.[domain].integration_type === "entity" &&
|
||||||
|
!ENTITY_DOMAINS_OTHER.has(domain))
|
||||||
|
? "dynamicGroups"
|
||||||
|
: this._manifests?.[domain].integration_type === "helper"
|
||||||
|
? "helpers"
|
||||||
|
: "other";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sortDomainsByCollection(
|
||||||
|
type: AddAutomationElementDialogParams["type"],
|
||||||
|
entries: [
|
||||||
|
string,
|
||||||
|
{ title: string; items: AddAutomationElementListItem[] },
|
||||||
|
][]
|
||||||
|
): { title: string; items: AddAutomationElementListItem[] }[] {
|
||||||
|
const order: string[] = [];
|
||||||
|
|
||||||
|
TYPES[type].collections.forEach((collection) => {
|
||||||
|
order.push(...Object.keys(collection.groups));
|
||||||
|
});
|
||||||
|
|
||||||
|
return entries
|
||||||
|
.sort((a, b) => {
|
||||||
|
const domainA = a[0];
|
||||||
|
const domainB = b[0];
|
||||||
|
|
||||||
|
if (order.includes(domainA) && order.includes(domainB)) {
|
||||||
|
return order.indexOf(domainA) - order.indexOf(domainB);
|
||||||
|
}
|
||||||
|
|
||||||
|
let typeA = domainA;
|
||||||
|
let typeB = domainB;
|
||||||
|
|
||||||
|
if (!order.includes(domainA)) {
|
||||||
|
typeA = this._getDomainType(domainA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!order.includes(domainB)) {
|
||||||
|
typeB = this._getDomainType(domainB);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeA === typeB) {
|
||||||
|
return stringCompare(
|
||||||
|
a[1].title,
|
||||||
|
b[1].title,
|
||||||
|
this.hass.locale.language
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return order.indexOf(typeA) - order.indexOf(typeB);
|
||||||
|
})
|
||||||
|
.map((entry) => entry[1]);
|
||||||
|
}
|
||||||
|
|
||||||
// #endregion data
|
// #endregion data
|
||||||
|
|
||||||
// #region data memoize
|
// #region data memoize
|
||||||
@@ -1358,12 +1468,12 @@ class DialogAddAutomationElement
|
|||||||
|
|
||||||
private _getAreaEntityLookupMemoized = memoizeOne(
|
private _getAreaEntityLookupMemoized = memoizeOne(
|
||||||
(entities: HomeAssistant["entities"]) =>
|
(entities: HomeAssistant["entities"]) =>
|
||||||
getAreaEntityLookup(Object.values(entities), true)
|
getAreaEntityLookup(Object.values(entities))
|
||||||
);
|
);
|
||||||
|
|
||||||
private _getDeviceEntityLookupMemoized = memoizeOne(
|
private _getDeviceEntityLookupMemoized = memoizeOne(
|
||||||
(entities: HomeAssistant["entities"]) =>
|
(entities: HomeAssistant["entities"]) =>
|
||||||
getDeviceEntityLookup(Object.values(entities), true)
|
getDeviceEntityLookup(Object.values(entities))
|
||||||
);
|
);
|
||||||
|
|
||||||
private _extractTypeAndIdFromTarget = memoizeOne(
|
private _extractTypeAndIdFromTarget = memoizeOne(
|
||||||
@@ -1428,8 +1538,9 @@ class DialogAddAutomationElement
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.values(items).sort((a, b) =>
|
return this._sortDomainsByCollection(
|
||||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
this._params!.type,
|
||||||
|
Object.entries(items)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1538,8 +1649,9 @@ class DialogAddAutomationElement
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.values(items).sort((a, b) =>
|
return this._sortDomainsByCollection(
|
||||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
this._params!.type,
|
||||||
|
Object.entries(items)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1570,8 +1682,9 @@ class DialogAddAutomationElement
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.values(items).sort((a, b) =>
|
return this._sortDomainsByCollection(
|
||||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
this._params!.type,
|
||||||
|
Object.entries(items)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1584,11 +1697,7 @@ class DialogAddAutomationElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _back() {
|
private _back() {
|
||||||
if (this._selectedTarget) {
|
mainWindow.history.back();
|
||||||
this._targetPickerElement?.navigateBack();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._selectedGroup = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _groupSelected(ev) {
|
private _groupSelected(ev) {
|
||||||
@@ -1600,11 +1709,26 @@ class DialogAddAutomationElement
|
|||||||
}
|
}
|
||||||
this._selectedGroup = group.value;
|
this._selectedGroup = group.value;
|
||||||
this._selectedCollectionIndex = ev.currentTarget.index;
|
this._selectedCollectionIndex = ev.currentTarget.index;
|
||||||
|
|
||||||
|
mainWindow.history.pushState(
|
||||||
|
{
|
||||||
|
dialogData: {
|
||||||
|
group: this._selectedGroup,
|
||||||
|
collectionIndex: this._selectedCollectionIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
""
|
||||||
|
);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this._itemsListElement?.scrollTo(0, 0);
|
this._itemsListElement?.scrollTo(0, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _paste() {
|
||||||
|
this._params!.add(PASTE_VALUE);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
private _selected(ev: CustomEvent<{ value: string }>) {
|
private _selected(ev: CustomEvent<{ value: string }>) {
|
||||||
let target: HassServiceTarget | undefined;
|
let target: HassServiceTarget | undefined;
|
||||||
if (
|
if (
|
||||||
@@ -1624,6 +1748,14 @@ class DialogAddAutomationElement
|
|||||||
this._targetItems = undefined;
|
this._targetItems = undefined;
|
||||||
this._loadItemsError = false;
|
this._loadItemsError = false;
|
||||||
this._selectedTarget = ev.detail.value;
|
this._selectedTarget = ev.detail.value;
|
||||||
|
mainWindow.history.pushState(
|
||||||
|
{
|
||||||
|
dialogData: {
|
||||||
|
target: this._selectedTarget,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (this._narrow) {
|
if (this._narrow) {
|
||||||
@@ -1668,14 +1800,19 @@ class DialogAddAutomationElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._params!.type === "action") {
|
if (this._params!.type === "action") {
|
||||||
const items = await getServicesForTarget(
|
const items: string[] = await getServicesForTarget(
|
||||||
this.hass.callWS,
|
this.hass.callWS,
|
||||||
this._selectedTarget
|
this._selectedTarget
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filteredItems = items.filter(
|
||||||
|
// homeassistant services are too generic to be applied on the selected target
|
||||||
|
(service) => !service.startsWith("homeassistant.")
|
||||||
|
);
|
||||||
|
|
||||||
this._targetItems = this._getDomainGroupedActionListItems(
|
this._targetItems = this._getDomainGroupedActionListItems(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
items
|
filteredItems
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1738,6 +1875,10 @@ class DialogAddAutomationElement
|
|||||||
this._tab = "targets";
|
this._tab = "targets";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleClosed() {
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
// #region interaction
|
// #region interaction
|
||||||
|
|
||||||
// #region render helpers
|
// #region render helpers
|
||||||
@@ -1903,7 +2044,7 @@ class DialogAddAutomationElement
|
|||||||
ha-wa-dialog {
|
ha-wa-dialog {
|
||||||
--dialog-content-padding: var(--ha-space-0);
|
--dialog-content-padding: var(--ha-space-0);
|
||||||
--ha-dialog-min-height: min(
|
--ha-dialog-min-height: min(
|
||||||
648px,
|
800px,
|
||||||
calc(
|
calc(
|
||||||
100vh - max(
|
100vh - max(
|
||||||
var(--safe-area-inset-bottom),
|
var(--safe-area-inset-bottom),
|
||||||
@@ -1912,7 +2053,7 @@ class DialogAddAutomationElement
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
--ha-dialog-min-height: min(
|
--ha-dialog-min-height: min(
|
||||||
648px,
|
800px,
|
||||||
calc(
|
calc(
|
||||||
100dvh - max(
|
100dvh - max(
|
||||||
var(--safe-area-inset-bottom),
|
var(--safe-area-inset-bottom),
|
||||||
|
|||||||
@@ -553,9 +553,6 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
|||||||
area.icon,
|
area.icon,
|
||||||
] as [string, string, string | undefined, string | undefined];
|
] as [string, string, string | undefined, string | undefined];
|
||||||
})
|
})
|
||||||
.sort(([, nameA], [, nameB]) =>
|
|
||||||
stringCompare(nameA, nameB, this.hass.locale.language)
|
|
||||||
)
|
|
||||||
.map(([areaTargetId, areaName, floorId, areaIcon]) => {
|
.map(([areaTargetId, areaName, floorId, areaIcon]) => {
|
||||||
const { open, devices, entities } =
|
const { open, devices, entities } =
|
||||||
this._entries[`floor${TARGET_SEPARATOR}${floorId || ""}`].areas![
|
this._entries[`floor${TARGET_SEPARATOR}${floorId || ""}`].areas![
|
||||||
@@ -708,7 +705,11 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
|||||||
this.floors
|
this.floors
|
||||||
);
|
);
|
||||||
|
|
||||||
const label = entityName || deviceName || entityId;
|
let label = entityName || deviceName || entityId;
|
||||||
|
|
||||||
|
if (this.entities[entityId]?.hidden) {
|
||||||
|
label += ` (${this.localize("ui.panel.config.automation.editor.entity_hidden")})`;
|
||||||
|
}
|
||||||
|
|
||||||
return [entityId, label, stateObj] as [string, string, HassEntity];
|
return [entityId, label, stateObj] as [string, string, HassEntity];
|
||||||
})
|
})
|
||||||
@@ -837,12 +838,12 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
|||||||
|
|
||||||
private _getAreaEntityLookupMemoized = memoizeOne(
|
private _getAreaEntityLookupMemoized = memoizeOne(
|
||||||
(entities: HomeAssistant["entities"]) =>
|
(entities: HomeAssistant["entities"]) =>
|
||||||
getAreaEntityLookup(Object.values(entities), true)
|
getAreaEntityLookup(Object.values(entities))
|
||||||
);
|
);
|
||||||
|
|
||||||
private _getDeviceEntityLookupMemoized = memoizeOne(
|
private _getDeviceEntityLookupMemoized = memoizeOne(
|
||||||
(entities: HomeAssistant["entities"]) =>
|
(entities: HomeAssistant["entities"]) =>
|
||||||
getDeviceEntityLookup(Object.values(entities), true)
|
getDeviceEntityLookup(Object.values(entities))
|
||||||
);
|
);
|
||||||
|
|
||||||
private _getSelectedTargetId = memoizeOne(
|
private _getSelectedTargetId = memoizeOne(
|
||||||
@@ -1382,92 +1383,6 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public navigateBack() {
|
|
||||||
if (!this.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const valueType = Object.keys(this.value)[0].replace("_id", "");
|
|
||||||
const valueId = this.value[`${valueType}_id`];
|
|
||||||
|
|
||||||
if (
|
|
||||||
valueType === "floor" ||
|
|
||||||
valueType === "label" ||
|
|
||||||
(!valueId &&
|
|
||||||
(valueType === "device" ||
|
|
||||||
valueType === "helper" ||
|
|
||||||
valueType === "service" ||
|
|
||||||
valueType === "area"))
|
|
||||||
) {
|
|
||||||
fireEvent(this, "value-changed", { value: undefined });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType === "area") {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { floor_id: this.areas[valueId].floor_id || undefined },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType === "device") {
|
|
||||||
if (
|
|
||||||
!this.devices[valueId].area_id &&
|
|
||||||
this.devices[valueId].entry_type === "service"
|
|
||||||
) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { service_id: undefined },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { area_id: this.devices[valueId].area_id || undefined },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType === "entity" && valueId) {
|
|
||||||
const deviceId = this.entities[valueId].device_id;
|
|
||||||
if (deviceId) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { device_id: deviceId },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const areaId = this.entities[valueId].area_id;
|
|
||||||
if (areaId) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { area_id: areaId },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const domain = valueId.split(".", 2)[0];
|
|
||||||
const manifest = this.manifests ? this.manifests[domain] : undefined;
|
|
||||||
if (manifest?.integration_type === "helper") {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { [`helper_${domain}_id`]: undefined },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { [`entity_${domain}_id`]: undefined },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType.startsWith("helper_") || valueType.startsWith("entity_")) {
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: {
|
|
||||||
[`${valueType.startsWith("helper_") ? "helper" : "device"}_id`]:
|
|
||||||
undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _expandHeight() {
|
private _expandHeight() {
|
||||||
this._fullHeight = true;
|
this._fullHeight = true;
|
||||||
this.style.setProperty("--max-height", "none");
|
this.style.setProperty("--max-height", "none");
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export class HaAutomationAddItems extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--ha-color-text-secondary);
|
color: var(--ha-color-text-secondary);
|
||||||
padding: var(--ha-space-0);
|
padding: var(--ha-space-0);
|
||||||
margin: var(--ha-space-3) var(--ha-space-4)
|
margin: var(--ha-space-0) var(--ha-space-4)
|
||||||
max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
||||||
line-height: var(--ha-line-height-expanded);
|
line-height: var(--ha-line-height-expanded);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -306,7 +306,7 @@ export class HaAutomationAddItems extends LitElement {
|
|||||||
.items .item-headline {
|
.items .item-headline {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--ha-space-1);
|
gap: var(--ha-space-2);
|
||||||
min-height: var(--ha-space-9);
|
min-height: var(--ha-space-9);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
@@ -366,12 +366,16 @@ export class HaAutomationAddItems extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selected-target state-badge {
|
.selected-target state-badge {
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 24px;
|
||||||
}
|
}
|
||||||
.selected-target state-badge,
|
.selected-target state-badge,
|
||||||
|
.selected-target ha-floor-icon {
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
.selected-target ha-domain-icon {
|
.selected-target ha-domain-icon {
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
import type {
|
||||||
|
HassServiceTarget,
|
||||||
|
UnsubscribeFunc,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||||
@@ -25,6 +28,7 @@ import {
|
|||||||
CONDITION_BUILDING_BLOCKS,
|
CONDITION_BUILDING_BLOCKS,
|
||||||
subscribeConditions,
|
subscribeConditions,
|
||||||
} from "../../../../data/condition";
|
} from "../../../../data/condition";
|
||||||
|
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import {
|
import {
|
||||||
@@ -74,19 +78,52 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _conditionKeys = new WeakMap<Condition, string>();
|
private _conditionKeys = new WeakMap<Condition, string>();
|
||||||
|
|
||||||
|
private _unsub?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@state() private _newTriggersAndConditions = false;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
protected hassSubscribe() {
|
protected hassSubscribe() {
|
||||||
return [
|
return [
|
||||||
subscribeConditions(this.hass, (conditions) =>
|
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||||
this._addConditions(conditions)
|
this._newTriggersAndConditions =
|
||||||
),
|
features.find(
|
||||||
|
(feature) =>
|
||||||
|
feature.domain === "automation" &&
|
||||||
|
feature.preview_feature === "new_triggers_conditions"
|
||||||
|
)?.enabled ?? false;
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addConditions(conditions: ConditionDescriptions) {
|
private _subscribeDescriptions() {
|
||||||
this._conditionDescriptions = {
|
this._unsubscribe();
|
||||||
...this._conditionDescriptions,
|
this._conditionDescriptions = {};
|
||||||
...conditions,
|
this._unsub = subscribeConditions(this.hass, (descriptions) => {
|
||||||
};
|
this._conditionDescriptions = {
|
||||||
|
...this._conditionDescriptions,
|
||||||
|
...descriptions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribe() {
|
||||||
|
if (this._unsub) {
|
||||||
|
this._unsub.then((unsub) => unsub());
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (changedProperties.has("_newTriggersAndConditions")) {
|
||||||
|
this._subscribeDescriptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
|||||||
@@ -69,6 +69,45 @@ export class HaPlatformCondition extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this._manifest = undefined;
|
this._manifest = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
oldValue?.condition !== this.condition?.condition &&
|
||||||
|
this.condition &&
|
||||||
|
this.description?.fields
|
||||||
|
) {
|
||||||
|
let updatedDefaultValue = false;
|
||||||
|
const updatedOptions = {};
|
||||||
|
const loadDefaults = !("options" in this.condition);
|
||||||
|
// Set mandatory bools without a default value to false
|
||||||
|
Object.entries(this.description.fields).forEach(([key, field]) => {
|
||||||
|
if (
|
||||||
|
field.selector &&
|
||||||
|
field.required &&
|
||||||
|
field.default === undefined &&
|
||||||
|
"boolean" in field.selector &&
|
||||||
|
updatedOptions[key] === undefined
|
||||||
|
) {
|
||||||
|
updatedDefaultValue = true;
|
||||||
|
updatedOptions[key] = false;
|
||||||
|
} else if (
|
||||||
|
loadDefaults &&
|
||||||
|
field.selector &&
|
||||||
|
field.default !== undefined &&
|
||||||
|
updatedOptions[key] === undefined
|
||||||
|
) {
|
||||||
|
updatedDefaultValue = true;
|
||||||
|
updatedOptions[key] = field.default;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (updatedDefaultValue) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.condition,
|
||||||
|
options: updatedOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -85,9 +124,9 @@ export class HaPlatformCondition extends LitElement {
|
|||||||
|
|
||||||
const hasOptional = Boolean(
|
const hasOptional = Boolean(
|
||||||
conditionDesc?.fields &&
|
conditionDesc?.fields &&
|
||||||
Object.values(conditionDesc.fields).some((field) =>
|
Object.values(conditionDesc.fields).some((field) =>
|
||||||
showOptionalToggle(field)
|
showOptionalToggle(field)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -354,6 +393,10 @@ export class HaPlatformCondition extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
margin: 0px calc(-1 * var(--ha-space-4));
|
||||||
|
}
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0 var(--ha-space-4);
|
padding: 0 var(--ha-space-4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { transform } from "../../../common/decorators/transform";
|
import { transform } from "../../../common/decorators/transform";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
@@ -112,6 +112,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("ha-automation-editor")
|
||||||
export class HaAutomationEditor extends PreventUnsavedMixin(
|
export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||||
KeyboardShortcutMixin(LitElement)
|
KeyboardShortcutMixin(LitElement)
|
||||||
) {
|
) {
|
||||||
@@ -1339,5 +1340,3 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-automation-editor", HaAutomationEditor);
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { slugify } from "../../../common/string/slugify";
|
||||||
|
import "../../../components/ha-tooltip";
|
||||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import {
|
import {
|
||||||
hasRejectedItems,
|
hasRejectedItems,
|
||||||
@@ -327,14 +329,19 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
const date = new Date(automation.last_triggered);
|
const date = new Date(automation.last_triggered);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dayDifference = differenceInDays(now, date);
|
const dayDifference = differenceInDays(now, date);
|
||||||
|
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||||
|
date,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
);
|
||||||
|
const elementId = "last-triggered-" + slugify(automation.entity_id);
|
||||||
return html`
|
return html`
|
||||||
${dayDifference > 3
|
${dayDifference > 3
|
||||||
? formatShortDateTimeWithConditionalYear(
|
? formattedTime
|
||||||
date,
|
: html`
|
||||||
this.hass.locale,
|
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||||
this.hass.config
|
<span id=${elementId}>${relativeTime(date, locale)}</span>
|
||||||
)
|
`}
|
||||||
: relativeTime(date, locale)}
|
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ import "./types/ha-automation-trigger-event";
|
|||||||
import "./types/ha-automation-trigger-geo_location";
|
import "./types/ha-automation-trigger-geo_location";
|
||||||
import "./types/ha-automation-trigger-homeassistant";
|
import "./types/ha-automation-trigger-homeassistant";
|
||||||
import "./types/ha-automation-trigger-list";
|
import "./types/ha-automation-trigger-list";
|
||||||
import "./types/ha-automation-trigger-mqtt";
|
|
||||||
import "./types/ha-automation-trigger-numeric_state";
|
import "./types/ha-automation-trigger-numeric_state";
|
||||||
import "./types/ha-automation-trigger-persistent_notification";
|
import "./types/ha-automation-trigger-persistent_notification";
|
||||||
import "./types/ha-automation-trigger-platform";
|
import "./types/ha-automation-trigger-platform";
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
import type {
|
||||||
|
HassServiceTarget,
|
||||||
|
UnsubscribeFunc,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -21,6 +24,7 @@ import {
|
|||||||
type Trigger,
|
type Trigger,
|
||||||
type TriggerList,
|
type TriggerList,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
|
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||||
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
@@ -67,16 +71,54 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||||
|
|
||||||
|
private _unsub?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@state() private _newTriggersAndConditions = false;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
protected hassSubscribe() {
|
protected hassSubscribe() {
|
||||||
return [
|
return [
|
||||||
subscribeTriggers(this.hass, (triggers) => this._addTriggers(triggers)),
|
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||||
|
this._newTriggersAndConditions =
|
||||||
|
features.find(
|
||||||
|
(feature) =>
|
||||||
|
feature.domain === "automation" &&
|
||||||
|
feature.preview_feature === "new_triggers_conditions"
|
||||||
|
)?.enabled ?? false;
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addTriggers(triggers: TriggerDescriptions) {
|
private _subscribeDescriptions() {
|
||||||
this._triggerDescriptions = { ...this._triggerDescriptions, ...triggers };
|
this._unsubscribe();
|
||||||
|
this._triggerDescriptions = {};
|
||||||
|
this._unsub = subscribeTriggers(this.hass, (descriptions) => {
|
||||||
|
this._triggerDescriptions = {
|
||||||
|
...this._triggerDescriptions,
|
||||||
|
...descriptions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribe() {
|
||||||
|
if (this._unsub) {
|
||||||
|
this._unsub.then((unsub) => unsub());
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (changedProperties.has("_newTriggersAndConditions")) {
|
||||||
|
this._subscribeDescriptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { html, LitElement } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
|
||||||
import "../../../../../components/ha-form/ha-form";
|
|
||||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
|
||||||
import type { MqttTrigger } from "../../../../../data/automation";
|
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
|
||||||
import type { TriggerElement } from "../ha-automation-trigger-row";
|
|
||||||
|
|
||||||
const SCHEMA = [
|
|
||||||
{ name: "topic", required: true, selector: { text: {} } },
|
|
||||||
{ name: "payload", selector: { text: {} } },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-mqtt")
|
|
||||||
export class HaMQTTTrigger extends LitElement implements TriggerElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public trigger!: MqttTrigger;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
|
||||||
|
|
||||||
public static get defaultConfig(): MqttTrigger {
|
|
||||||
return { trigger: "mqtt", topic: "" };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<ha-form
|
|
||||||
.schema=${SCHEMA}
|
|
||||||
.data=${this.trigger}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.computeLabel=${this._computeLabelCallback}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-form>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const newTrigger = ev.detail.value;
|
|
||||||
fireEvent(this, "value-changed", { value: newTrigger });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
|
||||||
schema: SchemaUnion<typeof SCHEMA>
|
|
||||||
): string =>
|
|
||||||
this.hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.triggers.type.mqtt.${schema.name}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-automation-trigger-mqtt": HaMQTTTrigger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,6 +25,16 @@ const showOptionalToggle = (field: TriggerDescription["fields"][string]) =>
|
|||||||
!field.required &&
|
!field.required &&
|
||||||
!("boolean" in field.selector && field.default);
|
!("boolean" in field.selector && field.default);
|
||||||
|
|
||||||
|
const DEFAULT_KEYS: (keyof PlatformTrigger)[] = [
|
||||||
|
"trigger",
|
||||||
|
"target",
|
||||||
|
"alias",
|
||||||
|
"id",
|
||||||
|
"variables",
|
||||||
|
"enabled",
|
||||||
|
"options",
|
||||||
|
] as const;
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-platform")
|
@customElement("ha-automation-trigger-platform")
|
||||||
export class HaPlatformTrigger extends LitElement {
|
export class HaPlatformTrigger extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -52,6 +62,31 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
if (!changedProperties.has("trigger")) {
|
if (!changedProperties.has("trigger")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newValue: PlatformTrigger | undefined;
|
||||||
|
|
||||||
|
for (const key in this.trigger) {
|
||||||
|
// Migrate old options to `options`
|
||||||
|
if (DEFAULT_KEYS.includes(key as keyof PlatformTrigger)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (newValue === undefined) {
|
||||||
|
newValue = {
|
||||||
|
...this.trigger,
|
||||||
|
options: { [key]: this.trigger[key] },
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newValue.options![key] = this.trigger[key];
|
||||||
|
}
|
||||||
|
delete newValue[key];
|
||||||
|
}
|
||||||
|
if (newValue !== undefined) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
this.trigger = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
const oldValue = changedProperties.get("trigger") as
|
const oldValue = changedProperties.get("trigger") as
|
||||||
| undefined
|
| undefined
|
||||||
| this["trigger"];
|
| this["trigger"];
|
||||||
@@ -69,6 +104,46 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this._manifest = undefined;
|
this._manifest = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
oldValue?.trigger !== this.trigger?.trigger &&
|
||||||
|
this.trigger &&
|
||||||
|
this.description?.fields
|
||||||
|
) {
|
||||||
|
let updatedDefaultValue = false;
|
||||||
|
const updatedOptions = {};
|
||||||
|
const loadDefaults = !("options" in this.trigger);
|
||||||
|
// Set mandatory bools without a default value to false
|
||||||
|
Object.entries(this.description.fields).forEach(([key, field]) => {
|
||||||
|
if (
|
||||||
|
field.selector &&
|
||||||
|
field.required &&
|
||||||
|
field.default === undefined &&
|
||||||
|
"boolean" in field.selector &&
|
||||||
|
updatedOptions[key] === undefined
|
||||||
|
) {
|
||||||
|
updatedDefaultValue = true;
|
||||||
|
updatedOptions[key] = false;
|
||||||
|
} else if (
|
||||||
|
loadDefaults &&
|
||||||
|
field.selector &&
|
||||||
|
field.default !== undefined &&
|
||||||
|
updatedOptions[key] === undefined
|
||||||
|
) {
|
||||||
|
updatedDefaultValue = true;
|
||||||
|
updatedOptions[key] = field.default;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updatedDefaultValue) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.trigger,
|
||||||
|
options: updatedOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -85,9 +160,9 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
|
|
||||||
const hasOptional = Boolean(
|
const hasOptional = Boolean(
|
||||||
triggerDesc?.fields &&
|
triggerDesc?.fields &&
|
||||||
Object.values(triggerDesc.fields).some((field) =>
|
Object.values(triggerDesc.fields).some((field) =>
|
||||||
showOptionalToggle(field)
|
showOptionalToggle(field)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -354,6 +429,10 @@ export class HaPlatformTrigger extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
margin: 0px calc(-1 * var(--ha-space-4));
|
||||||
|
}
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0 var(--ha-space-4);
|
padding: 0 var(--ha-space-4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mdiOpenInNew } from "@mdi/js";
|
import { mdiOpenInNew } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
@@ -13,6 +13,7 @@ import type { WebhookDialogParams } from "./show-dialog-manage-cloudhook";
|
|||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-copy-textfield";
|
import "../../../../components/ha-copy-textfield";
|
||||||
|
|
||||||
|
@customElement("dialog-manage-cloudhook")
|
||||||
export class DialogManageCloudhook extends LitElement {
|
export class DialogManageCloudhook extends LitElement {
|
||||||
protected hass?: HomeAssistant;
|
protected hass?: HomeAssistant;
|
||||||
|
|
||||||
@@ -155,5 +156,3 @@ declare global {
|
|||||||
"dialog-manage-cloudhook": DialogManageCloudhook;
|
"dialog-manage-cloudhook": DialogManageCloudhook;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-manage-cloudhook", DialogManageCloudhook);
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { mdiKey } from "@mdi/js";
|
||||||
|
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||||
|
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||||
|
import { fetchESPHomeEncryptionKey } from "../../../../../../data/esphome";
|
||||||
|
import type { HomeAssistant } from "../../../../../../types";
|
||||||
|
import { showESPHomeEncryptionKeyDialog } from "../../../../integrations/integration-panels/esphome/show-dialog-esphome-encryption-key";
|
||||||
|
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||||
|
|
||||||
|
export const getESPHomeDeviceActions = async (
|
||||||
|
el: HTMLElement,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device: DeviceRegistryEntry
|
||||||
|
): Promise<DeviceAction[]> => {
|
||||||
|
const actions: DeviceAction[] = [];
|
||||||
|
|
||||||
|
const configEntries = await getConfigEntries(hass, {
|
||||||
|
domain: "esphome",
|
||||||
|
});
|
||||||
|
|
||||||
|
const configEntry = configEntries.find((entry) =>
|
||||||
|
device.config_entries.includes(entry.entry_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!configEntry) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryId = configEntry.entry_id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encryptionKey = await fetchESPHomeEncryptionKey(hass, entryId);
|
||||||
|
|
||||||
|
if (encryptionKey.encryption_key) {
|
||||||
|
actions.push({
|
||||||
|
label: hass.localize(
|
||||||
|
"ui.panel.config.devices.esphome.show_encryption_key"
|
||||||
|
),
|
||||||
|
icon: mdiKey,
|
||||||
|
action: () =>
|
||||||
|
showESPHomeEncryptionKeyDialog(el, {
|
||||||
|
entry_id: entryId,
|
||||||
|
encryption_key: encryptionKey.encryption_key,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Failed to fetch ESPHome encryption key:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
};
|
||||||
@@ -1138,23 +1138,20 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (domains.includes("mqtt")) {
|
if (domains.includes("mqtt")) {
|
||||||
const mqtt = await import(
|
const mqtt =
|
||||||
"./device-detail/integration-elements/mqtt/device-actions"
|
await import("./device-detail/integration-elements/mqtt/device-actions");
|
||||||
);
|
|
||||||
const actions = mqtt.getMQTTDeviceActions(this, device);
|
const actions = mqtt.getMQTTDeviceActions(this, device);
|
||||||
deviceActions.push(...actions);
|
deviceActions.push(...actions);
|
||||||
}
|
}
|
||||||
if (domains.includes("zha")) {
|
if (domains.includes("zha")) {
|
||||||
const zha = await import(
|
const zha =
|
||||||
"./device-detail/integration-elements/zha/device-actions"
|
await import("./device-detail/integration-elements/zha/device-actions");
|
||||||
);
|
|
||||||
const actions = await zha.getZHADeviceActions(this, this.hass, device);
|
const actions = await zha.getZHADeviceActions(this, this.hass, device);
|
||||||
deviceActions.push(...actions);
|
deviceActions.push(...actions);
|
||||||
}
|
}
|
||||||
if (domains.includes("zwave_js")) {
|
if (domains.includes("zwave_js")) {
|
||||||
const zwave = await import(
|
const zwave =
|
||||||
"./device-detail/integration-elements/zwave_js/device-actions"
|
await import("./device-detail/integration-elements/zwave_js/device-actions");
|
||||||
);
|
|
||||||
const actions = await zwave.getZwaveDeviceActions(
|
const actions = await zwave.getZwaveDeviceActions(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
@@ -1162,10 +1159,19 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
);
|
);
|
||||||
deviceActions.push(...actions);
|
deviceActions.push(...actions);
|
||||||
}
|
}
|
||||||
if (domains.includes("matter")) {
|
if (domains.includes("esphome")) {
|
||||||
const matter = await import(
|
const esphome =
|
||||||
"./device-detail/integration-elements/matter/device-actions"
|
await import("./device-detail/integration-elements/esphome/device-actions");
|
||||||
|
const actions = await esphome.getESPHomeDeviceActions(
|
||||||
|
this,
|
||||||
|
this.hass,
|
||||||
|
device
|
||||||
);
|
);
|
||||||
|
deviceActions.push(...actions);
|
||||||
|
}
|
||||||
|
if (domains.includes("matter")) {
|
||||||
|
const matter =
|
||||||
|
await import("./device-detail/integration-elements/matter/device-actions");
|
||||||
const defaultActions = matter.getMatterDeviceDefaultActions(
|
const defaultActions = matter.getMatterDeviceDefaultActions(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
@@ -1209,9 +1215,8 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
).map((int) => int.domain);
|
).map((int) => int.domain);
|
||||||
|
|
||||||
if (domains.includes("zwave_js")) {
|
if (domains.includes("zwave_js")) {
|
||||||
const zwave = await import(
|
const zwave =
|
||||||
"./device-detail/integration-elements/zwave_js/device-alerts"
|
await import("./device-detail/integration-elements/zwave_js/device-alerts");
|
||||||
);
|
|
||||||
|
|
||||||
const alerts = await zwave.getZwaveDeviceAlerts(this.hass, device);
|
const alerts = await zwave.getZwaveDeviceAlerts(this.hass, device);
|
||||||
deviceAlerts.push(...alerts);
|
deviceAlerts.push(...alerts);
|
||||||
@@ -1293,9 +1298,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
if (domains.includes("zwave_js")) {
|
if (domains.includes("zwave_js")) {
|
||||||
import(
|
import("./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js");
|
||||||
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
|
||||||
);
|
|
||||||
deviceInfo.push(html`
|
deviceInfo.push(html`
|
||||||
<ha-device-info-zwave_js
|
<ha-device-info-zwave_js
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -1304,9 +1307,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
if (domains.includes("matter")) {
|
if (domains.includes("matter")) {
|
||||||
import(
|
import("./device-detail/integration-elements/matter/ha-device-info-matter");
|
||||||
"./device-detail/integration-elements/matter/ha-device-info-matter"
|
|
||||||
);
|
|
||||||
deviceInfo.push(html`
|
deviceInfo.push(html`
|
||||||
<ha-device-info-matter
|
<ha-device-info-matter
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
|||||||
@@ -1012,7 +1012,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
|||||||
? html`<ha-area-picker
|
? html`<ha-area-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._areaId}
|
.value=${this._areaId}
|
||||||
.placeholder=${this._device?.area_id}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
></ha-area-picker>`
|
></ha-area-picker>`
|
||||||
|
|||||||
@@ -116,8 +116,10 @@ import { showAddIntegrationDialog } from "../integrations/show-add-integration-d
|
|||||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||||
import { slugify } from "../../../common/string/slugify";
|
import { slugify } from "../../../common/string/slugify";
|
||||||
|
|
||||||
export interface StateEntity
|
export interface StateEntity extends Omit<
|
||||||
extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
|
EntityRegistryEntry,
|
||||||
|
"id" | "unique_id"
|
||||||
|
> {
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@@ -530,9 +530,7 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
|||||||
zha: {
|
zha: {
|
||||||
tag: "zha-config-dashboard-router",
|
tag: "zha-config-dashboard-router",
|
||||||
load: () =>
|
load: () =>
|
||||||
import(
|
import("./integrations/integration-panels/zha/zha-config-dashboard-router"),
|
||||||
"./integrations/integration-panels/zha/zha-config-dashboard-router"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
mqtt: {
|
mqtt: {
|
||||||
tag: "mqtt-config-panel",
|
tag: "mqtt-config-panel",
|
||||||
@@ -542,30 +540,22 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
|||||||
zwave_js: {
|
zwave_js: {
|
||||||
tag: "zwave_js-config-router",
|
tag: "zwave_js-config-router",
|
||||||
load: () =>
|
load: () =>
|
||||||
import(
|
import("./integrations/integration-panels/zwave_js/zwave_js-config-router"),
|
||||||
"./integrations/integration-panels/zwave_js/zwave_js-config-router"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
matter: {
|
matter: {
|
||||||
tag: "matter-config-panel",
|
tag: "matter-config-panel",
|
||||||
load: () =>
|
load: () =>
|
||||||
import(
|
import("./integrations/integration-panels/matter/matter-config-panel"),
|
||||||
"./integrations/integration-panels/matter/matter-config-panel"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
thread: {
|
thread: {
|
||||||
tag: "thread-config-panel",
|
tag: "thread-config-panel",
|
||||||
load: () =>
|
load: () =>
|
||||||
import(
|
import("./integrations/integration-panels/thread/thread-config-panel"),
|
||||||
"./integrations/integration-panels/thread/thread-config-panel"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
bluetooth: {
|
bluetooth: {
|
||||||
tag: "bluetooth-config-dashboard-router",
|
tag: "bluetooth-config-dashboard-router",
|
||||||
load: () =>
|
load: () =>
|
||||||
import(
|
import("./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"),
|
||||||
"./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
dhcp: {
|
dhcp: {
|
||||||
tag: "dhcp-config-panel",
|
tag: "dhcp-config-panel",
|
||||||
@@ -580,9 +570,7 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
|||||||
zeroconf: {
|
zeroconf: {
|
||||||
tag: "zeroconf-config-panel",
|
tag: "zeroconf-config-panel",
|
||||||
load: () =>
|
load: () =>
|
||||||
import(
|
import("./integrations/integration-panels/zeroconf/zeroconf-config-panel"),
|
||||||
"./integrations/integration-panels/zeroconf/zeroconf-config-panel"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
application_credentials: {
|
application_credentials: {
|
||||||
tag: "ha-config-application-credentials",
|
tag: "ha-config-application-credentials",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing } from "lit";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||||
import "../../../../components/ha-form/ha-form";
|
import "../../../../components/ha-form/ha-form";
|
||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
} from "./show-dialog-schedule-block-info";
|
} from "./show-dialog-schedule-block-info";
|
||||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||||
|
|
||||||
|
@customElement("dialog-schedule-block-info")
|
||||||
class DialogScheduleBlockInfo extends LitElement {
|
class DialogScheduleBlockInfo extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -156,5 +157,3 @@ declare global {
|
|||||||
"dialog-schedule-block-info": DialogScheduleBlockInfo;
|
"dialog-schedule-block-info": DialogScheduleBlockInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-schedule-block-info", DialogScheduleBlockInfo);
|
|
||||||
|
|||||||
@@ -968,12 +968,6 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
|
||||||
return {
|
|
||||||
n: () => this._createFlow(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { mdiClose, mdiContentCopy } from "@mdi/js";
|
||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
|
||||||
|
import "../../../../../components/ha-button";
|
||||||
|
import "../../../../../components/ha-dialog-footer";
|
||||||
|
import "../../../../../components/ha-dialog-header";
|
||||||
|
import "../../../../../components/ha-icon-button";
|
||||||
|
import "../../../../../components/ha-wa-dialog";
|
||||||
|
import { haStyleDialog } from "../../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import { showToast } from "../../../../../util/toast";
|
||||||
|
import type { ESPHomeEncryptionKeyDialogParams } from "./show-dialog-esphome-encryption-key";
|
||||||
|
|
||||||
|
@customElement("dialog-esphome-encryption-key")
|
||||||
|
class DialogESPHomeEncryptionKey extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: ESPHomeEncryptionKeyDialogParams;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: ESPHomeEncryptionKeyDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._params = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._params) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-wa-dialog
|
||||||
|
open
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
header-title=${this.hass.localize(
|
||||||
|
"ui.panel.config.devices.esphome.encryption_key_title"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-dialog-header slot="heading">
|
||||||
|
<ha-icon-button
|
||||||
|
slot="navigationIcon"
|
||||||
|
dialogAction="cancel"
|
||||||
|
.label=${this.hass.localize("ui.common.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
|
></ha-icon-button>
|
||||||
|
<span slot="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.devices.esphome.encryption_key_title"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</ha-dialog-header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.devices.esphome.encryption_key_description"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="key-row">
|
||||||
|
<div class="key-container">
|
||||||
|
<code>${this._params.encryption_key}</code>
|
||||||
|
</div>
|
||||||
|
<ha-icon-button
|
||||||
|
@click=${this._copyToClipboard}
|
||||||
|
.label=${this.hass.localize("ui.common.copy")}
|
||||||
|
.path=${mdiContentCopy}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ha-dialog-footer slot="footer">
|
||||||
|
<ha-button slot="primaryAction" data-dialog="close">
|
||||||
|
${this.hass.localize("ui.common.close")}
|
||||||
|
</ha-button>
|
||||||
|
</ha-dialog-footer>
|
||||||
|
</ha-wa-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _copyToClipboard(): Promise<void> {
|
||||||
|
if (!this._params?.encryption_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await copyToClipboard(this._params.encryption_key);
|
||||||
|
showToast(this, {
|
||||||
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--ha-space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--ha-space-2);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-container {
|
||||||
|
flex: 1;
|
||||||
|
border-radius: var(--ha-space-2);
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
background-color: var(
|
||||||
|
--code-editor-background-color,
|
||||||
|
var(--secondary-background-color)
|
||||||
|
);
|
||||||
|
padding: var(--ha-space-3);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
line-height: var(--ha-line-height-condensed);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-esphome-encryption-key": DialogESPHomeEncryptionKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface ESPHomeEncryptionKeyDialogParams {
|
||||||
|
entry_id: string;
|
||||||
|
encryption_key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadESPHomeEncryptionKeyDialog = () =>
|
||||||
|
import("./dialog-esphome-encryption-key");
|
||||||
|
|
||||||
|
export const showESPHomeEncryptionKeyDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: ESPHomeEncryptionKeyDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-esphome-encryption-key",
|
||||||
|
dialogImport: loadESPHomeEncryptionKeyDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||||
import "../../../../../components/buttons/ha-call-service-button";
|
import "../../../../../components/buttons/ha-call-service-button";
|
||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
@@ -15,6 +15,7 @@ import type { HomeAssistant } from "../../../../../types";
|
|||||||
import { formatAsPaddedHex } from "./functions";
|
import { formatAsPaddedHex } from "./functions";
|
||||||
import type { IssueCommandServiceData } from "./types";
|
import type { IssueCommandServiceData } from "./types";
|
||||||
|
|
||||||
|
@customElement("zha-cluster-commands")
|
||||||
export class ZHAClusterCommands extends LitElement {
|
export class ZHAClusterCommands extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@@ -259,5 +260,3 @@ declare global {
|
|||||||
"zha-cluster-commands": ZHAClusterCommands;
|
"zha-cluster-commands": ZHAClusterCommands;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("zha-cluster-commands", ZHAClusterCommands);
|
|
||||||
|
|||||||
@@ -192,9 +192,9 @@ export class ZHAGroupBindingControl extends LitElement {
|
|||||||
private get _canBind(): boolean {
|
private get _canBind(): boolean {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
this._groupToBind &&
|
this._groupToBind &&
|
||||||
this._clustersToBind &&
|
this._clustersToBind &&
|
||||||
this._clustersToBind?.length > 0 &&
|
this._clustersToBind?.length > 0 &&
|
||||||
this.device
|
this.device
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import {
|
|||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../../../../data/entity_registry";
|
} from "../../../../../../data/entity_registry";
|
||||||
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
|
||||||
import "./zwave-js-add-node-added-insecure";
|
import "./zwave-js-add-node-added-insecure";
|
||||||
import "./zwave-js-add-node-code-input";
|
import "./zwave-js-add-node-code-input";
|
||||||
import "./zwave-js-add-node-configure-device";
|
import "./zwave-js-add-node-configure-device";
|
||||||
@@ -69,7 +68,7 @@ import "./zwave-js-add-node-select-security-strategy";
|
|||||||
const INCLUSION_TIMEOUT_MINUTES = 5;
|
const INCLUSION_TIMEOUT_MINUTES = 5;
|
||||||
|
|
||||||
@customElement("dialog-zwave_js-add-node")
|
@customElement("dialog-zwave_js-add-node")
|
||||||
class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
class DialogZWaveJSAddNode extends LitElement {
|
||||||
// #region variables
|
// #region variables
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -103,6 +102,8 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _securityClasses: SecurityClass[] = [];
|
@state() private _securityClasses: SecurityClass[] = [];
|
||||||
|
|
||||||
|
@state() private _entities: EntityRegistryEntry[] = [];
|
||||||
|
|
||||||
@state() private _codeInput = "";
|
@state() private _codeInput = "";
|
||||||
|
|
||||||
@query("ha-dialog") private _dialog?: HaDialog;
|
@query("ha-dialog") private _dialog?: HaDialog;
|
||||||
@@ -113,22 +114,14 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _onStop?: () => void;
|
private _onStop?: () => void;
|
||||||
|
|
||||||
private _subscribed?: Promise<UnsubscribeFunc | undefined>;
|
private _subscribedAddZwaveNode?: Promise<UnsubscribeFunc | undefined>;
|
||||||
|
|
||||||
private _newDeviceSubscription?: Promise<UnsubscribeFunc | undefined>;
|
private _newDeviceSubscription?: Promise<UnsubscribeFunc | undefined>;
|
||||||
|
|
||||||
@state() private _entities: EntityRegistryEntry[] = [];
|
private _subscribedEntityRegistry?: UnsubscribeFunc;
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
|
||||||
return [
|
|
||||||
subscribeEntityRegistry(this.hass.connection, (entities) => {
|
|
||||||
this._entities = entities;
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._entryId) {
|
if (!this._entryId) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -439,11 +432,6 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
></zwave-js-add-node-loading>`;
|
></zwave-js-add-node-loading>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
window.addEventListener("beforeunload", this._onBeforeUnload);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
|
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
if (this._step && this._shouldPreventClose(this._step)) {
|
if (this._step && this._shouldPreventClose(this._step)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -468,6 +456,14 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
||||||
|
window.addEventListener("beforeunload", this._onBeforeUnload);
|
||||||
|
this._subscribedEntityRegistry = subscribeEntityRegistry(
|
||||||
|
this.hass.connection,
|
||||||
|
(entities) => {
|
||||||
|
this._entities = entities;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (this._step) {
|
if (this._step) {
|
||||||
// already started
|
// already started
|
||||||
return;
|
return;
|
||||||
@@ -562,7 +558,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
this._step = "select_method";
|
this._step = "select_method";
|
||||||
break;
|
break;
|
||||||
case "search_devices":
|
case "search_devices":
|
||||||
this._unsubscribe();
|
this._unsubscribeAddZwaveNode();
|
||||||
if (
|
if (
|
||||||
this._supportsSmartStart &&
|
this._supportsSmartStart &&
|
||||||
this.hass.auth.external?.config.hasBarCodeScanner
|
this.hass.auth.external?.config.hasBarCodeScanner
|
||||||
@@ -604,7 +600,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _searchDevicesShowSecurityOptions() {
|
private _searchDevicesShowSecurityOptions() {
|
||||||
this._unsubscribe();
|
this._unsubscribeAddZwaveNode();
|
||||||
this._step = "choose_security_strategy";
|
this._step = "choose_security_strategy";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,7 +622,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
this._lowSecurity = false;
|
this._lowSecurity = false;
|
||||||
|
|
||||||
const s2Device = qrProvisioningInformation || dsk;
|
const s2Device = qrProvisioningInformation || dsk;
|
||||||
this._subscribed = subscribeAddZwaveNode(
|
this._subscribedAddZwaveNode = subscribeAddZwaveNode(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entryId!,
|
this._entryId!,
|
||||||
(message) => {
|
(message) => {
|
||||||
@@ -635,7 +631,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
this._step = s2Device ? "search_s2_device" : "search_devices";
|
this._step = s2Device ? "search_s2_device" : "search_devices";
|
||||||
break;
|
break;
|
||||||
case "inclusion failed":
|
case "inclusion failed":
|
||||||
this._unsubscribe();
|
this._unsubscribeAddZwaveNode();
|
||||||
this._step = "failed";
|
this._step = "failed";
|
||||||
break;
|
break;
|
||||||
case "inclusion stopped":
|
case "inclusion stopped":
|
||||||
@@ -677,7 +673,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
this._lowSecurityReason = message.node.low_security_reason;
|
this._lowSecurityReason = message.node.low_security_reason;
|
||||||
break;
|
break;
|
||||||
case "interview completed":
|
case "interview completed":
|
||||||
this._unsubscribe();
|
this._unsubscribeAddZwaveNode();
|
||||||
this._step = "configure_device";
|
this._step = "configure_device";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -694,7 +690,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
this._addNodeTimeoutHandle = window.setTimeout(
|
this._addNodeTimeoutHandle = window.setTimeout(
|
||||||
() => {
|
() => {
|
||||||
this._unsubscribe();
|
this._unsubscribeAddZwaveNode();
|
||||||
this._error = this.hass.localize(
|
this._error = this.hass.localize(
|
||||||
"ui.panel.config.zwave_js.add_node.timeout_error",
|
"ui.panel.config.zwave_js.add_node.timeout_error",
|
||||||
{ minutes: INCLUSION_TIMEOUT_MINUTES }
|
{ minutes: INCLUSION_TIMEOUT_MINUTES }
|
||||||
@@ -1023,10 +1019,10 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _unsubscribe(): void {
|
private _unsubscribeAddZwaveNode(): void {
|
||||||
if (this._subscribed) {
|
if (this._subscribedAddZwaveNode) {
|
||||||
this._subscribed.then((unsub) => unsub && unsub());
|
this._subscribedAddZwaveNode.then((unsub) => unsub && unsub());
|
||||||
this._subscribed = undefined;
|
this._subscribedAddZwaveNode = undefined;
|
||||||
|
|
||||||
if (this._entryId) {
|
if (this._entryId) {
|
||||||
stopZwaveInclusion(this.hass, this._entryId);
|
stopZwaveInclusion(this.hass, this._entryId);
|
||||||
@@ -1060,8 +1056,17 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _unsubscribeDialog() {
|
||||||
|
if (this._subscribedEntityRegistry) {
|
||||||
|
this._subscribedEntityRegistry();
|
||||||
|
this._subscribedEntityRegistry = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _dialogClosed() {
|
private _dialogClosed() {
|
||||||
this._unsubscribe();
|
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||||
|
this._unsubscribeAddZwaveNode();
|
||||||
|
this._unsubscribeDialog();
|
||||||
this._open = false;
|
this._open = false;
|
||||||
this._entryId = undefined;
|
this._entryId = undefined;
|
||||||
this._step = undefined;
|
this._step = undefined;
|
||||||
@@ -1100,7 +1105,8 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||||
|
|
||||||
this._unsubscribe();
|
this._unsubscribeAddZwaveNode();
|
||||||
|
this._unsubscribeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-button";
|
import "../../../components/ha-button";
|
||||||
@@ -11,6 +12,7 @@ import type { HaSwitch } from "../../../components/ha-switch";
|
|||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import type { BackupConfig } from "../../../data/backup";
|
import type { BackupConfig } from "../../../data/backup";
|
||||||
import { fetchBackupConfig } from "../../../data/backup";
|
import { fetchBackupConfig } from "../../../data/backup";
|
||||||
|
import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
|
||||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import type { LabsPreviewFeatureEnableDialogParams } from "./show-dialog-labs-preview-feature-enable";
|
import type { LabsPreviewFeatureEnableDialogParams } from "./show-dialog-labs-preview-feature-enable";
|
||||||
@@ -35,7 +37,10 @@ export class DialogLabsPreviewFeatureEnable
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._createBackup = false;
|
this._createBackup = false;
|
||||||
await this._fetchBackupConfig();
|
this._fetchBackupConfig();
|
||||||
|
if (isComponentLoaded(this.hass, "hassio")) {
|
||||||
|
this._fetchUpdateBackupConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): boolean {
|
public closeDialog(): boolean {
|
||||||
@@ -54,15 +59,21 @@ export class DialogLabsPreviewFeatureEnable
|
|||||||
try {
|
try {
|
||||||
const { config } = await fetchBackupConfig(this.hass);
|
const { config } = await fetchBackupConfig(this.hass);
|
||||||
this._backupConfig = config;
|
this._backupConfig = config;
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore error, user will get manual backup option
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Default to enabled if automatic backups are configured, disabled otherwise
|
private async _fetchUpdateBackupConfig() {
|
||||||
this._createBackup =
|
try {
|
||||||
config.automatic_backups_configured &&
|
const config = await getSupervisorUpdateConfig(this.hass);
|
||||||
!!config.create_backup.password &&
|
this._createBackup = config.core_backup_before_update;
|
||||||
config.create_backup.agent_ids.length > 0;
|
} catch (err) {
|
||||||
} catch {
|
// Ignore error, user can still toggle the switch manually
|
||||||
// User will get manual backup option if fetch fails
|
// eslint-disable-next-line no-console
|
||||||
this._createBackup = false;
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
|||||||
<hass-subpage
|
<hass-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
back-path="/config"
|
back-path="/config/system"
|
||||||
.header=${this.hass.localize("ui.panel.config.labs.caption")}
|
.header=${this.hass.localize("ui.panel.config.labs.caption")}
|
||||||
>
|
>
|
||||||
${sortedFeatures.length
|
${sortedFeatures.length
|
||||||
@@ -385,6 +385,10 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a[slot="toolbar-icon"] {
|
||||||
|
color: var(--sidebar-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mdiClose, mdiContentCopy } from "@mdi/js";
|
import { mdiClose, mdiContentCopy } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } 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";
|
||||||
@@ -26,6 +26,7 @@ import { showToast } from "../../../util/toast";
|
|||||||
import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||||
import { formatSystemLogTime } from "./util";
|
import { formatSystemLogTime } from "./util";
|
||||||
|
|
||||||
|
@customElement("dialog-system-log-detail")
|
||||||
class DialogSystemLogDetail extends LitElement {
|
class DialogSystemLogDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -272,5 +273,3 @@ declare global {
|
|||||||
"dialog-system-log-detail": DialogSystemLogDetail;
|
"dialog-system-log-detail": DialogSystemLogDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-system-log-detail", DialogSystemLogDetail);
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
|
|
||||||
PANEL_DASHBOARDS.forEach((panel) => {
|
PANEL_DASHBOARDS.forEach((panel) => {
|
||||||
const panelInfo = this.hass.panels[panel];
|
const panelInfo = this.hass.panels[panel];
|
||||||
if (!panel) {
|
if (!panelInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const item: DataTableItem = {
|
const item: DataTableItem = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mdiPencil } from "@mdi/js";
|
import { mdiPencil } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/entity/ha-entities-picker";
|
import "../../../components/entity/ha-entities-picker";
|
||||||
@@ -43,6 +43,7 @@ const cropOptions: CropOptions = {
|
|||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@customElement("dialog-person-detail")
|
||||||
class DialogPersonDetail extends LitElement implements HassDialog {
|
class DialogPersonDetail extends LitElement implements HassDialog {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -567,5 +568,3 @@ declare global {
|
|||||||
"dialog-person-detail": DialogPersonDetail;
|
"dialog-person-detail": DialogPersonDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-person-detail", DialogPersonDetail);
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { LitElement, css, html, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
import { storage } from "../../../common/decorators/storage";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
@@ -301,10 +301,21 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
const date = new Date(scene.state);
|
const date = new Date(scene.state);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dayDifference = differenceInDays(now, date);
|
const dayDifference = differenceInDays(now, date);
|
||||||
|
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||||
|
date,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
);
|
||||||
|
const elementId = "last-activated-" + slugify(scene.entity_id);
|
||||||
return html`
|
return html`
|
||||||
${dayDifference > 3
|
${dayDifference > 3
|
||||||
? formatShortDateTime(date, this.hass.locale, this.hass.config)
|
? formattedTime
|
||||||
: relativeTime(date, this.hass.locale)}
|
: html`
|
||||||
|
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||||
|
<span id=${elementId}
|
||||||
|
>${relativeTime(date, this.hass.locale)}</span
|
||||||
|
>
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { goBack, navigate } from "../../../common/navigate";
|
import { goBack, navigate } from "../../../common/navigate";
|
||||||
@@ -79,6 +79,7 @@ import "./manual-script-editor";
|
|||||||
import type { HaManualScriptEditor } from "./manual-script-editor";
|
import type { HaManualScriptEditor } from "./manual-script-editor";
|
||||||
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||||
|
|
||||||
|
@customElement("ha-script-editor")
|
||||||
export class HaScriptEditor extends SubscribeMixin(
|
export class HaScriptEditor extends SubscribeMixin(
|
||||||
PreventUnsavedMixin(KeyboardShortcutMixin(LitElement))
|
PreventUnsavedMixin(KeyboardShortcutMixin(LitElement))
|
||||||
) {
|
) {
|
||||||
@@ -1278,8 +1279,6 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-script-editor", HaScriptEditor);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-script-editor": HaScriptEditor;
|
"ha-script-editor": HaScriptEditor;
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { slugify } from "../../../common/string/slugify";
|
||||||
|
import "../../../components/ha-tooltip";
|
||||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import {
|
import {
|
||||||
hasRejectedItems,
|
hasRejectedItems,
|
||||||
@@ -302,19 +304,27 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
title: localize("ui.card.automation.last_triggered"),
|
title: localize("ui.card.automation.last_triggered"),
|
||||||
template: (script) => {
|
template: (script) => {
|
||||||
|
if (!script.last_triggered) {
|
||||||
|
return this.hass.localize("ui.components.relative_time.never");
|
||||||
|
}
|
||||||
const date = new Date(script.last_triggered);
|
const date = new Date(script.last_triggered);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dayDifference = differenceInDays(now, date);
|
const dayDifference = differenceInDays(now, date);
|
||||||
|
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||||
|
date,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
);
|
||||||
|
const elementId = "last-triggered-" + slugify(script.entity_id);
|
||||||
return html`
|
return html`
|
||||||
${script.last_triggered
|
${dayDifference > 3
|
||||||
? dayDifference > 3
|
? formattedTime
|
||||||
? formatShortDateTimeWithConditionalYear(
|
: html`
|
||||||
date,
|
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||||
this.hass.locale,
|
<span id=${elementId}
|
||||||
this.hass.config
|
>${relativeTime(date, this.hass.locale)}</span
|
||||||
)
|
>
|
||||||
: relativeTime(date, this.hass.locale)
|
`}
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
|
||||||
|
@customElement("cloud-alexa-pref")
|
||||||
export class CloudAlexaPref extends LitElement {
|
export class CloudAlexaPref extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -297,5 +298,3 @@ declare global {
|
|||||||
"cloud-alexa-pref": CloudAlexaPref;
|
"cloud-alexa-pref": CloudAlexaPref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("cloud-alexa-pref", CloudAlexaPref);
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
||||||
@@ -23,6 +23,7 @@ import type { HomeAssistant } from "../../../types";
|
|||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
||||||
|
|
||||||
|
@customElement("cloud-google-pref")
|
||||||
export class CloudGooglePref extends LitElement {
|
export class CloudGooglePref extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -367,5 +368,3 @@ declare global {
|
|||||||
"cloud-google-pref": CloudGooglePref;
|
"cloud-google-pref": CloudGooglePref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("cloud-google-pref", CloudGooglePref);
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
@@ -19,6 +19,7 @@ const SCHEMA = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@customElement("dialog-home-zone-detail")
|
||||||
class DialogHomeZoneDetail extends LitElement {
|
class DialogHomeZoneDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -151,5 +152,3 @@ declare global {
|
|||||||
"dialog-home-zone-detail": DialogHomeZoneDetail;
|
"dialog-home-zone-detail": DialogHomeZoneDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-home-zone-detail", DialogHomeZoneDetail);
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
||||||
@@ -14,6 +14,7 @@ import { haStyleDialog } from "../../../resources/styles";
|
|||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import type { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
import type { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||||
|
|
||||||
|
@customElement("dialog-zone-detail")
|
||||||
class DialogZoneDetail extends LitElement {
|
class DialogZoneDetail extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -241,5 +242,3 @@ declare global {
|
|||||||
"dialog-zone-detail": DialogZoneDetail;
|
"dialog-zone-detail": DialogZoneDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("dialog-zone-detail", DialogZoneDetail);
|
|
||||||
|
|||||||
@@ -47,12 +47,9 @@ import { configSections } from "../ha-panel-config";
|
|||||||
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
|
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
|
||||||
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
|
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
|
||||||
import { slugify } from "../../../common/string/slugify";
|
import { slugify } from "../../../common/string/slugify";
|
||||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
|
||||||
|
|
||||||
@customElement("ha-config-zone")
|
@customElement("ha-config-zone")
|
||||||
export class HaConfigZone extends KeyboardShortcutMixin(
|
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||||
SubscribeMixin(LitElement)
|
|
||||||
) {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||||
@@ -540,12 +537,6 @@ export class HaConfigZone extends KeyboardShortcutMixin(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
|
||||||
return {
|
|
||||||
n: () => this._createZone(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
hass-loading-screen {
|
hass-loading-screen {
|
||||||
--app-header-background-color: var(--sidebar-background-color);
|
--app-header-background-color: var(--sidebar-background-color);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { ReactiveElement } from "lit";
|
import { ReactiveElement } from "lit";
|
||||||
import { property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { NavigateOptions } from "../../common/navigate";
|
import type { NavigateOptions } from "../../common/navigate";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import { deepEqual } from "../../common/util/deep-equal";
|
import { deepEqual } from "../../common/util/deep-equal";
|
||||||
@@ -22,6 +22,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@customElement("ha-panel-custom")
|
||||||
export class HaPanelCustom extends ReactiveElement {
|
export class HaPanelCustom extends ReactiveElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@@ -171,5 +172,3 @@ export class HaPanelCustom extends ReactiveElement {
|
|||||||
iframeDoc.close();
|
iframeDoc.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-panel-custom", HaPanelCustom);
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import type { HassService } from "home-assistant-js-websocket";
|
import type { HassService } from "home-assistant-js-websocket";
|
||||||
import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket";
|
import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket";
|
||||||
import { load } from "js-yaml";
|
import { dump, load } from "js-yaml";
|
||||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@@ -137,7 +137,7 @@ class HaPanelDevAction extends LitElement {
|
|||||||
|
|
||||||
const descriptionPlaceholders =
|
const descriptionPlaceholders =
|
||||||
domain && serviceName
|
domain && serviceName
|
||||||
? this.hass.services[domain][serviceName].description_placeholders
|
? this.hass.services[domain]?.[serviceName]?.description_placeholders
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -320,7 +320,10 @@ class HaPanelDevAction extends LitElement {
|
|||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`component.${domain}.services.${serviceName}.fields.${field.key}.example`,
|
`component.${domain}.services.${serviceName}.fields.${field.key}.example`,
|
||||||
descriptionPlaceholders
|
descriptionPlaceholders
|
||||||
) || field.example}
|
) ||
|
||||||
|
(typeof field.example === "object"
|
||||||
|
? html`<pre>${dump(field.example)}</pre>`
|
||||||
|
: field.example)}
|
||||||
</td>
|
</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
)}
|
)}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user