mirror of
https://github.com/home-assistant/frontend.git
synced 2026-11-12 07:54:25 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcf493223a | ||
|
|
8731a5682b | ||
|
|
a7a09af3be | ||
|
|
50678a9039 |
No files matched your search
@@ -28,18 +28,10 @@ AI can be a helpful tool for writing code and documentation. However, due to the
|
||||
|
||||
All contributions must be reviewed and understood by the contributor before submission. You should be able to explain every change in a pull request you submit. Pull requests that appear to be unreviewed AI output will be closed without review.
|
||||
|
||||
## Design contributions
|
||||
|
||||
AI can be a helpful tool to explore ideas and build mockups and prototypes. But AI tends to give the most common answer, not the one that fits Home Assistant and its users. So we require a human who brings the point of view and owns the design decisions.
|
||||
|
||||
You should be able to explain the problem your design solves and why you made your choices. If a mockup or prototype was mostly generated with AI, say so when you share it. Check your work against the [design guidelines](https://contributors.home-assistant.io/design/), as AI tools often make up components, colors, or spacing that don't exist in Home Assistant. Design proposals that look like unreviewed AI output may be closed or hidden.
|
||||
|
||||
## Our use of AI
|
||||
|
||||
Some of our projects use AI tools to assist with code reviews, issue triaging, reporting, and other project management tasks. These tools may leave comments on pull requests or issues. As with any automated tooling, these comments are not always correct.
|
||||
|
||||
We also use AI to help keep the design system, the Figma kit, and the code in sync. The design decisions behind them are made by people.
|
||||
|
||||
If an AI tool leaves a comment on your contribution, treat it as you would any other review comment. If you believe it is incorrect, say so; a brief explanation is sufficient. Maintainers always have the final say. If in doubt, ask a maintainer.
|
||||
|
||||
## Enforcement
|
||||
|
||||
@@ -44,21 +44,14 @@ export const lorawanFixtures: ConnectivityFixtures = {
|
||||
],
|
||||
commands: ["lorawan/"],
|
||||
manifests: [
|
||||
manifest("lorawan", "LoRaWAN", {
|
||||
config_flow: false,
|
||||
integration_type: "service",
|
||||
}),
|
||||
manifest("lorawan", "LoRaWAN", { integration_type: "helper" }),
|
||||
manifest("sensecap_lorawan", "SenseCAP", {
|
||||
integration_type: "device",
|
||||
dependencies: ["lorawan"],
|
||||
}),
|
||||
manifest("chirpstack", "ChirpStack", {
|
||||
integration_type: "hub",
|
||||
dependencies: ["lorawan"],
|
||||
}),
|
||||
manifest("chirpstack", "ChirpStack", { integration_type: "hub" }),
|
||||
manifest("thethingsnetwork_lorawan", "The Things Network", {
|
||||
integration_type: "hub",
|
||||
dependencies: ["lorawan"],
|
||||
}),
|
||||
],
|
||||
configEntries: [
|
||||
|
||||
@@ -10,26 +10,19 @@ export const FRONIUS_ENTRY_ID = "mock-fronius";
|
||||
export const RS485_PORT = "/dev/ttyUSB1";
|
||||
|
||||
export const modbusFixtures: ConnectivityFixtures = {
|
||||
components: ["modbus", "flexit", "solaredge_modbus", "fronius"],
|
||||
components: ["modbus"],
|
||||
commands: ["modbus/"],
|
||||
manifests: [
|
||||
manifest("modbus", "Modbus", {
|
||||
config_flow: false,
|
||||
iot_class: "local_polling",
|
||||
}),
|
||||
manifest("flexit", "Flexit", {
|
||||
integration_type: "device",
|
||||
dependencies: ["modbus"],
|
||||
iot_class: "local_polling",
|
||||
}),
|
||||
manifest("solaredge_modbus", "SolarEdge Modbus", {
|
||||
integration_type: "device",
|
||||
dependencies: ["modbus"],
|
||||
iot_class: "local_polling",
|
||||
}),
|
||||
manifest("fronius", "Fronius", {
|
||||
integration_type: "device",
|
||||
dependencies: ["modbus"],
|
||||
iot_class: "local_polling",
|
||||
}),
|
||||
],
|
||||
|
||||
+8
-8
@@ -77,11 +77,11 @@
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.24",
|
||||
"@tsparticles/engine": "4.5.3",
|
||||
"@tsparticles/preset-links": "4.5.3",
|
||||
"@versatiles/style": "9.0.0",
|
||||
"@tsparticles/engine": "4.5.2",
|
||||
"@tsparticles/preset-links": "4.5.2",
|
||||
"@versatiles/style": "8.1.1",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@vvo/tzdb": "6.204.0",
|
||||
"@vvo/tzdb": "6.202.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"barcode-detector": "3.2.3",
|
||||
"cally": "0.9.2",
|
||||
@@ -108,7 +108,7 @@
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
"lit": "3.3.3",
|
||||
"lit-html": "3.3.3",
|
||||
"maplibre-gl": "6.17.0",
|
||||
"maplibre-gl": "6.16.0",
|
||||
"marked": "18.1.3",
|
||||
"memoize-one": "6.0.0",
|
||||
"nanoid": "6.0.2",
|
||||
@@ -147,7 +147,7 @@
|
||||
"@eslint/js": "10.0.1",
|
||||
"@gfx/zopfli": "1.0.15",
|
||||
"@home-assistant/components": "workspace:*",
|
||||
"@html-eslint/eslint-plugin": "0.68.0",
|
||||
"@html-eslint/eslint-plugin": "0.67.0",
|
||||
"@lokalise/node-api": "17.0.1",
|
||||
"@octokit/auth-oauth-device": "8.0.5",
|
||||
"@octokit/rest": "22.0.1",
|
||||
@@ -180,7 +180,7 @@
|
||||
"eslint": "10.14.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-webpack": "0.13.11",
|
||||
"eslint-plugin-import-x": "4.18.0",
|
||||
"eslint-plugin-import-x": "4.17.1",
|
||||
"eslint-plugin-lit": "2.3.1",
|
||||
"eslint-plugin-lit-a11y": "5.1.3",
|
||||
"eslint-plugin-unused-imports": "4.4.1",
|
||||
@@ -208,7 +208,7 @@
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.9.10",
|
||||
"rspack-manifest-plugin": "5.2.2",
|
||||
"serve": "14.2.7",
|
||||
"serve": "14.2.6",
|
||||
"sinon": "22.1.0",
|
||||
"tar": "7.5.22",
|
||||
"terser-webpack-plugin": "5.6.1",
|
||||
|
||||
Generated
+236
-182
@@ -277,7 +277,7 @@ importers:
|
||||
version: 1.0.3
|
||||
'@maplibre/maplibre-gl-leaflet':
|
||||
specifier: 0.1.4
|
||||
version: 0.1.4(@types/[email protected])([email protected])([email protected]7.0)
|
||||
version: 0.1.4(@types/[email protected])([email protected])([email protected]6.0)
|
||||
'@mdi/js':
|
||||
specifier: 7.4.47
|
||||
version: 7.4.47
|
||||
@@ -291,20 +291,20 @@ importers:
|
||||
specifier: 0.5.24
|
||||
version: 0.5.24
|
||||
'@tsparticles/engine':
|
||||
specifier: 4.5.3
|
||||
version: 4.5.3
|
||||
specifier: 4.5.2
|
||||
version: 4.5.2
|
||||
'@tsparticles/preset-links':
|
||||
specifier: 4.5.3
|
||||
version: 4.5.3
|
||||
specifier: 4.5.2
|
||||
version: 4.5.2
|
||||
'@versatiles/style':
|
||||
specifier: 9.0.0
|
||||
version: 9.0.0([email protected]7.0)
|
||||
specifier: 8.1.1
|
||||
version: 8.1.1([email protected]6.0)
|
||||
'@vibrant/color':
|
||||
specifier: 4.0.4
|
||||
version: 4.0.4
|
||||
'@vvo/tzdb':
|
||||
specifier: 6.204.0
|
||||
version: 6.204.0
|
||||
specifier: 6.202.0
|
||||
version: 6.202.0
|
||||
'@webcomponents/scoped-custom-element-registry':
|
||||
specifier: 0.0.10
|
||||
version: 0.0.10
|
||||
@@ -384,8 +384,8 @@ importers:
|
||||
specifier: 3.3.3
|
||||
version: 3.3.3
|
||||
maplibre-gl:
|
||||
specifier: 6.17.0
|
||||
version: 6.17.0
|
||||
specifier: 6.16.0
|
||||
version: 6.16.0
|
||||
marked:
|
||||
specifier: 18.1.3
|
||||
version: 18.1.3
|
||||
@@ -496,8 +496,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:packages/components
|
||||
'@html-eslint/eslint-plugin':
|
||||
specifier: 0.68.0
|
||||
version: 0.68.0([email protected]([email protected]))
|
||||
specifier: 0.67.0
|
||||
version: 0.67.0([email protected]([email protected]))
|
||||
'@lokalise/node-api':
|
||||
specifier: 17.0.1
|
||||
version: 17.0.1
|
||||
@@ -595,8 +595,8 @@ importers:
|
||||
specifier: 0.13.11
|
||||
version: 0.13.11([email protected])
|
||||
eslint-plugin-import-x:
|
||||
specifier: 4.18.0
|
||||
version: 4.18.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])
|
||||
specifier: 4.17.1
|
||||
version: 4.17.1(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])
|
||||
eslint-plugin-lit:
|
||||
specifier: 2.3.1
|
||||
version: 2.3.1([email protected]([email protected]))
|
||||
@@ -679,8 +679,8 @@ importers:
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2(@rspack/[email protected](@swc/[email protected]))
|
||||
serve:
|
||||
specifier: 14.2.7
|
||||
version: 14.2.7([email protected])
|
||||
specifier: 14.2.6
|
||||
version: 14.2.6([email protected])
|
||||
sinon:
|
||||
specifier: 22.1.0
|
||||
version: 22.1.0
|
||||
@@ -2075,27 +2075,27 @@ packages:
|
||||
resolution: {integrity: sha512-EX1qiX5h3LAoTjuE3rgfuRlNq3Rds5tGrgDGlHoFbxhXaCSTTtuYU6tdR8vgOXKGUDi5gBDrcoNdLYKFXSrknA==}
|
||||
engines: {node: '>=14.17.0'}
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
resolution: {integrity: sha512-u3HioqcKIH6dVQp2uulmpcuUqgKLqiuOWyHAl7hV4hIUkuJ3ge7vEUwPoWvNRbfy3aWOMygttokdGxL5q9YFIg==}
|
||||
'@html-eslint/[email protected]7.0':
|
||||
resolution: {integrity: sha512-MPoKw4woCkuFYs2xdt0vEjzGbUzU8lxeTZkxyrtHM+zZlaK/u/lqcIapZmyCTQBqNE0H9J8zc9TmXjygfHcuXg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
resolution: {integrity: sha512-fiR8UTYBr4+fGftEG1u+tT3AiuOSowvSZbsY4tLcMl9hOuFzquHw4BIaFFlV3xTeqF5Y0M69aPJoEhFJUkQyog==}
|
||||
'@html-eslint/[email protected]7.0':
|
||||
resolution: {integrity: sha512-gr2wKoS1OOZa6uL8DDNCjvPyTu6mWsyQJ/WXmvP/DeFRe6pdumsb8ePYfP7qZW5741MtVd0OpLbxa82XivxxWQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: '>=8.0.0 || ^10.0.0-0'
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
resolution: {integrity: sha512-X22bU2qzEpzPI0bHtnfXzoUS8q/It/DNycJnIdsPzBDXcly+PItV5AJU4nm/+9FZCXSE3IZJvORRCTuvvpV8Bg==}
|
||||
'@html-eslint/[email protected]7.0':
|
||||
resolution: {integrity: sha512-ejdtqLqclQPDfivgsD2yWHXa+FyyEytMlGNNfiHw/UbUeV9tn1hBjIZLJ5VvzjO0IweaAIWIZKlEPgKT6tYP8A==}
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
resolution: {integrity: sha512-zLgikGAF7izvrvUefy5EBEl6WpM0sTN9xzldwp7UgkkjR+zaFvqi9YsOWj+Y7Tu3XMXI0d7Gxv7PPPumVmJTuw==}
|
||||
'@html-eslint/[email protected]7.0':
|
||||
resolution: {integrity: sha512-fvFAuAaKzkg9deV69uT1NoVJSwXTu3mvGDWzlZKe+xwm5JnkbWC626eyDRlAQDzff2/zX7tWFq4EMgOoZmgtRA==}
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
resolution: {integrity: sha512-qbIF3MqmRckg6pXhwY/Ytxnb0eOoq5LPVt9wq8N4UBQHxL2z4cF407FHAiLNhpy8DO4AJUQbI6IZzhN8fHkvPA==}
|
||||
'@html-eslint/[email protected]7.0':
|
||||
resolution: {integrity: sha512-leHUXnpFgRSR3o7rpw5OtOVGfzcChIgrhXDgPlBwdoL7V4+5KZjxGLJAPVwbj7sVVNrSWynlIQ1Wn4FuNKxZhw==}
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
resolution: {integrity: sha512-FW+FjFHp/w1V4uZ0us7keR6mAA+WGh6OsHvJ+Kzy8aBeogyOapDPEMT+zkXSsiJ+V9maJ6umqG20BXcs2y6lsw==}
|
||||
'@html-eslint/[email protected]7.0':
|
||||
resolution: {integrity: sha512-8w9V0sTm2DGLh2uslL7+Un9g/FHNcevgouiTLhlAJoBhhGOK5+fX76QCKdWbgmpudNGo8Nh1kHKsJP3a6twzXA==}
|
||||
|
||||
'@humanfs/[email protected]':
|
||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||
@@ -3270,85 +3270,85 @@ packages:
|
||||
resolution: {integrity: sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-Fz2bTDc6Folav8isOk2scXM+LCSPhQ9hD8xCSmsEGuGH7JkE+XTsS4j6E9H6b+RFlZ1yb2TxdVBJnfXy8XM9Bw==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-bwvu84lMX5Sp79pyKu5yW2SFAqY52B7OlaxDjeeP3TiO7hJLFWxd8VPmSfOarcYK/z/uxp/dZfbcvp/5ylXyiQ==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-f0gHWnqChK4ksE9Bq3YN0iuJVMi2B81YSkCL5BrohuykoaKCLSVVSqjhefmELTMrdiFVsdt6WPJ+HjVQgjt7CA==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-lA+GP2EN5uN3TnOMEFqYBS0yLuQyZ2UlHy72TedgREGeEsrUoXB/FtZKseVt0g9FId4ZI6jXizxEavs65PbUsA==}
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-kbZoUA2uqR2043FHfH2+KFz7JhYTZjyTeDqVL/Z3z38mfdHEVrOJ4q+mwYjr+hX4tq2YoLf1D4DA6w/er1AlKA==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-NaxB1X/90lSu+uI0ayaaNC4okLIwJ93nBWvrdYHQ5/xsyan01qU+rnDjdg8Y9fPewncXlHkOaQezHeG4RjDubg==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-ak8AAPQKinJlwztRAo+OA5ItwjLtymmjayefQCF6K1FIWs5SaWiHWbzMB5vdOvuGUE6OOchqj8NLcu3HfHmdjw==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-JmaqBMZW/OzXGeQBz1g67EwTKeOYccIFrQMnBmiZgavO7LLhglABpyTWJAT9Cb7M/kgx3GIecZGkPxFzQ44pxQ==}
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-NAm9yL8o3oaeYgB7WLzuoD0yfLw9kvEwu0N+UHTwoe+kYRLJvmoUyMlED7hPQHW74VTlSmuZ6gqWTnaOv1mFLA==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-kNwh59Oyi8lvFQYK9z4YgAhS0GcOfFLkIhafTo0qlklqSUl3CvZq0aS14t368oz4Qk/l29pvnLJQg8pmRxIMaw==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/plugin-interactivity': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
'@tsparticles/plugin-interactivity': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-fJJp0xVEeGQDpkjGzvftMbCrLWtwqo+ees49gCnr9SZYVMRbxi3vQYukvi4ULOzYgEW5bTseVprDfiqOwfQJcw==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-7mO9tpJvXkbRIn4OjjUNoiPKAEJkEeazLl8eQB9W9lYpxuNCyZZ+sXJpS2LazyBNKTvGy1P0ekoX/rHqc84vKA==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-k+AkTG8r2UTE3n2u4kmb9dODRVtBc3Yttk7/UQ5HPlcBMr8AnKbx9gyxzzCx2MaEFZG2My0cghJPvQvibrAW7Q==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-gzcPgOzb3vP3p1AF/op5045buNYjsUOHoeTx9WHABrbDDVrQYUo1KsThg6Uo6Uahd8yMIuWUDlzjgF722KBn0w==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-jfyHjPvrc8ORwaiIJ3xgzIveRhlImn21TiblpakeF2Ft5HgxJ+Lb8eIN/2VUJFjdqLpQ3H1nGSJspHYzeU5RtQ==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-/gIqWofLBYLcYcEGt1jxZPD+kgCJOoajS22NxxbVi80MrUxvT586qqbhYiiAesBxRUgCZOJnK39BF2S4A0Ik7Q==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-4w1m8WFL5UJElwTO978P9fOub2vtLCYRLD0YpmXY+AumZw02wLRH6Al7xY2Sgchbgm/uLHkCDzeUTYUi9GzURg==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-uzZGjqxFhz/LUAKnbBYApgelXBrXSJt25fad8TxL5RoDVGgd22pxPErwEtCTAbhJyL6Z5Clj9p5vk9Vve+sodg==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-kyriGuLkOu5uVv3cHRacJRk8XMm9gga7OzCuf3oxbgfqqIaazva4Qw8N19RCpVCW1EfwyWFGq3h5VD/QplkA0w==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-Oh2FPN26M+JlSFvLr7rOJEyTO7n0AUu5x3gHCx4mX9Dqm0JQsL0T17nUMc8pra/nHOemiK3W+dCIm0Y7/8GQiQ==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-lKlI9BaHXy+L30d2CKWWn3B9Ye7LOqRHav8rzXcxAQMfZxj4iK1UuGRT+27/KzQMZBYMPd9G4R7x7lV8RngFdQ==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-y6s2yant8vBdvIw6chTlPVxf9T5lzdmpSyEsI6gX3obTSaJyhw6nbfl6iWcQNx1jjgp4dYVhEf8PneNMqC8VQw==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-U21Aj8pPjxW7wEH1Itm7Ik1rBO5rTXyxKWLjZXEDlT1agXxcwCVbL/5rffUQzbeRFAk/t4KiUGi4rTvAS62wTQ==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-WH8dfjSMWa5uCzheemqy+UzQXIpIymWVrnBOK3L3eBusYFhAg3L0ChBhjKBDjPDlKOquoeRrdt5D8ebgBjlY8g==}
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-jtJAqqnltG12YCy7VpTtIIEOlRNmVDz11SQrZlxFUHWgIYa0UcTEUDeLpP4h9IW7KtnxJKBo/xu1JLRq4tY6fw==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-D6sKXKLPZZ45csOfj3mUQ1bzBgO5mJw7qfpAIYLlk7H+slrHlIoOwxwWgwcuBTY7JJ0jLgiq2062yLHxjEfdeA==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-VaWHjSacuvcDpUPKriQMeG4xzNTKRuK7tZJL0toDQdB4uei2LX5qUBUqTrI0oBWW5Rk0QdUZR83LkhCLVJWtew==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-/mJkwVoFZZl4TtkPmAhdZYuiSjaegJ4zs5l7YJdwKPnN7/6xmvJwrQhj9UGQHwusyUBZ5/V+6cXQFEG7Em154Q==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-gx9cV9LutgY0f25AQtFqW285jGCsHpAxYKbmoN4MOUEYOVX9WiDfrf59Ta9c/SelLvOanVsu5V1hVzBsJyX37w==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-vpSXG73F0lFMsb1uwgut7SiuFNF0Hk5QRD7i8DJO5oyyTrSkrHUjQHFFL7ONb+zmPzLC1J8SlfHXkldJZlpKww==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-I+tPJgq7M5Qcu4pX97J1Hfo0s+bnCmj32IiZ3AJTFFA/+xScaL0SF7T7tW5YtXR45ZwYjaSdnmQ80CwrLxJjdQ==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-6EXelSVOv0LQrAretlDMIxMV/e1+GfJftBfPsHuCTPrTbmcoEIHQRC9VsGQDS/k4ija6oA2GTqTvoni8Ztbk8A==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
resolution: {integrity: sha512-hh62wuc4HZ+BydUC62bA74hgr+qV888iWm23V222d6rqIkOpYzi5tcoPAh48Mgjl2gMSigTE6sySJez7reffNQ==}
|
||||
'@tsparticles/[email protected].2':
|
||||
resolution: {integrity: sha512-XwcyTnRssZ+ssuL500ojFaMdR5w+eb9XVs7MHd2G8q3A5N5a2UAKIhExiTEbWyAGXSgfHo/LQxUTX2Lq1cxSRg==}
|
||||
peerDependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tufjs/[email protected]':
|
||||
resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==}
|
||||
@@ -3785,8 +3785,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@versatiles/style@9.0.0':
|
||||
resolution: {integrity: sha512-hKtVfdQvxLYX7BaGKQhgtcnOHtj+dc1qU7hXL3V4KzMWU+RZtmGI83m5JPxGIB5M/UoDKc3cyXZaf2OeiL5jCA==}
|
||||
'@versatiles/style@8.1.1':
|
||||
resolution: {integrity: sha512-wotbl1VYI0JK94oq7Ji/QTdUi4jHoXtWpBnia/tcFBvAM8Ho4URnRjkNdJ6VbMM/3PqoFEYhUispAhsdCvv5hA==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
maplibre-gl: '>=5.5.0'
|
||||
@@ -3864,8 +3864,8 @@ packages:
|
||||
'@vscode/[email protected]':
|
||||
resolution: {integrity: sha512-2ZUIRfhofZ/npLlf872EBnPmn27Kt4M2UssmQIfnJvgGgMYZJ5fvtHEDnttBBf2hnVtBgNCqZMVHJA+wsFVqTA==}
|
||||
|
||||
'@vvo/[email protected]4.0':
|
||||
resolution: {integrity: sha512-GC7i3D4rCs6l5jnf6NofoRRumZwVHS1cBNhgbenDLLyJNqJOrTo/8zrWovIrJl5BujiFurqn0Ayj10wysmOKrQ==}
|
||||
'@vvo/[email protected]2.0':
|
||||
resolution: {integrity: sha512-MGsedU65ypk/Y7yrp+oOLBzhQj/124xvyoUzx7csPiBSf1b3lgTp5imatuWU4SF6s1Ue3I6GxC+GxE8R5O1l0w==}
|
||||
|
||||
'@web/[email protected]':
|
||||
resolution: {integrity: sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==}
|
||||
@@ -4050,6 +4050,9 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
@@ -4397,9 +4400,6 @@ packages:
|
||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
|
||||
engines: {node: '>= 10.0'}
|
||||
@@ -4420,6 +4420,10 @@ packages:
|
||||
resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==}
|
||||
engines: {node: '>=18.20'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
@@ -4905,9 +4909,6 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4961,8 +4962,8 @@ packages:
|
||||
eslint-plugin-import: '>=1.4.0'
|
||||
webpack: '>=1.11.0'
|
||||
|
||||
[email protected]8.0:
|
||||
resolution: {integrity: sha512-UfxHFmV+f9vwybrNn6LQ1rPzfeDMPGi/dRbqQnCFO/jBzYrujSOyX7jxqBSYr/HwXs/V4XJdEGPvC3Re46nzKg==}
|
||||
[email protected]7.1:
|
||||
resolution: {integrity: sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/utils': ^8.56.0
|
||||
@@ -5066,6 +5067,10 @@ packages:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
|
||||
|
||||
@@ -5283,6 +5288,10 @@ packages:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5489,6 +5498,10 @@ packages:
|
||||
resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5625,6 +5638,11 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5774,6 +5792,10 @@ packages:
|
||||
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
|
||||
@@ -6116,8 +6138,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==}
|
||||
|
||||
[email protected]7.0:
|
||||
resolution: {integrity: sha512-2chIZwpLGenoUQOcnm9Pk4SLbA8ppNFq8jlpXiq9vcYm3Y1wJ3fc82FL0GLwBaPYJCLzPv/mC46KEupVov75rQ==}
|
||||
[email protected]6.0:
|
||||
resolution: {integrity: sha512-gApO66EMtoDZuio680RUBvskR7ahFd/NNhLN4tBAs+Y7FeT4gQV8zVFgYVMRmFxQznOEzecW19w6xOd9XjZjiA==}
|
||||
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
|
||||
|
||||
[email protected]:
|
||||
@@ -6413,6 +6435,10 @@ packages:
|
||||
resolution: {integrity: sha512-vzc1svxw/kw1IRjFsLi6gaxe1Olqm88V0tIfu2u5raL0b1gChe6ZEXNkyUlKxUC7s/egt5NxZHkbY18tMKKLfQ==}
|
||||
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
@@ -7014,8 +7040,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==}
|
||||
|
||||
[email protected].7:
|
||||
resolution: {integrity: sha512-mTJ9LAyBwHhO2T/PNaI0oWxmKl7FFuJ+ZEHKL1rRO69fWV1Y4qcsFB3gpuD2HfHvJCwvJq5ChibjjAGe0wc0vw==}
|
||||
[email protected].6:
|
||||
resolution: {integrity: sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==}
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
@@ -7269,6 +7295,10 @@ packages:
|
||||
resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -7409,10 +7439,6 @@ packages:
|
||||
resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==}
|
||||
engines: {node: ^16.14.0 || >= 17.3.0}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||
|
||||
@@ -9797,40 +9823,40 @@ snapshots:
|
||||
- '@types/node'
|
||||
- '@types/react'
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
'@html-eslint/[email protected]7.0':
|
||||
dependencies:
|
||||
'@html-eslint/types': 0.68.0
|
||||
'@html-eslint/types': 0.67.0
|
||||
html-standard: 0.0.13
|
||||
|
||||
'@html-eslint/[email protected]8.0([email protected]([email protected]))':
|
||||
'@html-eslint/[email protected]7.0([email protected]([email protected]))':
|
||||
dependencies:
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@html-eslint/core': 0.68.0
|
||||
'@html-eslint/parser': 0.68.0
|
||||
'@html-eslint/template-parser': 0.68.0
|
||||
'@html-eslint/template-syntax-parser': 0.68.0
|
||||
'@html-eslint/types': 0.68.0
|
||||
'@html-eslint/core': 0.67.0
|
||||
'@html-eslint/parser': 0.67.0
|
||||
'@html-eslint/template-parser': 0.67.0
|
||||
'@html-eslint/template-syntax-parser': 0.67.0
|
||||
'@html-eslint/types': 0.67.0
|
||||
'@rviscomi/capo.js': 2.2.1
|
||||
eslint: 10.14.0([email protected])
|
||||
html-standard: 0.0.13
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
'@html-eslint/[email protected]7.0':
|
||||
dependencies:
|
||||
'@html-eslint/template-syntax-parser': 0.68.0
|
||||
'@html-eslint/types': 0.68.0
|
||||
'@html-eslint/template-syntax-parser': 0.67.0
|
||||
'@html-eslint/types': 0.67.0
|
||||
css-tree: 3.2.1
|
||||
es-html-parser: 0.3.2
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
'@html-eslint/[email protected]7.0':
|
||||
dependencies:
|
||||
'@html-eslint/types': 0.68.0
|
||||
'@html-eslint/types': 0.67.0
|
||||
es-html-parser: 0.3.2
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
'@html-eslint/[email protected]7.0':
|
||||
dependencies:
|
||||
'@html-eslint/types': 0.68.0
|
||||
'@html-eslint/types': 0.67.0
|
||||
|
||||
'@html-eslint/[email protected]8.0':
|
||||
'@html-eslint/[email protected]7.0':
|
||||
dependencies:
|
||||
'@types/css-tree': 2.3.11
|
||||
'@types/estree': 1.0.9
|
||||
@@ -10080,11 +10106,11 @@ snapshots:
|
||||
dependencies:
|
||||
kdbush: 4.1.0
|
||||
|
||||
'@maplibre/[email protected](@types/[email protected])([email protected])([email protected]7.0)':
|
||||
'@maplibre/[email protected](@types/[email protected])([email protected])([email protected]6.0)':
|
||||
dependencies:
|
||||
'@types/leaflet': 1.9.22
|
||||
leaflet: 1.9.4
|
||||
maplibre-gl: 6.17.0
|
||||
maplibre-gl: 6.16.0
|
||||
|
||||
'@maplibre/[email protected]':
|
||||
dependencies:
|
||||
@@ -11092,88 +11118,88 @@ snapshots:
|
||||
magic-string: 0.30.21
|
||||
string.prototype.matchall: 4.0.12
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
'@tsparticles/[email protected].2':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/plugin-blend': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/plugin-hex-color': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/plugin-hsl-color': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/plugin-move': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/plugin-rgb-color': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/shape-circle': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/updater-opacity': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/updater-out-modes': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/updater-paint': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/updater-size': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/engine': 4.5.2
|
||||
'@tsparticles/plugin-blend': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/plugin-hex-color': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/plugin-hsl-color': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/plugin-move': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/plugin-rgb-color': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/shape-circle': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/updater-opacity': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/updater-out-modes': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/updater-paint': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/updater-size': 4.5.2(@tsparticles/[email protected].2)
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3': {}
|
||||
'@tsparticles/[email protected].2': {}
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)(@tsparticles/[email protected].3(@tsparticles/[email protected].3))':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)(@tsparticles/[email protected].2(@tsparticles/[email protected].2))':
|
||||
dependencies:
|
||||
'@tsparticles/canvas-utils': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/plugin-interactivity': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/canvas-utils': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/engine': 4.5.2
|
||||
'@tsparticles/plugin-interactivity': 4.5.2(@tsparticles/[email protected].2)
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3':
|
||||
'@tsparticles/[email protected].2':
|
||||
dependencies:
|
||||
'@tsparticles/basic': 4.5.3
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/interaction-particles-links': 4.5.3(@tsparticles/[email protected].3)(@tsparticles/[email protected].3(@tsparticles/[email protected].3))
|
||||
'@tsparticles/plugin-interactivity': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/basic': 4.5.2
|
||||
'@tsparticles/engine': 4.5.2
|
||||
'@tsparticles/interaction-particles-links': 4.5.2(@tsparticles/[email protected].2)(@tsparticles/[email protected].2(@tsparticles/[email protected].2))
|
||||
'@tsparticles/plugin-interactivity': 4.5.2(@tsparticles/[email protected].2)
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/animation-utils': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/animation-utils': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tsparticles/[email protected].3(@tsparticles/[email protected].3)':
|
||||
'@tsparticles/[email protected].2(@tsparticles/[email protected].2)':
|
||||
dependencies:
|
||||
'@tsparticles/animation-utils': 4.5.3(@tsparticles/[email protected].3)
|
||||
'@tsparticles/engine': 4.5.3
|
||||
'@tsparticles/animation-utils': 4.5.2(@tsparticles/[email protected].2)
|
||||
'@tsparticles/engine': 4.5.2
|
||||
|
||||
'@tufjs/[email protected]': {}
|
||||
|
||||
@@ -11527,11 +11553,11 @@ snapshots:
|
||||
'@unrs/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@versatiles/style@9.0.0([email protected]7.0)':
|
||||
'@versatiles/style@8.1.1([email protected]6.0)':
|
||||
dependencies:
|
||||
'@maplibre/maplibre-gl-style-spec': 26.5.1
|
||||
optionalDependencies:
|
||||
maplibre-gl: 6.17.0
|
||||
maplibre-gl: 6.16.0
|
||||
|
||||
'@vibrant/[email protected]': {}
|
||||
|
||||
@@ -11621,7 +11647,7 @@ snapshots:
|
||||
|
||||
'@vscode/[email protected]': {}
|
||||
|
||||
'@vvo/[email protected]4.0': {}
|
||||
'@vvo/[email protected]2.0': {}
|
||||
|
||||
'@web/[email protected]':
|
||||
dependencies:
|
||||
@@ -11760,6 +11786,8 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.2
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -12137,8 +12165,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
source-map: 0.6.1
|
||||
@@ -12153,6 +12179,12 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
arch: 2.2.0
|
||||
execa: 5.1.1
|
||||
is-wsl: 2.2.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
@@ -12661,8 +12693,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -12721,17 +12751,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected]8.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected]):
|
||||
[email protected]7.1(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.72.1
|
||||
cjs-module-lexer: 2.3.0
|
||||
comment-parser: 1.4.8
|
||||
debug: 4.4.3([email protected])
|
||||
es-module-lexer: 3.0.4
|
||||
eslint: 10.14.0([email protected])
|
||||
eslint-import-context: 0.1.9([email protected])
|
||||
is-glob: 4.0.3
|
||||
minimatch: 11.0.5
|
||||
minimatch: 10.2.6
|
||||
semver: 7.8.5
|
||||
stable-hash-x: 0.2.0
|
||||
unrs-resolver: 1.12.2
|
||||
@@ -12856,6 +12884,18 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
get-stream: 6.0.1
|
||||
human-signals: 2.1.0
|
||||
is-stream: 2.0.1
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 4.0.1
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
@@ -13105,6 +13145,8 @@ snapshots:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -13377,6 +13419,8 @@ snapshots:
|
||||
- kerberos
|
||||
- supports-color
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
@@ -13506,6 +13550,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -13628,6 +13674,10 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -13994,7 +14044,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]7.0:
|
||||
[email protected]6.0:
|
||||
dependencies:
|
||||
'@mapbox/point-geometry': 1.1.0
|
||||
'@mapbox/tiny-sdf': 2.3.0
|
||||
@@ -14318,6 +14368,10 @@ snapshots:
|
||||
- kerberos
|
||||
- supports-color
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
@@ -14988,7 +15042,7 @@ snapshots:
|
||||
path-to-regexp: 3.3.0
|
||||
range-parser: 1.2.0
|
||||
|
||||
[email protected].7([email protected]):
|
||||
[email protected].6([email protected]):
|
||||
dependencies:
|
||||
'@zeit/schemas': 2.36.0
|
||||
ajv: 8.18.0
|
||||
@@ -14996,10 +15050,10 @@ snapshots:
|
||||
boxen: 7.0.0
|
||||
chalk: 5.0.1
|
||||
chalk-template: 0.4.0
|
||||
clipboardy: 3.0.0
|
||||
compression: 1.8.1([email protected])
|
||||
is-port-reachable: 4.0.0
|
||||
serve-handler: 6.1.7
|
||||
tinyclip: 0.1.15
|
||||
update-check: 1.5.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -15343,6 +15397,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
@@ -15458,8 +15514,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/** Where a value is in a config, e.g. `["actions", 0, "then", 1]`. */
|
||||
export type ConfigPath = (string | number)[];
|
||||
|
||||
export const stringifyPath = (path: ConfigPath): string => path.join("/");
|
||||
|
||||
export const pathEquals = (a: ConfigPath, b: ConfigPath): boolean =>
|
||||
a.length === b.length && a.every((segment, index) => segment === b[index]);
|
||||
@@ -401,13 +401,9 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
|
||||
if (properties.has("data")) {
|
||||
// Clean up checked rows that no longer exist or can no longer be selected
|
||||
// Clean up checked rows that no longer exist in the data
|
||||
if (this._checkedRows.length) {
|
||||
const validIds = new Set(
|
||||
this.data
|
||||
.filter((row) => row.selectable !== false)
|
||||
.map((row) => String(row[this.id]))
|
||||
);
|
||||
const validIds = new Set(this.data.map((row) => String(row[this.id])));
|
||||
const validCheckedRows = this._checkedRows.filter((id) =>
|
||||
validIds.has(id)
|
||||
);
|
||||
|
||||
@@ -97,8 +97,6 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
|
||||
@state() private _dateValue?: string;
|
||||
|
||||
@state() private _rangeIncomplete = false;
|
||||
|
||||
@state() private _timeValue = {
|
||||
from: { hours: 0, minutes: 0 },
|
||||
to: { hours: 23, minutes: 59 },
|
||||
@@ -197,7 +195,6 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
.locale=${this._i18n.locale.language}
|
||||
.focusedDate=${this._focusDate}
|
||||
@focusday=${this._focusChanged}
|
||||
@rangestart=${this._handleRangeStart}
|
||||
@change=${this._handleChange}
|
||||
show-outside-days
|
||||
.firstDayOfWeek=${firstWeekdayIndex(this._i18n.locale)}
|
||||
@@ -256,9 +253,7 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
<ha-button appearance="plain" @click=${this._cancel}
|
||||
>${this._i18n.localize("ui.common.cancel")}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
.disabled=${!this._dateValue || this._rangeIncomplete}
|
||||
@click=${this._save}
|
||||
<ha-button .disabled=${!this._dateValue} @click=${this._save}
|
||||
>${this._i18n.localize(
|
||||
"ui.components.date-range-picker.select"
|
||||
)}</ha-button
|
||||
@@ -285,7 +280,7 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _save() {
|
||||
if (!this._dateValue || this._rangeIncomplete) {
|
||||
if (!this._dateValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -336,21 +331,9 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
this._focusDate = ev.target.focusedDate;
|
||||
}
|
||||
|
||||
private _handleRangeStart(
|
||||
ev: HASSDomTargetEvent<HTMLElementTagNameMap["calendar-range"]>
|
||||
) {
|
||||
this._rangeIncomplete = matchMedia("(hover: hover)").matches;
|
||||
if (this._rangeIncomplete) {
|
||||
return;
|
||||
}
|
||||
const day = ev.target.tentative;
|
||||
this._dateValue = `${day}/${day}`;
|
||||
}
|
||||
|
||||
private _handleChange(
|
||||
ev: HASSDomTargetEvent<HTMLElementTagNameMap["calendar-range"]>
|
||||
) {
|
||||
this._rangeIncomplete = false;
|
||||
const dateElement = ev.target as HTMLElementTagNameMap["calendar-range"];
|
||||
this._dateValue = dateElement.value;
|
||||
this._focusDate = dateElement.focusedDate;
|
||||
|
||||
@@ -62,6 +62,10 @@ export class HaControlSelect extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
public override focus(): void {
|
||||
this._focusOption(this._tabbableIndex);
|
||||
}
|
||||
|
||||
private _handleBlur(ev: FocusEvent) {
|
||||
// Only reset if focus is leaving the entire component
|
||||
if (!this.contains(ev.relatedTarget as Node)) {
|
||||
|
||||
@@ -219,7 +219,6 @@ export class HaControlSlider extends LitElement {
|
||||
this.pressed = false;
|
||||
this._hideTooltip();
|
||||
this.value = savedValue;
|
||||
fireEvent(this, "slider-moved", { value: undefined });
|
||||
});
|
||||
this._mc.on("panmove", (e) => {
|
||||
if (this.disabled) return;
|
||||
|
||||
@@ -214,19 +214,15 @@ class HaHsColorPicker extends LitElement {
|
||||
this._mc.add(new Tap({ event: "singletap" }));
|
||||
|
||||
let savedPosition;
|
||||
let savedValue: [number, number] | undefined;
|
||||
this._mc.on("panstart", (e) => {
|
||||
if (this.disabled) return;
|
||||
this._pressed = e.pointerType;
|
||||
savedPosition = this._cursorPosition;
|
||||
savedValue = this._localValue;
|
||||
});
|
||||
this._mc.on("pancancel", () => {
|
||||
if (this.disabled) return;
|
||||
this._pressed = undefined;
|
||||
this._cursorPosition = savedPosition;
|
||||
this._localValue = savedValue;
|
||||
fireEvent(this, "cursor-moved", { value: undefined });
|
||||
});
|
||||
this._mc.on("panmove", (e) => {
|
||||
if (this.disabled) return;
|
||||
|
||||
@@ -21,8 +21,6 @@ export class HaActionSelector extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public value?: Action;
|
||||
|
||||
@property() public name?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
@@ -53,7 +51,6 @@ export class HaActionSelector extends LitElement {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-automation-action
|
||||
.pathKey=${this.name}
|
||||
.disabled=${this.disabled}
|
||||
.actions=${this._actions(this.value)}
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -16,8 +16,6 @@ export class HaConditionSelector extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public value?: Condition;
|
||||
|
||||
@property() public name?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
@@ -29,7 +27,6 @@ export class HaConditionSelector extends LitElement {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-automation-condition
|
||||
.pathKey=${this.name}
|
||||
.disabled=${this.disabled}
|
||||
.conditions=${this.value || []}
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -17,8 +17,6 @@ export class HaTriggerSelector extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public value?: Trigger;
|
||||
|
||||
@property() public name?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
@@ -34,7 +32,6 @@ export class HaTriggerSelector extends LitElement {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-automation-trigger
|
||||
.pathKey=${this.name}
|
||||
.disabled=${this.disabled}
|
||||
.triggers=${this._triggers(this.value)}
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { ConfigEntry } from "./config_entries";
|
||||
import type { DataEntryFlowProgress } from "./data_entry_flow";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
import type { IntegrationSetup } from "./integration";
|
||||
import type { RepairsIssue } from "./repairs";
|
||||
import type { LoggedError } from "./system_log";
|
||||
import { getLoggedErrorIntegration } from "./system_log";
|
||||
|
||||
export type HealthStatus = "ok" | "warning" | "error" | "info";
|
||||
|
||||
export type OverallHealthStatus = Exclude<HealthStatus, "info">;
|
||||
|
||||
export interface ConfigEntryHealth {
|
||||
status: OverallHealthStatus;
|
||||
state: HealthStatus;
|
||||
entities: {
|
||||
status: HealthStatus;
|
||||
total: number;
|
||||
unavailable: EntityRegistryEntry[];
|
||||
};
|
||||
repairs: { status: HealthStatus; issues: RepairsIssue[] };
|
||||
logs: { status: HealthStatus; errors: number; warnings: number };
|
||||
flows: { status: HealthStatus; flows: DataEntryFlowProgress[] };
|
||||
setupSeconds?: number;
|
||||
}
|
||||
|
||||
const ATTENTION_SOURCES = ["reauth", "reconfigure"];
|
||||
|
||||
const STATE_STATUS: Record<ConfigEntry["state"], HealthStatus> = {
|
||||
loaded: "ok",
|
||||
setup_in_progress: "info",
|
||||
unload_in_progress: "info",
|
||||
not_loaded: "info",
|
||||
setup_retry: "warning",
|
||||
setup_error: "error",
|
||||
migration_error: "error",
|
||||
failed_unload: "error",
|
||||
};
|
||||
|
||||
const worst = (statuses: HealthStatus[]): OverallHealthStatus =>
|
||||
statuses.includes("error")
|
||||
? "error"
|
||||
: statuses.includes("warning")
|
||||
? "warning"
|
||||
: "ok";
|
||||
|
||||
export const computeConfigEntryHealth = (
|
||||
entry: ConfigEntry,
|
||||
entities: EntityRegistryEntry[],
|
||||
states: HassEntities,
|
||||
issues: RepairsIssue[],
|
||||
log: LoggedError[],
|
||||
flows: DataEntryFlowProgress[],
|
||||
setups: IntegrationSetup[],
|
||||
// Library loggers from the manifest, so dependency errors count too.
|
||||
loggers: string[] = []
|
||||
): ConfigEntryHealth => {
|
||||
const enabled = entities.filter((entity) => !entity.disabled_by);
|
||||
// Entities without a state yet (e.g. while the entry is not loaded) count as unavailable.
|
||||
const unavailable = enabled.filter(
|
||||
(entity) =>
|
||||
!states[entity.entity_id] ||
|
||||
states[entity.entity_id].state === "unavailable"
|
||||
);
|
||||
const entryIssues = issues.filter(
|
||||
(issue) =>
|
||||
!issue.ignored && (issue.issue_domain ?? issue.domain) === entry.domain
|
||||
);
|
||||
const entryLog = log.filter(
|
||||
(item) =>
|
||||
getLoggedErrorIntegration(item) === entry.domain ||
|
||||
loggers.some(
|
||||
(logger) => item.name === logger || item.name.startsWith(`${logger}.`)
|
||||
)
|
||||
);
|
||||
const sumCount = (levels: LoggedError["level"][]) =>
|
||||
entryLog
|
||||
.filter((item) => levels.includes(item.level))
|
||||
.reduce((total, item) => total + item.count, 0);
|
||||
const errors = sumCount(["error", "critical"]);
|
||||
const warnings = sumCount(["warning"]);
|
||||
const entryFlows = flows.filter(
|
||||
(flow) =>
|
||||
flow.handler === entry.domain &&
|
||||
flow.context.entry_id === entry.entry_id &&
|
||||
ATTENTION_SOURCES.includes(flow.context.source)
|
||||
);
|
||||
|
||||
// A disabled entry that failed to unload still needs a restart.
|
||||
const state: HealthStatus =
|
||||
entry.disabled_by && entry.state !== "failed_unload"
|
||||
? "info"
|
||||
: STATE_STATUS[entry.state];
|
||||
const health: ConfigEntryHealth = {
|
||||
status: "ok",
|
||||
state,
|
||||
entities: {
|
||||
status: entry.disabled_by || !unavailable.length ? "ok" : "warning",
|
||||
total: enabled.length,
|
||||
unavailable,
|
||||
},
|
||||
repairs: {
|
||||
status: entryIssues.some((issue) => issue.severity !== "warning")
|
||||
? "error"
|
||||
: entryIssues.length
|
||||
? "warning"
|
||||
: "ok",
|
||||
issues: entryIssues,
|
||||
},
|
||||
logs: {
|
||||
status: errors ? "error" : warnings ? "warning" : "ok",
|
||||
errors,
|
||||
warnings,
|
||||
},
|
||||
flows: { status: entryFlows.length ? "error" : "ok", flows: entryFlows },
|
||||
setupSeconds: setups.find((setup) => setup.domain === entry.domain)
|
||||
?.seconds,
|
||||
};
|
||||
health.status = worst([
|
||||
state,
|
||||
health.entities.status,
|
||||
health.repairs.status,
|
||||
health.logs.status,
|
||||
health.flows.status,
|
||||
]);
|
||||
return health;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type {
|
||||
DataEntryFlowProgress,
|
||||
DataEntryFlowStep,
|
||||
@@ -78,10 +78,10 @@ export const deleteConfigFlow = (
|
||||
|
||||
// The backend indexes a single key, and only these two exist.
|
||||
export const getConfigFlowHandlers = (
|
||||
callApi: HomeAssistantApi["callApi"],
|
||||
hass: HomeAssistant,
|
||||
type?: "helper" | "integration"
|
||||
) =>
|
||||
callApi<string[]>(
|
||||
hass.callApi<string[]>(
|
||||
"GET",
|
||||
`config/config_entries/flow_handlers${type ? `?type=${type}` : ""}`
|
||||
);
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface Counter {
|
||||
id: string;
|
||||
@@ -25,10 +25,10 @@ export const fetchCounter = (hass: HomeAssistant) =>
|
||||
hass.callWS<Counter[]>({ type: "counter/list" });
|
||||
|
||||
export const createCounter = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: CounterMutableParams
|
||||
) =>
|
||||
callWS<Counter>({
|
||||
hass.callWS<Counter>({
|
||||
type: "counter/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../../types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { ConfigEntry } from "../config_entries";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
@@ -231,11 +231,11 @@ export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) =>
|
||||
devices.filter((device) => device.area_id === areaId);
|
||||
|
||||
export const updateDeviceRegistryEntry = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
deviceId: string,
|
||||
updates: Partial<DeviceRegistryEntryMutableParams>
|
||||
) =>
|
||||
callWS<DeviceRegistryEntry>({
|
||||
hass.callWS<DeviceRegistryEntry>({
|
||||
type: "config/device_registry/update",
|
||||
device_id: deviceId,
|
||||
...updates,
|
||||
|
||||
@@ -5,7 +5,7 @@ import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../../types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { LightColor } from "../light";
|
||||
import type { NextNamePart, RegistryEntry } from "../registry";
|
||||
import type { Segment } from "../vacuum";
|
||||
@@ -284,11 +284,11 @@ export const getExtendedEntityRegistryEntries = (
|
||||
});
|
||||
|
||||
export const updateEntityRegistryEntry = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: Pick<HomeAssistant, "callWS">,
|
||||
entityId: string,
|
||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
): Promise<UpdateEntityRegistryEntryResult> =>
|
||||
callWS({
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/update",
|
||||
entity_id: entityId,
|
||||
...updates,
|
||||
@@ -388,11 +388,11 @@ export interface AutomaticEntityIdOverrides {
|
||||
}
|
||||
|
||||
export const getAutomaticEntityIds = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
entity_ids: string[],
|
||||
overrides?: Record<string, AutomaticEntityIdOverrides>
|
||||
) =>
|
||||
callWS<Record<string, string | null>>({
|
||||
hass.callWS<Record<string, string | null>>({
|
||||
type: "config/entity_registry/get_automatic_entity_ids",
|
||||
entity_ids,
|
||||
...(overrides && { overrides }),
|
||||
@@ -403,22 +403,22 @@ export const getAutomaticEntityIds = (
|
||||
* the area. Returns the entity ID of each entity after the change.
|
||||
*/
|
||||
export const updateEntitiesArea = async (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
entityIds: string[],
|
||||
areaId: string
|
||||
): Promise<Record<string, string>> => {
|
||||
await Promise.all(
|
||||
entityIds.map((entityId) =>
|
||||
updateEntityRegistryEntry(callWS, entityId, { area_id: areaId })
|
||||
updateEntityRegistryEntry(hass, entityId, { area_id: areaId })
|
||||
)
|
||||
);
|
||||
const newEntityIds = await getAutomaticEntityIds(callWS, entityIds);
|
||||
const newEntityIds = await getAutomaticEntityIds(hass, entityIds);
|
||||
const result: Record<string, string> = {};
|
||||
for (const entityId of entityIds) {
|
||||
const newEntityId = newEntityIds[entityId];
|
||||
if (newEntityId) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await updateEntityRegistryEntry(callWS, entityId, {
|
||||
await updateEntityRegistryEntry(hass, entityId, {
|
||||
new_entity_id: newEntityId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,12 +49,20 @@ export interface SecurityAlertEntityConfig {
|
||||
export interface SecurityFrontendSystemData {
|
||||
alert_entities?: SecurityAlertEntityConfig[];
|
||||
favorite_entities?: string[];
|
||||
hidden_entities?: string[];
|
||||
}
|
||||
|
||||
export interface MaintenanceFrontendSystemData {
|
||||
hidden_entities?: string[];
|
||||
}
|
||||
|
||||
export interface ClimateFrontendSystemData {
|
||||
hidden_entities?: string[];
|
||||
}
|
||||
|
||||
export type HomeSummaryPanelSystemDataKey =
|
||||
"security" | "maintenance" | "climate";
|
||||
|
||||
export interface EnergyFrontendSystemData {
|
||||
// Stable "<view>.<card-type>" keys of energy dashboard cards the user has
|
||||
// hidden. An absent key or array means nothing is hidden (all cards visible),
|
||||
@@ -74,6 +82,7 @@ declare global {
|
||||
energy: EnergyFrontendSystemData;
|
||||
security: SecurityFrontendSystemData;
|
||||
maintenance: MaintenanceFrontendSystemData;
|
||||
climate: ClimateFrontendSystemData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputBoolean {
|
||||
id: string;
|
||||
@@ -17,10 +17,10 @@ export const fetchInputBoolean = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputBoolean[]>({ type: "input_boolean/list" });
|
||||
|
||||
export const createInputBoolean = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: InputBooleanMutableParams
|
||||
) =>
|
||||
callWS<InputBoolean>({
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputButton {
|
||||
id: string;
|
||||
@@ -15,10 +15,10 @@ export const fetchInputButton = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputButton[]>({ type: "input_button/list" });
|
||||
|
||||
export const createInputButton = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: InputButtonMutableParams
|
||||
) =>
|
||||
callWS<InputButton>({
|
||||
hass.callWS<InputButton>({
|
||||
type: "input_button/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -45,10 +45,10 @@ export const fetchInputDateTime = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputDateTime[]>({ type: "input_datetime/list" });
|
||||
|
||||
export const createInputDateTime = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: InputDateTimeMutableParams
|
||||
) =>
|
||||
callWS<InputDateTime>({
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputNumber {
|
||||
id: string;
|
||||
@@ -29,10 +29,10 @@ export const fetchInputNumber = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputNumber[]>({ type: "input_number/list" });
|
||||
|
||||
export const createInputNumber = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: InputNumberMutableParams
|
||||
) =>
|
||||
callWS<InputNumber>({
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
interface InputSelectEntityAttributes extends HassEntityAttributeBase {
|
||||
options: string[];
|
||||
@@ -41,10 +41,10 @@ export const fetchInputSelect = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputSelect[]>({ type: "input_select/list" });
|
||||
|
||||
export const createInputSelect = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: InputSelectMutableParams
|
||||
) =>
|
||||
callWS<InputSelect>({
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputText {
|
||||
id: string;
|
||||
@@ -36,10 +36,10 @@ export const fetchInputText = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputText[]>({ type: "input_text/list" });
|
||||
|
||||
export const createInputText = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: InputTextMutableParams
|
||||
) =>
|
||||
callWS<InputText>({
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -196,8 +196,8 @@ export const fetchIntegrationManifest = (
|
||||
.callWS<IntegrationManifest>({ type: "manifest/get", integration })
|
||||
.then(sanitizeManifest);
|
||||
|
||||
export const fetchIntegrationSetups = (callWS: HomeAssistant["callWS"]) =>
|
||||
callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
|
||||
export const fetchIntegrationSetups = (hass: HomeAssistant) =>
|
||||
hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
|
||||
|
||||
export const fetchIntegrationLogInfo = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Connection, HassEventBase } from "home-assistant-js-websocket";
|
||||
import { getCollection } from "home-assistant-js-websocket";
|
||||
import type { ConfigPath } from "../common/util/config-path";
|
||||
import type { HuiBadge } from "../panels/lovelace/badges/hui-badge";
|
||||
import type { HuiCard } from "../panels/lovelace/cards/hui-card";
|
||||
import type { HuiSection } from "../panels/lovelace/sections/hui-section";
|
||||
import type { LovelacePath } from "../panels/lovelace/editor/lovelace-path";
|
||||
import type { Lovelace } from "../panels/lovelace/types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { LovelaceSectionConfig } from "./lovelace/config/section";
|
||||
@@ -21,7 +21,7 @@ export interface LovelaceViewElement extends HTMLElement {
|
||||
narrow?: boolean;
|
||||
// Temporary compatibility: custom view layouts still read the view index
|
||||
index?: number;
|
||||
path?: ConfigPath;
|
||||
path?: LovelacePath;
|
||||
cards?: HuiCard[];
|
||||
badges?: HuiBadge[];
|
||||
sections?: HuiSection[];
|
||||
@@ -34,7 +34,7 @@ export interface LovelaceSectionElement extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
lovelace?: Lovelace;
|
||||
preview?: boolean;
|
||||
path?: ConfigPath;
|
||||
path?: LovelacePath;
|
||||
cards?: HuiCard[];
|
||||
badges?: HuiBadge[];
|
||||
sections?: HuiSection[];
|
||||
|
||||
+2
-3
@@ -21,9 +21,8 @@ export const findOTBRInfoForDataset = (
|
||||
}
|
||||
|
||||
const preferredAddress = dataset.preferred_extended_address;
|
||||
const preferred = preferredAddress ? otbrInfo[preferredAddress] : undefined;
|
||||
if (preferred?.extended_pan_id === dataset.extended_pan_id) {
|
||||
return preferred;
|
||||
if (preferredAddress && otbrInfo[preferredAddress]) {
|
||||
return otbrInfo[preferredAddress];
|
||||
}
|
||||
|
||||
return Object.values(otbrInfo).find(
|
||||
|
||||
@@ -15,7 +15,7 @@ export const regenerateEntityIds = async (
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
): Promise<void> => {
|
||||
const entityIdsMapping = await getAutomaticEntityIds(hass.callWS, entities);
|
||||
const entityIdsMapping = await getAutomaticEntityIds(hass, entities);
|
||||
|
||||
const entityIdsEntries = Object.entries(entityIdsMapping);
|
||||
|
||||
@@ -95,7 +95,7 @@ export const regenerateEntityIds = async (
|
||||
entityIdsEntries
|
||||
.filter(([oldId, newId]) => newId && oldId !== newId)
|
||||
.forEach(([oldEntityId, newEntityId]) =>
|
||||
updateEntityRegistryEntry(hass.callWS, oldEntityId, {
|
||||
updateEntityRegistryEntry(hass, oldEntityId, {
|
||||
new_entity_id: newEntityId!,
|
||||
}).catch((err: any) => {
|
||||
showAlertDialog(element, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const weekdays = [
|
||||
"sunday",
|
||||
@@ -33,10 +33,10 @@ export const fetchSchedule = (hass: HomeAssistant) =>
|
||||
hass.callWS<Schedule[]>({ type: "schedule/list" });
|
||||
|
||||
export const createSchedule = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
values: ScheduleMutableParams
|
||||
) =>
|
||||
callWS<Schedule>({
|
||||
hass.callWS<Schedule>({
|
||||
type: "schedule/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -23,8 +23,8 @@ export interface LoggedError {
|
||||
first_occurred: number;
|
||||
}
|
||||
|
||||
export const fetchSystemLog = async (callWS: HomeAssistant["callWS"]) => {
|
||||
const log = await callWS<LoggedError[]>({ type: "system_log/list" });
|
||||
export const fetchSystemLog = async (hass: HomeAssistant) => {
|
||||
const log = await hass.callWS<LoggedError[]>({ type: "system_log/list" });
|
||||
for (const error of log) {
|
||||
error.level = error.level.toLowerCase() as LoggedError["level"];
|
||||
}
|
||||
|
||||
+3
-6
@@ -11,7 +11,7 @@ import secondsToDuration from "../common/datetime/seconds_to_duration";
|
||||
import { formatNumericDuration } from "../common/datetime/format_duration";
|
||||
import type { FormatEntityStateFunc } from "../common/translations/entity-state";
|
||||
import type { HaDurationData } from "../components/ha-duration-input";
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
|
||||
export type TimerEntity = HassEntityBase & {
|
||||
@@ -48,11 +48,8 @@ export interface TimerMutableParams {
|
||||
export const fetchTimer = (hass: HomeAssistant) =>
|
||||
hass.callWS<Timer[]>({ type: "timer/list" });
|
||||
|
||||
export const createTimer = (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
values: TimerMutableParams
|
||||
) =>
|
||||
callWS<Timer>({
|
||||
export const createTimer = (hass: HomeAssistant, values: TimerMutableParams) =>
|
||||
hass.callWS<Timer>({
|
||||
type: "timer/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
@@ -303,7 +303,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
if (Object.keys(this._deviceUpdate).length) {
|
||||
const deviceUpdates = Object.entries(this._deviceUpdate).map(
|
||||
([deviceId, update]) =>
|
||||
updateDeviceRegistryEntry(this.hass.callWS, deviceId, {
|
||||
updateDeviceRegistryEntry(this.hass, deviceId, {
|
||||
name_by_user: update.name,
|
||||
area_id: update.area,
|
||||
}).catch((err: unknown) => {
|
||||
@@ -333,14 +333,14 @@ class StepFlowCreateEntry extends LitElement {
|
||||
});
|
||||
|
||||
const entityIdsMapping = await getAutomaticEntityIds(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
entityIds
|
||||
);
|
||||
|
||||
Object.entries(entityIdsMapping).forEach(([oldEntityId, newEntityId]) => {
|
||||
if (newEntityId) {
|
||||
entityUpdates.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, oldEntityId, {
|
||||
updateEntityRegistryEntry(this.hass, oldEntityId, {
|
||||
new_entity_id: newEntityId,
|
||||
}).catch((err) =>
|
||||
showAlertDialog(this, {
|
||||
@@ -423,7 +423,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
): Promise<ExtEntityRegistryEntry[]> {
|
||||
try {
|
||||
const newEntityIds = await updateEntitiesArea(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
this._entitiesWithoutDevice(entities).map((entity) => entity.entity_id),
|
||||
areaId
|
||||
);
|
||||
|
||||
@@ -146,7 +146,7 @@ export class HaMoreInfoCoverFavoritePositions extends LitElement {
|
||||
}
|
||||
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this._api.callWS,
|
||||
this._api,
|
||||
this.entry.entity_id,
|
||||
{
|
||||
options_domain: "cover",
|
||||
|
||||
@@ -75,7 +75,7 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
|
||||
private async _save(newFavoriteColors: LightColor[]): Promise<void> {
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this._api.callWS,
|
||||
this._api,
|
||||
this.entry!.entity_id,
|
||||
{
|
||||
options_domain: "light",
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { PendingValueSync } from "../../../../common/util/pending-value-sync";
|
||||
import { throttle } from "../../../../common/util/throttle";
|
||||
import "../../../../components/ha-hs-color-picker";
|
||||
import "../../../../components/ha-icon";
|
||||
@@ -60,8 +59,6 @@ class LightRgbColorPicker extends LitElement {
|
||||
|
||||
@state() private _isInteracting?: boolean;
|
||||
|
||||
private _pendingSync = new PendingValueSync();
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj) {
|
||||
return nothing;
|
||||
@@ -184,29 +181,23 @@ class LightRgbColorPicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _updateBrightnessAdjusted() {
|
||||
const stateObj = this.stateObj;
|
||||
|
||||
this._brightnessAdjusted = undefined;
|
||||
if (
|
||||
stateObj.attributes.color_mode === LightColorMode.RGB &&
|
||||
stateObj.attributes.rgb_color &&
|
||||
!lightSupportsColorMode(stateObj, LightColorMode.RGBWW) &&
|
||||
!lightSupportsColorMode(stateObj, LightColorMode.RGBW)
|
||||
) {
|
||||
const maxVal = Math.max(...stateObj.attributes.rgb_color);
|
||||
|
||||
if (maxVal < 255) {
|
||||
this._brightnessAdjusted = maxVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSliderValues() {
|
||||
const stateObj = this.stateObj;
|
||||
|
||||
if (stateObj.state === "on") {
|
||||
this._updateBrightnessAdjusted();
|
||||
this._brightnessAdjusted = undefined;
|
||||
if (
|
||||
stateObj.attributes.color_mode === LightColorMode.RGB &&
|
||||
stateObj.attributes.rgb_color &&
|
||||
!lightSupportsColorMode(stateObj, LightColorMode.RGBWW) &&
|
||||
!lightSupportsColorMode(stateObj, LightColorMode.RGBW)
|
||||
) {
|
||||
const maxVal = Math.max(...stateObj.attributes.rgb_color);
|
||||
|
||||
if (maxVal < 255) {
|
||||
this._brightnessAdjusted = maxVal;
|
||||
}
|
||||
}
|
||||
|
||||
this._wvSliderValue =
|
||||
stateObj.attributes.color_mode === LightColorMode.RGBW &&
|
||||
@@ -244,23 +235,11 @@ class LightRgbColorPicker extends LitElement {
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!changedProps.has("stateObj")) {
|
||||
if (this._isInteracting || !changedProps.has("stateObj")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldStateObj = changedProps.get("stateObj") as LightEntity | undefined;
|
||||
if (oldStateObj?.entity_id !== this.stateObj?.entity_id) {
|
||||
this._throttleUpdateColor.cancel();
|
||||
this._pendingSync.reset();
|
||||
this._isInteracting = false;
|
||||
this._updateSliderValues();
|
||||
} else if (!this._isInteracting && this._pendingSync.shouldSync()) {
|
||||
this._updateSliderValues();
|
||||
} else if (this.stateObj.state === "on") {
|
||||
// Keep the picker values, but the brightness normalization has to
|
||||
// follow the reported state, as it is applied to the next color sent.
|
||||
this._updateBrightnessAdjusted();
|
||||
}
|
||||
this._updateSliderValues();
|
||||
}
|
||||
|
||||
private _hsColorCursorMoved(ev: CustomEvent) {
|
||||
@@ -271,7 +250,6 @@ class LightRgbColorPicker extends LitElement {
|
||||
return;
|
||||
}
|
||||
this._hsPickerValue = color;
|
||||
this._pendingSync.schedule();
|
||||
|
||||
this._throttleUpdateColor();
|
||||
}
|
||||
@@ -334,23 +312,13 @@ class LightRgbColorPicker extends LitElement {
|
||||
this._colorBrightnessSliderValue = hsv[2] / 2.55;
|
||||
}
|
||||
|
||||
this._pendingSync.schedule();
|
||||
this._throttleUpdateColor();
|
||||
}
|
||||
|
||||
private _hsColorChanged(ev: CustomEvent) {
|
||||
this._isInteracting = false;
|
||||
if (!ev.detail.value) {
|
||||
// Nothing is sent, so stop blocking state updates.
|
||||
this._throttleUpdateColor.cancel();
|
||||
this._pendingSync.reset();
|
||||
this._updateSliderValues();
|
||||
return;
|
||||
}
|
||||
|
||||
// A pending throttled call would otherwise send an older color after the
|
||||
// final one.
|
||||
this._throttleUpdateColor.cancel();
|
||||
this._hsPickerValue = ev.detail.value;
|
||||
|
||||
this._updateColor();
|
||||
@@ -398,15 +366,11 @@ class LightRgbColorPicker extends LitElement {
|
||||
|
||||
private _applyColor(color: LightColor, params?: Record<string, any>) {
|
||||
fireEvent(this, "color-changed", color);
|
||||
return this._pendingSync.run(
|
||||
() =>
|
||||
this._api.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
...color,
|
||||
...params,
|
||||
}),
|
||||
() => this._updateSliderValues()
|
||||
);
|
||||
this._api.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
...color,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
private _colorBrightnessSliderChanged(ev: CustomEvent) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
} from "../../../../common/color/convert-light-color";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||
import { PendingValueSync } from "../../../../common/util/pending-value-sync";
|
||||
import { throttle } from "../../../../common/util/throttle";
|
||||
import "../../../../components/ha-control-slider";
|
||||
import {
|
||||
@@ -67,8 +66,6 @@ class LightColorTempPicker extends LitElement {
|
||||
|
||||
@state() private _isInteracting?: boolean;
|
||||
|
||||
private _pendingSync = new PendingValueSync();
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj) {
|
||||
return nothing;
|
||||
@@ -128,19 +125,11 @@ class LightColorTempPicker extends LitElement {
|
||||
public willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!changedProps.has("stateObj")) {
|
||||
if (this._isInteracting || !changedProps.has("stateObj")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldStateObj = changedProps.get("stateObj");
|
||||
if (oldStateObj?.entity_id !== this.stateObj?.entity_id) {
|
||||
this._throttleUpdateColorTemp.cancel();
|
||||
this._pendingSync.reset();
|
||||
this._isInteracting = false;
|
||||
this._updateSliderValues();
|
||||
} else if (!this._isInteracting && this._pendingSync.shouldSync()) {
|
||||
this._updateSliderValues();
|
||||
}
|
||||
this._updateSliderValues();
|
||||
}
|
||||
|
||||
private _ctColorCursorMoved(ev: CustomEvent) {
|
||||
@@ -153,7 +142,6 @@ class LightColorTempPicker extends LitElement {
|
||||
}
|
||||
|
||||
this._ctPickerValue = ct;
|
||||
this._pendingSync.schedule();
|
||||
|
||||
this._throttleUpdateColorTemp();
|
||||
}
|
||||
@@ -165,16 +153,7 @@ class LightColorTempPicker extends LitElement {
|
||||
private _ctColorChanged(ev: CustomEvent) {
|
||||
const ct = ev.detail.value;
|
||||
|
||||
this._isInteracting = false;
|
||||
|
||||
// A pending throttled call would otherwise send an older value after the
|
||||
// final one.
|
||||
this._throttleUpdateColorTemp.cancel();
|
||||
|
||||
if (isNaN(ct)) {
|
||||
// Nothing is sent, so stop blocking state updates.
|
||||
this._pendingSync.reset();
|
||||
this._updateSliderValues();
|
||||
if (isNaN(ct) || this._ctPickerValue === ct) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,15 +170,11 @@ class LightColorTempPicker extends LitElement {
|
||||
|
||||
private _applyColor(color: LightColor, params?: Record<string, any>) {
|
||||
fireEvent(this, "color-changed", color);
|
||||
return this._pendingSync.run(
|
||||
() =>
|
||||
this._api.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
...color,
|
||||
...params,
|
||||
}),
|
||||
() => this._updateSliderValues()
|
||||
);
|
||||
this._api.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
...color,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -112,7 +112,7 @@ export class HaMoreInfoTimerPresets extends LitElement {
|
||||
};
|
||||
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this._api.callWS,
|
||||
this._api,
|
||||
this.entry.entity_id,
|
||||
{
|
||||
options_domain: "timer",
|
||||
|
||||
@@ -82,7 +82,7 @@ export class HaMoreInfoViewVacuumSegmentMapping extends LitElement {
|
||||
};
|
||||
|
||||
try {
|
||||
await updateEntityRegistryEntry(this.hass.callWS, this.params.entityId, {
|
||||
await updateEntityRegistryEntry(this.hass, this.params.entityId, {
|
||||
options_domain: "vacuum",
|
||||
options: options,
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ export class HaMoreInfoValveFavoritePositions extends LitElement {
|
||||
currentOptions.favorite_positions = this._favoritePositions;
|
||||
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this._api.callWS,
|
||||
this._api,
|
||||
this.entry.entity_id,
|
||||
{
|
||||
options_domain: "valve",
|
||||
|
||||
@@ -124,12 +124,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
private _volumeController = new VolumeSliderController({
|
||||
getSlider: () => this._volumeSlider,
|
||||
step: this._volumeStep,
|
||||
onSetVolume: (value) => {
|
||||
// The final value is set right away, so drop the pending update from
|
||||
// the input events before it, or the same value is sent twice
|
||||
this._debouncedVolumeSet.cancel();
|
||||
this._setVolume(value);
|
||||
},
|
||||
onSetVolume: (value) => this._setVolume(value),
|
||||
onSetVolumeDebounced: (value) => this._debouncedVolumeSet(value),
|
||||
});
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ const copyFavoriteOptionsToEntities = async (
|
||||
if (selected?.entity) {
|
||||
const result = await Promise.allSettled(
|
||||
selected.entity.map((entityId: string) =>
|
||||
updateEntityRegistryEntry(hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(hass, entityId, {
|
||||
options_domain: domain,
|
||||
options,
|
||||
})
|
||||
|
||||
@@ -531,7 +531,7 @@ export class MoreInfoDialog extends DirtyStateProviderMixin<
|
||||
}
|
||||
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
favoritesContext.entry.entity_id,
|
||||
{
|
||||
options_domain: favoritesHandler.domain,
|
||||
|
||||
@@ -50,7 +50,7 @@ export class HaVoiceAssistantSetupStepArea extends LitElement {
|
||||
});
|
||||
return;
|
||||
}
|
||||
await updateDeviceRegistryEntry(this.hass.callWS, this.deviceId, {
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, {
|
||||
area_id: area,
|
||||
});
|
||||
this._nextStep();
|
||||
|
||||
@@ -351,7 +351,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
private async _done() {
|
||||
if (this._deviceName) {
|
||||
try {
|
||||
updateDeviceRegistryEntry(this.hass.callWS, this.deviceId, {
|
||||
updateDeviceRegistryEntry(this.hass, this.deviceId, {
|
||||
name_by_user: this._deviceName,
|
||||
});
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -45,42 +45,15 @@
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer-top {
|
||||
flex: 1;
|
||||
margin-top: 142px;
|
||||
margin-top: calc( 2 * max(var(--safe-area-inset-top, 0px), 48px) + 46px );
|
||||
padding-top: 48px;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
||||
flex: 1;
|
||||
padding-top: 16px;
|
||||
}
|
||||
#ha-launch-screen-info-box p {
|
||||
max-width: 400px;
|
||||
margin: 0 16px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
/* Margins, not flex `gap` (Safari 14.1). */
|
||||
#ha-launch-screen .ha-launch-screen-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin: -4px 16px;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-actions button {
|
||||
margin: 4px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 18px;
|
||||
background: none;
|
||||
color: #009ac7;
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-actions .ha-launch-screen-continue {
|
||||
border-color: currentColor;
|
||||
}
|
||||
.ohf-logo {
|
||||
margin: 48px 0;
|
||||
margin: max(var(--safe-area-inset-bottom, 0px), 48px) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -100,16 +73,6 @@
|
||||
aspect-ratio: 237 / 24;
|
||||
height: auto;
|
||||
}
|
||||
/* max() with var() fails after the cascade, so plain fallbacks above
|
||||
need @supports (Chrome below 79). `body`: Jinja2, see below. */
|
||||
@supports (margin: max(1px, 2px)) {
|
||||
body #ha-launch-screen .ha-launch-screen-spacer-top {
|
||||
margin-top: calc( 2 * max(var(--safe-area-inset-top, 0px), 48px) + 46px );
|
||||
}
|
||||
.ohf-logo {
|
||||
margin: max(var(--safe-area-inset-bottom, 0px), 48px) 0;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
* Core renders the app's index.html through Jinja2, where the minified
|
||||
|
||||
@@ -65,83 +65,23 @@
|
||||
</script>
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
// Floor check: BigInt for Chrome 67, <dialog> for Safari 15.4 and
|
||||
// Firefox 98. Not transpiled, so keep this ES5.
|
||||
(function () {
|
||||
var ACCEPTED_KEY = "haUnsupportedBrowserAccepted";
|
||||
var REMIND_AFTER = 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function loadApp() {
|
||||
window.customPanelJS = "<%= es5CustomPanelJS %>";
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
_ls("<%= entry %>", true);
|
||||
<% } %>
|
||||
}
|
||||
|
||||
if (
|
||||
typeof BigInt !== "undefined" &&
|
||||
typeof document.createElement("dialog").showModal === "function"
|
||||
) {
|
||||
loadApp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Shown while the app loads too.
|
||||
var box = document.getElementById("ha-launch-screen-info-box");
|
||||
var message = document.createElement("p");
|
||||
message.textContent =
|
||||
"This browser is no longer supported. Some parts of Home Assistant may not work.";
|
||||
box.appendChild(message);
|
||||
|
||||
// Stored: "always", or when the user last continued (ms).
|
||||
var stored = null;
|
||||
try {
|
||||
stored = localStorage.getItem(ACCEPTED_KEY);
|
||||
} catch (_err) {
|
||||
// Ask on every load.
|
||||
}
|
||||
if (stored === "always") {
|
||||
loadApp();
|
||||
return;
|
||||
}
|
||||
var acceptedAt = Number(stored) || 0;
|
||||
// A clock behind the stored time asks again.
|
||||
var age = Date.now() - acceptedAt;
|
||||
if (acceptedAt && age >= 0 && age < REMIND_AFTER) {
|
||||
loadApp();
|
||||
return;
|
||||
}
|
||||
|
||||
var actions = document.createElement("div");
|
||||
actions.className = "ha-launch-screen-actions";
|
||||
box.appendChild(actions);
|
||||
|
||||
function addButton(label, value) {
|
||||
var button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = label;
|
||||
button.addEventListener("click", function () {
|
||||
try {
|
||||
localStorage.setItem(ACCEPTED_KEY, value());
|
||||
} catch (_err) {
|
||||
// Continue this time only.
|
||||
}
|
||||
box.removeChild(actions);
|
||||
loadApp();
|
||||
});
|
||||
actions.appendChild(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
addButton("Continue anyway", function () {
|
||||
return String(Date.now());
|
||||
}).className = "ha-launch-screen-continue";
|
||||
if (acceptedAt) {
|
||||
addButton("Don't show again", function () {
|
||||
return "always";
|
||||
});
|
||||
}
|
||||
})();
|
||||
// Below the [legacy] browserslist floor the entry bundles cannot be
|
||||
// parsed, or dialogs cannot open, and nothing would ever leave the
|
||||
// launch screen. Test the features that set the floors: BigInt for
|
||||
// Chrome 67, native <dialog> for Safari 15.4 and Firefox 98. Not
|
||||
// transpiled, so keep this ES5.
|
||||
if (
|
||||
typeof BigInt === "undefined" ||
|
||||
typeof document.createElement("dialog").showModal !== "function"
|
||||
) {
|
||||
document.getElementById("ha-launch-screen-info-box").textContent =
|
||||
"This browser is too old to run Home Assistant.";
|
||||
} else {
|
||||
window.customPanelJS = "<%= es5CustomPanelJS %>";
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
_ls("<%= entry %>", true);
|
||||
<% } %>
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,171 +1,9 @@
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import type { LovelaceStrategyViewConfig } from "../../data/lovelace/config/view";
|
||||
import { ChildPanelReady } from "../../layouts/panel-ready";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-background";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import "../../components/ha-top-app-bar-fixed";
|
||||
|
||||
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||
strategy: {
|
||||
type: "climate",
|
||||
},
|
||||
};
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaHomeSummaryPanelBase } from "../home/ha-home-summary-panel-base";
|
||||
|
||||
@customElement("ha-panel-climate")
|
||||
class PanelClimate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _viewIndex = 0;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
private _childPanelReady?: ChildPanelReady;
|
||||
|
||||
public willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.willUpdate(changedProps);
|
||||
// Initial setup
|
||||
if (!this.hasUpdated) {
|
||||
this._setup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._setLovelace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldHass && this.hass) {
|
||||
// If the entity registry changed, ask the user if they want to refresh the config
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If ha started, refresh the config
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
oldHass.config.state !== "RUNNING"
|
||||
) {
|
||||
this._setLovelace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _setup() {
|
||||
await this.hass.loadFragmentTranslation("lovelace");
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
private _debounceRegistriesChanged = debounce(
|
||||
() => this._registriesChanged(),
|
||||
200
|
||||
);
|
||||
|
||||
private _registriesChanged = async () => {
|
||||
this._setLovelace();
|
||||
};
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-top-app-bar-fixed
|
||||
.narrow=${this.narrow}
|
||||
.backButton=${this._searchParams.has("historyBack")}
|
||||
>
|
||||
<div slot="title">${this.hass.localize("panel.climate")}</div>
|
||||
${
|
||||
this._lovelace
|
||||
? html`
|
||||
<hui-view-container .hass=${this.hass}>
|
||||
<hui-view-background .hass=${this.hass}>
|
||||
</hui-view-background>
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${this._viewIndex}
|
||||
></hui-view
|
||||
></hui-view-container>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</ha-top-app-bar-fixed>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
const viewConfig = await generateLovelaceViewStrategy(
|
||||
CLIMATE_LOVELACE_VIEW_CONFIG,
|
||||
this.hass
|
||||
);
|
||||
|
||||
const config = { views: [viewConfig] };
|
||||
const rawConfig = { views: [CLIMATE_LOVELACE_VIEW_CONFIG] };
|
||||
|
||||
if (deepEqual(config, this._lovelace?.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._childPanelReady ??= new ChildPanelReady(this);
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: rawConfig,
|
||||
editMode: false,
|
||||
urlPath: "climate",
|
||||
mode: "generated",
|
||||
locale: this.hass.locale,
|
||||
enableFullEditMode: () => undefined,
|
||||
saveConfig: async () => undefined,
|
||||
deleteConfig: async () => undefined,
|
||||
setEditMode: () => undefined,
|
||||
showToast: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
user-select: none;
|
||||
}
|
||||
hui-view-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
hui-view {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
class PanelClimate extends HaHomeSummaryPanelBase<"climate"> {
|
||||
protected readonly _systemDataKey = "climate";
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -2,9 +2,10 @@ import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getAreasFloorHierarchy } from "../../../common/areas/areas-floor-hierarchy";
|
||||
import {
|
||||
findEntities,
|
||||
findFilteredEntities,
|
||||
generateEntityFilter,
|
||||
type EntityFilter,
|
||||
type EntityFilterSources,
|
||||
} from "../../../common/entity/entity_filter";
|
||||
import { floorDefaultIcon } from "../../../components/ha-floor-icon";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
@@ -12,9 +13,15 @@ import type { LovelaceSectionRawConfig } from "../../../data/lovelace/config/sec
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { computeAreaTileCardConfig } from "../../lovelace/strategies/areas/helpers/areas-strategy-helper";
|
||||
import {
|
||||
wrapEntityVisibility,
|
||||
type ComputeEntityCard,
|
||||
} from "../../lovelace/strategies/helpers/entity-visibility";
|
||||
|
||||
export interface ClimateViewStrategyConfig {
|
||||
type: "climate";
|
||||
hidden_entities?: string[];
|
||||
edit_visibility?: boolean;
|
||||
}
|
||||
|
||||
export const climateEntityFilters: EntityFilter[] = [
|
||||
@@ -42,40 +49,53 @@ export const climateEntityFilters: EntityFilter[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const hasClimateEntities = (hass: HomeAssistant): boolean => {
|
||||
const hasAreaSensor = Object.values(hass.areas).some(
|
||||
(area) =>
|
||||
(area.temperature_entity_id && hass.states[area.temperature_entity_id]) ||
|
||||
(area.humidity_entity_id && hass.states[area.humidity_entity_id])
|
||||
);
|
||||
export const findClimateEntities = (
|
||||
sources: EntityFilterSources,
|
||||
hiddenEntities?: string[]
|
||||
): string[] =>
|
||||
findFilteredEntities(sources, climateEntityFilters, hiddenEntities);
|
||||
|
||||
if (hasAreaSensor) {
|
||||
return true;
|
||||
type ClimateAreaSensorKind = "temperature" | "humidity";
|
||||
|
||||
export const findClimateAreaSensors = (
|
||||
sources: EntityFilterSources,
|
||||
hiddenEntities?: string[]
|
||||
): Map<string, ClimateAreaSensorKind> => {
|
||||
const hidden = new Set(hiddenEntities);
|
||||
const sensors = new Map<string, ClimateAreaSensorKind>();
|
||||
for (const area of Object.values(sources.areas)) {
|
||||
const slots = [
|
||||
[area.temperature_entity_id, "temperature"],
|
||||
[area.humidity_entity_id, "humidity"],
|
||||
] as const;
|
||||
for (const [entityId, kind] of slots) {
|
||||
if (entityId && entityId in sources.states && !hidden.has(entityId)) {
|
||||
sensors.set(entityId, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const entityIds = Object.keys(hass.states);
|
||||
|
||||
return climateEntityFilters.some((filter) =>
|
||||
entityIds.some(
|
||||
generateEntityFilter(
|
||||
hass.states,
|
||||
hass.entities,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.floors,
|
||||
filter
|
||||
)
|
||||
)
|
||||
);
|
||||
return sensors;
|
||||
};
|
||||
|
||||
export const hasClimateEntities = (
|
||||
hass: HomeAssistant,
|
||||
hiddenEntities?: string[]
|
||||
): boolean =>
|
||||
findClimateAreaSensors(hass, hiddenEntities).size > 0 ||
|
||||
findClimateEntities(hass, hiddenEntities).length > 0;
|
||||
|
||||
interface ClimateCardOptions {
|
||||
entities: string[];
|
||||
areaSensors: Map<string, ClimateAreaSensorKind>;
|
||||
computeCard: ComputeEntityCard;
|
||||
}
|
||||
|
||||
const processAreasForClimate = (
|
||||
areaIds: string[],
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
{ entities, areaSensors, computeCard }: ClimateCardOptions
|
||||
): LovelaceCardConfig[] => {
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, true);
|
||||
|
||||
for (const areaId of areaIds) {
|
||||
const area = hass.areas[areaId];
|
||||
@@ -96,25 +116,13 @@ const processAreasForClimate = (
|
||||
|
||||
// Add temperature and humidity sensors with trend graphs for areas
|
||||
const temperatureEntityId = area.temperature_entity_id;
|
||||
if (temperatureEntityId && hass.states[temperatureEntityId]) {
|
||||
areaCards.push({
|
||||
...computeTileCard(temperatureEntityId),
|
||||
name:
|
||||
hass.localize("component.sensor.entity_component.temperature.name") ||
|
||||
"Temperature",
|
||||
features: [{ type: "trend-graph" }],
|
||||
});
|
||||
if (temperatureEntityId && areaSensors.has(temperatureEntityId)) {
|
||||
areaCards.push(computeCard(temperatureEntityId));
|
||||
}
|
||||
|
||||
const humidityEntityId = area.humidity_entity_id;
|
||||
if (humidityEntityId && hass.states[humidityEntityId]) {
|
||||
areaCards.push({
|
||||
...computeTileCard(humidityEntityId),
|
||||
name:
|
||||
hass.localize("component.sensor.entity_component.humidity.name") ||
|
||||
"Humidity",
|
||||
features: [{ type: "trend-graph" }],
|
||||
});
|
||||
if (humidityEntityId && areaSensors.has(humidityEntityId)) {
|
||||
areaCards.push(computeCard(humidityEntityId));
|
||||
}
|
||||
|
||||
// Add other climate entities
|
||||
@@ -124,18 +132,7 @@ const processAreasForClimate = (
|
||||
continue;
|
||||
}
|
||||
|
||||
const state = hass.states[entityId];
|
||||
if (
|
||||
state?.attributes.device_class === "temperature" ||
|
||||
state?.attributes.device_class === "humidity"
|
||||
) {
|
||||
areaCards.push({
|
||||
...computeTileCard(entityId),
|
||||
features: [{ type: "trend-graph" }],
|
||||
});
|
||||
} else {
|
||||
areaCards.push(computeTileCard(entityId));
|
||||
}
|
||||
areaCards.push(computeCard(entityId));
|
||||
}
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
@@ -159,7 +156,7 @@ const processAreasForClimate = (
|
||||
|
||||
const processUnassignedEntities = (
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
{ entities, computeCard }: ClimateCardOptions
|
||||
): LovelaceCardConfig[] => {
|
||||
const unassignedFilter = generateEntityFilter(
|
||||
hass.states,
|
||||
@@ -173,10 +170,9 @@ const processUnassignedEntities = (
|
||||
);
|
||||
const unassignedEntities = entities.filter(unassignedFilter);
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, true);
|
||||
|
||||
for (const entityId of unassignedEntities) {
|
||||
areaCards.push(computeTileCard(entityId));
|
||||
areaCards.push(computeCard(entityId));
|
||||
}
|
||||
|
||||
return areaCards;
|
||||
@@ -185,7 +181,7 @@ const processUnassignedEntities = (
|
||||
@customElement("climate-view-strategy")
|
||||
export class ClimateViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
_config: ClimateViewStrategyConfig,
|
||||
config: ClimateViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const areas = Object.values(hass.areas);
|
||||
@@ -194,20 +190,40 @@ export class ClimateViewStrategy extends ReactiveElement {
|
||||
|
||||
const sections: LovelaceSectionRawConfig[] = [];
|
||||
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const climateFilters = climateEntityFilters.map((filter) =>
|
||||
generateEntityFilter(
|
||||
hass.states,
|
||||
hass.entities,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.floors,
|
||||
filter
|
||||
)
|
||||
const hiddenEntities = new Set(config.hidden_entities);
|
||||
const editVisibility = Boolean(config.edit_visibility);
|
||||
const entities = findClimateEntities(
|
||||
hass,
|
||||
editVisibility ? undefined : config.hidden_entities
|
||||
);
|
||||
|
||||
const entities = findEntities(allEntities, climateFilters);
|
||||
const areaSensors = findClimateAreaSensors(
|
||||
hass,
|
||||
editVisibility ? undefined : config.hidden_entities
|
||||
);
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, true);
|
||||
const computeClimateCard = (entityId: string): LovelaceCardConfig => {
|
||||
const tileCard = computeTileCard(entityId);
|
||||
const areaSensor = areaSensors.get(entityId);
|
||||
if (!areaSensor) {
|
||||
return tileCard;
|
||||
}
|
||||
return {
|
||||
...tileCard,
|
||||
name:
|
||||
hass.localize(
|
||||
`component.sensor.entity_component.${areaSensor}.name`
|
||||
) || undefined,
|
||||
features: [{ type: "trend-graph" }],
|
||||
};
|
||||
};
|
||||
const cardOptions: ClimateCardOptions = {
|
||||
entities,
|
||||
areaSensors,
|
||||
computeCard: editVisibility
|
||||
? wrapEntityVisibility(hiddenEntities, computeClimateCard)
|
||||
: computeClimateCard,
|
||||
};
|
||||
|
||||
const floorCount =
|
||||
hierarchy.floors.length + (hierarchy.areas.length ? 1 : 0);
|
||||
@@ -233,7 +249,7 @@ export class ClimateViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForClimate(areaIds, hass, entities);
|
||||
const areaCards = processAreasForClimate(areaIds, hass, cardOptions);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
@@ -257,7 +273,11 @@ export class ClimateViewStrategy extends ReactiveElement {
|
||||
],
|
||||
};
|
||||
|
||||
const areaCards = processAreasForClimate(hierarchy.areas, hass, entities);
|
||||
const areaCards = processAreasForClimate(
|
||||
hierarchy.areas,
|
||||
hass,
|
||||
cardOptions
|
||||
);
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
section.cards!.push(...areaCards);
|
||||
@@ -266,7 +286,7 @@ export class ClimateViewStrategy extends ReactiveElement {
|
||||
}
|
||||
|
||||
// Process unassigned entities
|
||||
const unassignedCards = processUnassignedEntities(hass, entities);
|
||||
const unassignedCards = processUnassignedEntities(hass, cardOptions);
|
||||
|
||||
if (unassignedCards.length > 0) {
|
||||
const section: LovelaceSectionRawConfig = {
|
||||
|
||||
@@ -857,8 +857,6 @@ class HaConfigAreaPage extends LitElement {
|
||||
},
|
||||
this.hass.auth.data.hassUrl
|
||||
)}
|
||||
width="24"
|
||||
height="24"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>`;
|
||||
@@ -1063,7 +1061,7 @@ class HaConfigAreaPage extends LitElement {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.img-container img {
|
||||
img {
|
||||
border-radius: var(
|
||||
--ha-card-border-radius,
|
||||
var(--ha-border-radius-lg)
|
||||
@@ -1117,6 +1115,11 @@ class HaConfigAreaPage extends LitElement {
|
||||
ha-list-item-button ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
img[slot="start"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
}
|
||||
ha-card > *:first-child {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,6 @@ import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
import { renderCtrlOrCmd } from "../../../../common/keyboard/ctrl-or-cmd";
|
||||
import { scrollRowIntoView } from "../scroll-row-into-view";
|
||||
import { AutomationPathController } from "../automation-path";
|
||||
|
||||
export interface ActionElement extends LitElement {
|
||||
action: Action;
|
||||
@@ -183,8 +182,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public root = false;
|
||||
|
||||
@property({ attribute: false }) public index!: number;
|
||||
|
||||
@property({ type: Boolean }) public first?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public last?: boolean;
|
||||
@@ -197,11 +194,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
protected readonly pathController = new AutomationPathController(this, {
|
||||
segment: () => [this.index],
|
||||
row: () => (this.optionsInSidebar ? this : undefined),
|
||||
});
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
|
||||
@@ -43,7 +43,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
|
||||
return html`
|
||||
<ha-automation-option
|
||||
path-key="choose"
|
||||
.options=${options}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._optionsChanged}
|
||||
|
||||
@@ -46,7 +46,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-condition
|
||||
path-key="if"
|
||||
.conditions=${action.if ?? []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._ifChanged}
|
||||
@@ -61,7 +60,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
path-key="then"
|
||||
.actions=${action.then ?? []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._thenChanged}
|
||||
@@ -75,7 +73,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
path-key="else"
|
||||
.actions=${action.else || []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._elseChanged}
|
||||
|
||||
@@ -35,7 +35,6 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
|
||||
return html`
|
||||
<ha-automation-action
|
||||
path-key="parallel"
|
||||
.actions=${action.parallel}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
import { AutomationPathController } from "../../automation-path";
|
||||
|
||||
import { isTemplate } from "../../../../../common/string/has-template";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
@@ -41,11 +40,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
||||
|
||||
// The form edits the repeat options, so its fields are keyed below "repeat"
|
||||
protected readonly pathController = new AutomationPathController(this, {
|
||||
segment: () => ["repeat"],
|
||||
});
|
||||
|
||||
@query("ha-form")
|
||||
private _formElement?: HaForm;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
||||
|
||||
return html`
|
||||
<ha-automation-action
|
||||
path-key="sequence"
|
||||
.actions=${action.sequence ?? []}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -90,7 +90,6 @@ export class HaWaitForTriggerAction
|
||||
${
|
||||
this.indent || (!this.inSidebar && !this.indent)
|
||||
? html`<ha-automation-trigger
|
||||
path-key="wait_for_trigger"
|
||||
class=${!this.inSidebar && !this.indent ? "expansion-panel" : ""}
|
||||
.triggers=${ensureArray(this.action.wait_for_trigger)}
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import { ContextProvider, createContext } from "@lit/context";
|
||||
import type { ReactiveElement } from "lit";
|
||||
import {
|
||||
consumeContext,
|
||||
ContextController,
|
||||
} from "../../../common/decorators/consume";
|
||||
import {
|
||||
pathEquals,
|
||||
stringifyPath,
|
||||
type ConfigPath,
|
||||
} from "../../../common/util/config-path";
|
||||
|
||||
export const automationPathContext =
|
||||
createContext<ConfigPath>("automation-path");
|
||||
|
||||
export interface AutomationPathRow extends HTMLElement {
|
||||
readonly selected: boolean;
|
||||
openSidebar(): void;
|
||||
expand(): void;
|
||||
}
|
||||
|
||||
const owner = (rows?: AutomationPathRow[]) => rows?.[rows.length - 1];
|
||||
|
||||
/**
|
||||
* Rows that open in the sidebar, by path. Lets the editor find the row a
|
||||
* config path belongs to, e.g. to show it or point at an error in it.
|
||||
*/
|
||||
export class AutomationRowRegistry {
|
||||
// Rows claiming each path, the latest one owns it. While a row is dragged
|
||||
// over another list it claims the path of a row there, which takes the path
|
||||
// back once the dragged row leaves.
|
||||
private _rows = new Map<
|
||||
string,
|
||||
{ path: ConfigPath; rows: AutomationPathRow[] }
|
||||
>();
|
||||
|
||||
public register(path: ConfigPath, row: AutomationPathRow) {
|
||||
const key = stringifyPath(path);
|
||||
const entry = this._rows.get(key);
|
||||
if (!entry) {
|
||||
this._rows.set(key, { path, rows: [row] });
|
||||
return;
|
||||
}
|
||||
entry.rows = [...entry.rows.filter((claimed) => claimed !== row), row];
|
||||
}
|
||||
|
||||
public unregister(path: ConfigPath, row: AutomationPathRow) {
|
||||
const key = stringifyPath(path);
|
||||
const entry = this._rows.get(key);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
entry.rows = entry.rows.filter((claimed) => claimed !== row);
|
||||
if (!entry.rows.length) {
|
||||
this._rows.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
public pathOf(row: AutomationPathRow): ConfigPath | undefined {
|
||||
return [...this._rows.values()].find((entry) => owner(entry.rows) === row)
|
||||
?.path;
|
||||
}
|
||||
|
||||
/** The rows containing `path`, outermost first. */
|
||||
public rowsAlong(path: ConfigPath): AutomationPathRow[] {
|
||||
return path
|
||||
.map((_segment, index) =>
|
||||
owner(this._rows.get(stringifyPath(path.slice(0, index + 1)))?.rows)
|
||||
)
|
||||
.filter((row): row is AutomationPathRow => row !== undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export const automationRowRegistryContext =
|
||||
createContext<AutomationRowRegistry>("automation-row-registry");
|
||||
|
||||
interface AutomationPathControllerOptions {
|
||||
/** Path segments this element adds to the path of its ancestor. */
|
||||
segment: () => ConfigPath;
|
||||
/** The host while it is a row that opens in the sidebar. */
|
||||
row?: () => AutomationPathRow | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the path of the closest ancestor and provides it extended by the
|
||||
* host's own segment to its descendants.
|
||||
*/
|
||||
export class AutomationPathController extends ContextController {
|
||||
public path: ConfigPath = [];
|
||||
|
||||
@consumeContext({ context: automationPathContext, subscribe: true })
|
||||
private _parentPath: ConfigPath = [];
|
||||
|
||||
@consumeContext({ context: automationRowRegistryContext })
|
||||
private _registry?: AutomationRowRegistry;
|
||||
|
||||
private _registeredPath?: ConfigPath;
|
||||
|
||||
private _registeredRow?: AutomationPathRow;
|
||||
|
||||
private readonly _provider: ContextProvider<typeof automationPathContext>;
|
||||
|
||||
constructor(
|
||||
host: ReactiveElement,
|
||||
private readonly _options: AutomationPathControllerOptions
|
||||
) {
|
||||
super(host);
|
||||
this._provider = new ContextProvider(host, {
|
||||
context: automationPathContext,
|
||||
initialValue: this.path,
|
||||
});
|
||||
}
|
||||
|
||||
// The path is not rendered, so a changed ancestor path is passed on without
|
||||
// updating the host. Only the host's own segment changes go through an update.
|
||||
protected contextUpdated() {
|
||||
this._sync();
|
||||
}
|
||||
|
||||
public hostUpdate() {
|
||||
this._sync();
|
||||
}
|
||||
|
||||
public hostConnected() {
|
||||
// A row moved by a drag reconnects without an update
|
||||
this._sync();
|
||||
}
|
||||
|
||||
private _sync() {
|
||||
const path = [...this._parentPath, ...this._options.segment()];
|
||||
if (!pathEquals(path, this.path)) {
|
||||
this.path = path;
|
||||
this._provider.setValue(path);
|
||||
}
|
||||
this._syncRegistration();
|
||||
}
|
||||
|
||||
public hostDisconnected() {
|
||||
this._unregister();
|
||||
}
|
||||
|
||||
private _syncRegistration() {
|
||||
if (!this._registry || !this._options.row) {
|
||||
return;
|
||||
}
|
||||
const row = this.host.isConnected ? this._options.row() : undefined;
|
||||
if (
|
||||
this._registeredPath &&
|
||||
(!row || !pathEquals(this._registeredPath, this.path))
|
||||
) {
|
||||
this._unregister();
|
||||
}
|
||||
if (row && !this._registeredPath) {
|
||||
this._registry.register(this.path, row);
|
||||
this._registeredPath = this.path;
|
||||
this._registeredRow = row;
|
||||
}
|
||||
}
|
||||
|
||||
private _unregister() {
|
||||
if (this._registeredPath && this._registeredRow) {
|
||||
this._registry?.unregister(this._registeredPath, this._registeredRow);
|
||||
}
|
||||
this._registeredPath = undefined;
|
||||
this._registeredRow = undefined;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,6 @@ import "./types/ha-automation-condition-trigger";
|
||||
import "./types/ha-automation-condition-zone";
|
||||
import { renderCtrlOrCmd } from "../../../../common/keyboard/ctrl-or-cmd";
|
||||
import { scrollRowIntoView } from "../scroll-row-into-view";
|
||||
import { AutomationPathController } from "../automation-path";
|
||||
|
||||
export interface ConditionElement extends LitElement {
|
||||
condition: Condition;
|
||||
@@ -114,8 +113,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public root = false;
|
||||
|
||||
@property({ attribute: false }) public index!: number;
|
||||
|
||||
@property({ type: Boolean }) public first?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public last?: boolean;
|
||||
@@ -127,11 +124,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
protected readonly pathController = new AutomationPathController(this, {
|
||||
segment: () => [this.index],
|
||||
row: () => (this.optionsInSidebar ? this : undefined),
|
||||
});
|
||||
|
||||
@state() private _collapsed = true;
|
||||
|
||||
@state() private _isNew = false;
|
||||
|
||||
@@ -29,7 +29,6 @@ export abstract class HaLogicalCondition
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-automation-condition
|
||||
path-key="conditions"
|
||||
.conditions=${this.condition.conditions || []}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -1162,7 +1162,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
}
|
||||
|
||||
if (entityId) {
|
||||
await updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
await updateEntityRegistryEntry(this.hass, entityId, {
|
||||
categories: {
|
||||
automation: this.entityRegistryUpdate.category || null,
|
||||
},
|
||||
|
||||
@@ -1267,7 +1267,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
|
||||
this._selected.forEach((entityId) => {
|
||||
promises.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(this.hass, entityId, {
|
||||
categories: { automation: category },
|
||||
})
|
||||
);
|
||||
@@ -1306,7 +1306,7 @@ ${rejected
|
||||
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
|
||||
this._selected.forEach((entityId) => {
|
||||
promises.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(this.hass, entityId, {
|
||||
labels:
|
||||
action === "add"
|
||||
? this.hass.entities[entityId].labels.concat(label)
|
||||
@@ -1351,7 +1351,7 @@ ${rejected
|
||||
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
|
||||
this._selected.forEach((entityId) => {
|
||||
promises.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(this.hass, entityId, {
|
||||
area_id: area,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import type { AutomationClipboard } from "../../../data/automation";
|
||||
import type { Constructor, HomeAssistant } from "../../../types";
|
||||
import { AutomationPathController } from "./automation-path";
|
||||
|
||||
const rowKeys = new WeakMap<object, string>();
|
||||
|
||||
@@ -76,13 +75,6 @@ export const AutomationSortableListMixin = <T extends object>(
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
/** Key of this list in the config of the row or editor rendering it. */
|
||||
@property({ attribute: "path-key" }) public pathKey?: string;
|
||||
|
||||
protected readonly pathController = new AutomationPathController(this, {
|
||||
segment: () => (this.pathKey === undefined ? [] : [this.pathKey]),
|
||||
});
|
||||
|
||||
@state() protected rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { provide } from "@lit/context";
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
import {
|
||||
html,
|
||||
@@ -34,10 +33,6 @@ import type {
|
||||
ValueChangedEvent,
|
||||
} from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import {
|
||||
AutomationRowRegistry,
|
||||
automationRowRegistryContext,
|
||||
} from "./automation-path";
|
||||
import "./ha-automation-sidebar";
|
||||
import type HaAutomationSidebar from "./ha-automation-sidebar";
|
||||
|
||||
@@ -87,9 +82,6 @@ export const ManualEditorMixin = <TConfig>(
|
||||
@query("ha-automation-sidebar")
|
||||
protected sidebarElement?: HaAutomationSidebar;
|
||||
|
||||
@provide({ context: automationRowRegistryContext })
|
||||
protected rowRegistry = new AutomationRowRegistry();
|
||||
|
||||
declare protected collapsableElements?: NodeListOf<
|
||||
Element & {
|
||||
expandAll(): void;
|
||||
|
||||
@@ -111,7 +111,6 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
|
||||
<ha-automation-trigger
|
||||
role="region"
|
||||
path-key="triggers"
|
||||
aria-labelledby="triggers-heading"
|
||||
.triggers=${this.config.triggers || []}
|
||||
.highlightedTriggers=${this.pastedConfig?.triggers}
|
||||
@@ -150,7 +149,6 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
|
||||
<ha-automation-condition
|
||||
role="region"
|
||||
path-key="conditions"
|
||||
aria-labelledby="conditions-heading"
|
||||
.conditions=${this.config.conditions || []}
|
||||
.highlightedConditions=${this.pastedConfig?.conditions}
|
||||
@@ -185,7 +183,6 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
|
||||
<ha-automation-action
|
||||
role="region"
|
||||
path-key="actions"
|
||||
aria-labelledby="actions-heading"
|
||||
.actions=${this.config.actions || []}
|
||||
.highlightedActions=${this.pastedConfig?.actions}
|
||||
|
||||
@@ -64,7 +64,6 @@ import {
|
||||
} from "../styles";
|
||||
import "../trigger/ha-automation-trigger-references";
|
||||
import { getUiSupportWarnings, isConfigObject } from "../ui-support";
|
||||
import { AutomationPathController } from "../automation-path";
|
||||
|
||||
@customElement("ha-automation-option-row")
|
||||
export default class HaAutomationOptionRow extends LitElement {
|
||||
@@ -90,11 +89,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
protected readonly pathController = new AutomationPathController(this, {
|
||||
segment: () => (this.defaultActions ? ["default"] : [this.index]),
|
||||
row: () => (this.optionsInSidebar ? this : undefined),
|
||||
});
|
||||
|
||||
@state() private _expanded = false;
|
||||
|
||||
@state() private _selected = false;
|
||||
@@ -454,7 +448,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-condition
|
||||
path-key="conditions"
|
||||
.conditions=${ensureArray<string | Condition>(
|
||||
this.option.conditions
|
||||
)}
|
||||
@@ -473,7 +466,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
.pathKey=${this.option ? "sequence" : undefined}
|
||||
.actions=${
|
||||
(this.option
|
||||
? ensureArray(this.option.sequence) || []
|
||||
|
||||
@@ -120,7 +120,6 @@ import "./types/ha-automation-trigger-webhook";
|
||||
import "./types/ha-automation-trigger-zone";
|
||||
import { renderCtrlOrCmd } from "../../../../common/keyboard/ctrl-or-cmd";
|
||||
import { scrollRowIntoView } from "../scroll-row-into-view";
|
||||
import { AutomationPathController } from "../automation-path";
|
||||
import { targetExpandGroupContext } from "../../../../components/target-picker/target-expand-group-context";
|
||||
|
||||
export interface TriggerElement extends LitElement {
|
||||
@@ -165,7 +164,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public last?: boolean;
|
||||
|
||||
@property({ attribute: false }) public index!: number;
|
||||
@property({ type: Number }) public index?: number;
|
||||
|
||||
@property({ type: Boolean }) public highlight?: boolean;
|
||||
|
||||
@@ -175,11 +174,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
protected readonly pathController = new AutomationPathController(this, {
|
||||
segment: () => [this.index],
|
||||
row: () => (this.optionsInSidebar ? this : undefined),
|
||||
});
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _triggered = false;
|
||||
|
||||
@@ -26,7 +26,6 @@ export class HaTriggerList extends LitElement implements TriggerElement {
|
||||
|
||||
return html`
|
||||
<ha-automation-trigger
|
||||
path-key="triggers"
|
||||
.triggers=${triggers}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -216,7 +216,7 @@ class HaConfigBackupOverview extends LitElement {
|
||||
back-path="/config/system"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.backup.caption")}
|
||||
.header=${this.hass.localize("ui.panel.config.backup.overview.header")}
|
||||
>
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
|
||||
@@ -123,7 +123,7 @@ class DialogAssignCategory extends DirtyStateProviderMixin<AssignCategoryFormSta
|
||||
this._error = undefined;
|
||||
try {
|
||||
await updateEntityRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
this._params!.entityReg.entity_id,
|
||||
{
|
||||
categories: { [this._scope!]: this._category || null },
|
||||
|
||||
@@ -17,8 +17,6 @@ import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
const ADDRESS_TYPES = ["mac", "bluetooth", "zigbee"];
|
||||
|
||||
@customElement("ha-device-info-card")
|
||||
export class HaDeviceCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -236,13 +234,9 @@ export class HaDeviceCard extends LitElement {
|
||||
}
|
||||
|
||||
protected _getAddresses() {
|
||||
return this.device.connections
|
||||
.filter((conn) => ADDRESS_TYPES.includes(conn[0]))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
ADDRESS_TYPES.indexOf(a[0]) - ADDRESS_TYPES.indexOf(b[0]) ||
|
||||
stringCompare(a[1], b[1])
|
||||
);
|
||||
return this.device.connections.filter((conn) =>
|
||||
["mac", "bluetooth", "zigbee"].includes(conn[0])
|
||||
);
|
||||
}
|
||||
|
||||
private _computeDeviceNameDisplay(deviceId: string) {
|
||||
|
||||
@@ -1600,11 +1600,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
delete updates.disabled_by;
|
||||
}
|
||||
try {
|
||||
await updateDeviceRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this.deviceId,
|
||||
updates
|
||||
);
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
|
||||
} catch (err: unknown) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
@@ -1620,7 +1616,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
};
|
||||
|
||||
private async _enableDevice(): Promise<void> {
|
||||
await updateDeviceRegistryEntry(this.hass.callWS, this.deviceId, {
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, {
|
||||
disabled_by: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1167,7 +1167,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
const promises: Promise<DeviceRegistryEntry>[] = [];
|
||||
this._selected.forEach((deviceId) => {
|
||||
promises.push(
|
||||
updateDeviceRegistryEntry(this.hass.callWS, deviceId, {
|
||||
updateDeviceRegistryEntry(this.hass, deviceId, {
|
||||
area_id: area,
|
||||
})
|
||||
);
|
||||
@@ -1219,7 +1219,7 @@ ${rejected
|
||||
const promises: Promise<DeviceRegistryEntry>[] = [];
|
||||
this._selected.forEach((deviceId) => {
|
||||
promises.push(
|
||||
updateDeviceRegistryEntry(this.hass.callWS, deviceId, {
|
||||
updateDeviceRegistryEntry(this.hass, deviceId, {
|
||||
labels:
|
||||
action === "add"
|
||||
? this.hass.devices[deviceId].labels.concat(label)
|
||||
|
||||
@@ -114,7 +114,7 @@ export class DialogVacuumSegmentMapping
|
||||
last_seen_segments: mapper.lastSeenSegments,
|
||||
};
|
||||
|
||||
await updateEntityRegistryEntry(this.hass.callWS, this._params.entityId, {
|
||||
await updateEntityRegistryEntry(this.hass, this._params.entityId, {
|
||||
options_domain: "vacuum",
|
||||
options: options,
|
||||
});
|
||||
|
||||
@@ -100,6 +100,7 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
: html`
|
||||
<span @value-changed=${this._valueChanged}>
|
||||
${dynamicElement(`ha-${this.entry.platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
entry: this.entry,
|
||||
disabled: this._item === null,
|
||||
|
||||
@@ -1414,7 +1414,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this.hass!.callWS,
|
||||
this.hass!,
|
||||
this.entry.entity_id,
|
||||
params
|
||||
);
|
||||
@@ -1593,7 +1593,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
private async _restoreEntityId(): Promise<void> {
|
||||
// use the unsaved name and area, as they are part of the entity ID
|
||||
const entityIds = await getAutomaticEntityIds(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
[this._origEntityId],
|
||||
{
|
||||
[this._origEntityId]: {
|
||||
@@ -1821,11 +1821,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
showDeviceRegistryDetailDialog(this, {
|
||||
device: this._device!,
|
||||
updateEntry: async (updates) => {
|
||||
await updateDeviceRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this._device!.id,
|
||||
updates
|
||||
);
|
||||
await updateDeviceRegistryEntry(this.hass, this._device!.id, updates);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
showDeviceRegistryDetailDialog(this, {
|
||||
device: device,
|
||||
updateEntry: async (updates) => {
|
||||
await updateDeviceRegistryEntry(this.hass.callWS, device.id, updates);
|
||||
await updateDeviceRegistryEntry(this.hass, device.id, updates);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -189,7 +189,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
this._submitting = true;
|
||||
try {
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this.hass!.callWS,
|
||||
this.hass!,
|
||||
this.entry.entity_id,
|
||||
{ disabled_by: null }
|
||||
);
|
||||
|
||||
@@ -1388,7 +1388,7 @@ export class HaConfigEntities extends LitElement {
|
||||
const result = await Promise.allSettled(
|
||||
this._selected.map(async (entity) => {
|
||||
const updateResult = await updateEntityRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
entity,
|
||||
{
|
||||
disabled_by: null,
|
||||
@@ -1453,7 +1453,7 @@ export class HaConfigEntities extends LitElement {
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
confirm: () => {
|
||||
this._selected.forEach((entity) =>
|
||||
updateEntityRegistryEntry(this.hass.callWS, entity, {
|
||||
updateEntityRegistryEntry(this.hass, entity, {
|
||||
disabled_by: "user",
|
||||
})
|
||||
);
|
||||
@@ -1475,7 +1475,7 @@ export class HaConfigEntities extends LitElement {
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
confirm: () => {
|
||||
this._selected.forEach((entity) =>
|
||||
updateEntityRegistryEntry(this.hass.callWS, entity, {
|
||||
updateEntityRegistryEntry(this.hass, entity, {
|
||||
hidden_by: "user",
|
||||
})
|
||||
);
|
||||
@@ -1486,7 +1486,7 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
private _unhideSelected = () => {
|
||||
this._selected.forEach((entity) =>
|
||||
updateEntityRegistryEntry(this.hass.callWS, entity, {
|
||||
updateEntityRegistryEntry(this.hass, entity, {
|
||||
hidden_by: null,
|
||||
})
|
||||
);
|
||||
@@ -1523,7 +1523,7 @@ export class HaConfigEntities extends LitElement {
|
||||
return;
|
||||
}
|
||||
promises.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(this.hass, entityId, {
|
||||
labels:
|
||||
action === "add"
|
||||
? entityReg.labels.concat(label)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { mdiAlertOutline } from "@mdi/js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { Connection, HassConfig } from "home-assistant-js-websocket";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -31,12 +29,6 @@ import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tooltip";
|
||||
import { getConfigFlowHandlers } from "../../../data/config_flow";
|
||||
import {
|
||||
apiContext,
|
||||
configContext,
|
||||
connectionContext,
|
||||
internationalizationContext,
|
||||
} from "../../../data/context";
|
||||
import { createCounter } from "../../../data/counter";
|
||||
import {
|
||||
fetchEntityRegistry,
|
||||
@@ -66,13 +58,7 @@ import {
|
||||
type FuseWeightedKey,
|
||||
} from "../../../resources/fuseMultiTerm";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type {
|
||||
HomeAssistantApi,
|
||||
HomeAssistantConfig,
|
||||
HomeAssistantConnection,
|
||||
HomeAssistantInternationalization,
|
||||
ValueChangedEvent,
|
||||
} from "../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
import type {
|
||||
Helper,
|
||||
HelperCategory,
|
||||
@@ -91,7 +77,7 @@ type HelperCreators = Record<
|
||||
HelperDomain,
|
||||
{
|
||||
create: (
|
||||
callWS: HomeAssistantApi["callWS"],
|
||||
hass: HomeAssistant,
|
||||
// Not properly typed because there is currently a mismatch for this._item between:
|
||||
// 1. Type passed to form should be Helper
|
||||
// 2. Type received by creator should be MutableParams version
|
||||
@@ -161,25 +147,7 @@ const HELPER_SEARCH_KEYS: FuseWeightedKey[] = [
|
||||
export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
{ item?: HelperFormItem; area?: string } | undefined
|
||||
>()(LitElement) {
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: HomeAssistantInternationalization;
|
||||
|
||||
@state()
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
@transform<HomeAssistantConfig, HassConfig>({
|
||||
transformer: ({ config }) => config,
|
||||
})
|
||||
private _hassConfig!: HassConfig;
|
||||
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: HomeAssistantApi;
|
||||
|
||||
@consume({ context: connectionContext, subscribe: true })
|
||||
@transform<HomeAssistantConnection, Connection>({
|
||||
transformer: ({ connection }) => connection,
|
||||
})
|
||||
private _connection!: Connection;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _item?: HelperFormItem;
|
||||
|
||||
@@ -235,9 +203,9 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
}
|
||||
this._open = true;
|
||||
await this.updateComplete;
|
||||
this._i18n.loadFragmentTranslation("config");
|
||||
const flows = await getConfigFlowHandlers(this._api.callApi, "helper");
|
||||
await this._i18n.loadBackendTranslation("title", flows, true);
|
||||
this.hass.loadFragmentTranslation("config");
|
||||
const flows = await getConfigFlowHandlers(this.hass, "helper");
|
||||
await this.hass.loadBackendTranslation("title", flows, true);
|
||||
// Ensure the titles are loaded before we render the flows.
|
||||
this._helperFlows = flows;
|
||||
|
||||
@@ -246,13 +214,13 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
this._focusPicker();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
protected updated(changedProps: PropertyValues<this>): void {
|
||||
super.updated(changedProps);
|
||||
const oldI18n = changedProps.get("_i18n") as
|
||||
HomeAssistantInternationalization | undefined;
|
||||
const oldHass = changedProps.get("hass");
|
||||
if (
|
||||
(oldI18n && oldI18n.localize !== this._i18n.localize) ||
|
||||
changedProps.get("_hassConfig") !== undefined
|
||||
oldHass &&
|
||||
(oldHass.localize !== this.hass.localize ||
|
||||
oldHass.config !== this.hass.config)
|
||||
) {
|
||||
this._comboBox?.refreshItems();
|
||||
}
|
||||
@@ -304,6 +272,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
<div class="form" @value-changed=${this._valueChanged}>
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : ""}
|
||||
${dynamicElement(`ha-${this._domain}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
new: true,
|
||||
autofocus: true,
|
||||
@@ -326,7 +295,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
@click=${this._goBack}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this._i18n.localize("ui.common.back")}
|
||||
${this.hass!.localize("ui.common.back")}
|
||||
</ha-button>`
|
||||
}
|
||||
<ha-button
|
||||
@@ -334,7 +303,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
@click=${this._createItem}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this._i18n.localize("ui.panel.config.helpers.dialog.create")}
|
||||
${this.hass!.localize("ui.panel.config.helpers.dialog.create")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
`;
|
||||
@@ -348,16 +317,16 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
mode="dialog"
|
||||
.shown=${this._shown}
|
||||
no-sort
|
||||
.label=${this._i18n.localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.search_helper"
|
||||
)}
|
||||
.getItems=${this._getItems}
|
||||
.rowRenderer=${this._renderHelperRow}
|
||||
.sections=${this._getSections(
|
||||
this._helperFlows,
|
||||
this._i18n.localize,
|
||||
this._hassConfig,
|
||||
this._i18n.locale.language
|
||||
this.hass.localize,
|
||||
this.hass.config,
|
||||
this.hass.locale.language
|
||||
)}
|
||||
.selectedSection=${this._category}
|
||||
@index-selected=${this._helperPicked}
|
||||
@@ -373,12 +342,12 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
header-title=${
|
||||
this._domain
|
||||
? this._i18n.localize(
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.helpers.dialog.create_platform",
|
||||
{
|
||||
platform:
|
||||
(isHelperDomain(this._domain) &&
|
||||
this._i18n.localize(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${
|
||||
this._domain as HelperDomain
|
||||
}`
|
||||
@@ -386,9 +355,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
this._domain,
|
||||
}
|
||||
)
|
||||
: this._i18n.localize(
|
||||
"ui.panel.config.helpers.dialog.create_helper"
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.helpers.dialog.create_helper")
|
||||
}
|
||||
@after-show=${this._dialogShown}
|
||||
@closed=${this._dialogClosed}
|
||||
@@ -474,9 +441,9 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
this._search = search;
|
||||
const allItems = this._getHelperItems(
|
||||
this._helperFlows ?? [],
|
||||
this._i18n.localize,
|
||||
this._hassConfig,
|
||||
this._i18n.locale.language
|
||||
this.hass.localize,
|
||||
this.hass.config,
|
||||
this.hass.locale.language
|
||||
);
|
||||
const items = search
|
||||
? multiTermSortedSearch(
|
||||
@@ -496,7 +463,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
const categoryItems = items.filter((item) => item.category === category);
|
||||
return categoryItems.length
|
||||
? [
|
||||
this._i18n.localize(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.helpers.dialog.categories.${category}`
|
||||
),
|
||||
...categoryItems,
|
||||
@@ -531,7 +498,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
@click=${stopPropagation}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip slot="end" .for="icon-${domain}">
|
||||
${this._i18n.localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
{ platform: domain }
|
||||
)}
|
||||
@@ -556,10 +523,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
this._submitting = true;
|
||||
this._error = "";
|
||||
try {
|
||||
const createdEntity = await HELPERS[domain].create(
|
||||
this._api.callWS,
|
||||
this._item
|
||||
);
|
||||
const createdEntity = await HELPERS[domain].create(this.hass, this._item);
|
||||
// The dialog was closed or reopened while waiting
|
||||
if (this._params !== params) {
|
||||
return;
|
||||
@@ -601,13 +565,13 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
try {
|
||||
// The entity ID usually matches the helper ID, unless it was taken
|
||||
const entry = await getExtendedEntityRegistryEntry(
|
||||
this._api,
|
||||
this.hass,
|
||||
`${domain}.${uniqueId}`
|
||||
).catch(() => undefined);
|
||||
if (entry && isCreated(entry)) {
|
||||
return entry.entity_id;
|
||||
}
|
||||
return (await fetchEntityRegistry(this._connection)).find(isCreated)
|
||||
return (await fetchEntityRegistry(this.hass.connection)).find(isCreated)
|
||||
?.entity_id;
|
||||
} catch (_err) {
|
||||
return undefined;
|
||||
@@ -621,7 +585,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
let error = "Entity not found";
|
||||
if (entityId) {
|
||||
try {
|
||||
return (await updateEntitiesArea(this._api.callWS, [entityId], areaId))[
|
||||
return (await updateEntitiesArea(this.hass, [entityId], areaId))[
|
||||
entityId
|
||||
];
|
||||
} catch (err: unknown) {
|
||||
@@ -629,7 +593,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
}
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
text: this._i18n.localize(
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error_saving_entity",
|
||||
{ error }
|
||||
),
|
||||
@@ -650,7 +614,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
const { id: domain, loaded } = ev.detail.item as HelperPickerItem;
|
||||
if (!loaded) {
|
||||
showAlertDialog(this, {
|
||||
text: this._i18n.localize(
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
{ platform: domain }
|
||||
),
|
||||
@@ -673,7 +637,7 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
} else {
|
||||
this._pendingConfigFlow = {
|
||||
startFlowHandler: domain,
|
||||
manifest: await fetchIntegrationManifest(this._api, domain),
|
||||
manifest: await fetchIntegrationManifest(this.hass, domain),
|
||||
dialogClosedCallback: this._params?.dialogClosedCallback,
|
||||
navigateToResult: this._params?.navigateToResult,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-switch";
|
||||
@@ -11,12 +9,11 @@ import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/input/ha-input";
|
||||
import type { Counter } from "../../../../data/counter";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-counter-form")
|
||||
class HaCounterForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -66,7 +63,7 @@ class HaCounterForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -76,10 +73,12 @@ class HaCounterForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -89,7 +88,9 @@ class HaCounterForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-input
|
||||
@@ -97,7 +98,9 @@ class HaCounterForm extends LitElement {
|
||||
.configValue=${"minimum"}
|
||||
type="number"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.counter.minimum")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.minimum"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-input>
|
||||
<ha-input
|
||||
@@ -105,7 +108,9 @@ class HaCounterForm extends LitElement {
|
||||
.configValue=${"maximum"}
|
||||
type="number"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.counter.maximum")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.maximum"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-input>
|
||||
<ha-input
|
||||
@@ -113,11 +118,13 @@ class HaCounterForm extends LitElement {
|
||||
.configValue=${"initial"}
|
||||
type="number"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.counter.initial")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.initial"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-input>
|
||||
<ha-expansion-panel
|
||||
header=${this._localize(
|
||||
header=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
)}
|
||||
outlined
|
||||
@@ -127,7 +134,9 @@ class HaCounterForm extends LitElement {
|
||||
.configValue=${"step"}
|
||||
type="number"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.counter.step")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.step"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-input>
|
||||
<div class="row">
|
||||
@@ -139,7 +148,9 @@ class HaCounterForm extends LitElement {
|
||||
>
|
||||
</ha-switch>
|
||||
<div>
|
||||
${this._localize("ui.dialogs.helper_settings.counter.restore")}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.counter.restore"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
|
||||
@@ -2,9 +2,7 @@ import { mdiInformationOutline } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
@@ -16,12 +14,11 @@ import type { HaRadioGroup } from "../../../../components/radio/ha-radio-group";
|
||||
import "../../../../components/radio/ha-radio-option";
|
||||
import type { InputBoolean } from "../../../../data/input_boolean";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-input_boolean-form")
|
||||
class HaInputBooleanForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -55,7 +52,7 @@ class HaInputBooleanForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -65,10 +62,12 @@ class HaInputBooleanForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -78,11 +77,13 @@ class HaInputBooleanForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-expansion-panel
|
||||
header=${this._localize(
|
||||
header=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
)}
|
||||
outlined
|
||||
@@ -100,7 +101,7 @@ class HaInputBooleanForm extends LitElement {
|
||||
@change=${this._initialChanged}
|
||||
>
|
||||
<span slot="label">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.initial"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
@@ -112,23 +113,23 @@ class HaInputBooleanForm extends LitElement {
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
<ha-radio-option value="restore">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.restore"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="on">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.turn_on"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="off">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.turn_off"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
</ha-radio-group>
|
||||
<ha-tooltip for="initial-note" placement="top">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.initial_helper"
|
||||
)}
|
||||
</ha-tooltip>
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/input/ha-input";
|
||||
import type { InputButton } from "../../../../data/input_button";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-input_button-form")
|
||||
class HaInputButtonForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -43,7 +40,7 @@ class HaInputButtonForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -53,10 +50,12 @@ class HaInputButtonForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -66,7 +65,9 @@ class HaInputButtonForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/input/ha-input";
|
||||
import "../../../../components/radio/ha-radio-group";
|
||||
@@ -11,12 +9,11 @@ import type { HaRadioGroup } from "../../../../components/radio/ha-radio-group";
|
||||
import "../../../../components/radio/ha-radio-option";
|
||||
import type { InputDateTime } from "../../../../data/input_datetime";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-input_datetime-form")
|
||||
class HaInputDateTimeForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -57,7 +54,7 @@ class HaInputDateTimeForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -67,10 +64,12 @@ class HaInputDateTimeForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -80,11 +79,13 @@ class HaInputDateTimeForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-radio-group
|
||||
.label=${this._localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.mode"
|
||||
)}
|
||||
.value=${this._mode}
|
||||
@@ -93,13 +94,17 @@ class HaInputDateTimeForm extends LitElement {
|
||||
@change=${this._modeChanged}
|
||||
>
|
||||
<ha-radio-option value="date">
|
||||
${this._localize("ui.dialogs.helper_settings.input_datetime.date")}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.date"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="time">
|
||||
${this._localize("ui.dialogs.helper_settings.input_datetime.time")}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.time"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="datetime">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.datetime"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
|
||||
@@ -2,9 +2,7 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-device-class-picker";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/radio/ha-radio-group";
|
||||
@@ -15,7 +13,7 @@ import "../../../../components/input/ha-input";
|
||||
import type { InputNumber } from "../../../../data/input_number";
|
||||
import type { UnitOfMeasurementSelector } from "../../../../data/selector";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { ValueChangedEvent } from "../../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
|
||||
const UNIT_SELECTOR: UnitOfMeasurementSelector = {
|
||||
unit_of_measurement: {},
|
||||
@@ -23,9 +21,7 @@ const UNIT_SELECTOR: UnitOfMeasurementSelector = {
|
||||
|
||||
@customElement("ha-input_number-form")
|
||||
class HaInputNumberForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -85,7 +81,7 @@ class HaInputNumberForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -95,10 +91,12 @@ class HaInputNumberForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -108,7 +106,9 @@ class HaInputNumberForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-input
|
||||
@@ -117,7 +117,7 @@ class HaInputNumberForm extends LitElement {
|
||||
type="number"
|
||||
step="any"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.min"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@@ -128,7 +128,7 @@ class HaInputNumberForm extends LitElement {
|
||||
type="number"
|
||||
step="any"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.max"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@@ -137,7 +137,7 @@ class HaInputNumberForm extends LitElement {
|
||||
<ha-radio-group
|
||||
orientation="horizontal"
|
||||
class="mode"
|
||||
.label=${this._localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.mode"
|
||||
)}
|
||||
.value=${this._mode}
|
||||
@@ -146,10 +146,12 @@ class HaInputNumberForm extends LitElement {
|
||||
@change=${this._modeChanged}
|
||||
>
|
||||
<ha-radio-option value="slider">
|
||||
${this._localize("ui.dialogs.helper_settings.input_number.slider")}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.slider"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="box">
|
||||
${this._localize("ui.dialogs.helper_settings.input_number.box")}
|
||||
${this.hass.localize("ui.dialogs.helper_settings.input_number.box")}
|
||||
</ha-radio-option>
|
||||
</ha-radio-group>
|
||||
<ha-input
|
||||
@@ -158,7 +160,7 @@ class HaInputNumberForm extends LitElement {
|
||||
type="number"
|
||||
step="any"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.step"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@@ -172,7 +174,7 @@ class HaInputNumberForm extends LitElement {
|
||||
@value-changed=${this._pickerValueChanged}
|
||||
></ha-device-class-picker>
|
||||
<ha-selector-unit_of_measurement
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.unit_of_measurement"
|
||||
)}
|
||||
.selector=${UNIT_SELECTOR}
|
||||
|
||||
@@ -3,9 +3,7 @@ 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 { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { afterNextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
@@ -19,12 +17,11 @@ import type { HaInput } from "../../../../components/input/ha-input";
|
||||
import type { InputSelect } from "../../../../data/input_select";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-input_select-form")
|
||||
class HaInputSelectForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -96,7 +93,7 @@ class HaInputSelectForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -106,11 +103,13 @@ class HaInputSelectForm extends LitElement {
|
||||
dialogInitialFocus
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
.value=${this._name}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
@@ -119,11 +118,15 @@ class HaInputSelectForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<div class="header">
|
||||
${this._localize("ui.dialogs.helper_settings.input_select.options")}:
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.options"
|
||||
)}:
|
||||
</div>
|
||||
<ha-sortable
|
||||
@item-moved=${this._optionMoved}
|
||||
@@ -132,7 +135,7 @@ class HaInputSelectForm extends LitElement {
|
||||
>
|
||||
<ha-list-base
|
||||
class="options"
|
||||
aria-label=${this._localize(
|
||||
aria-label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.options"
|
||||
)}
|
||||
>
|
||||
@@ -157,7 +160,7 @@ class HaInputSelectForm extends LitElement {
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.index=${index}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.remove_option"
|
||||
)}
|
||||
@click=${this._removeOption}
|
||||
@@ -175,7 +178,7 @@ class HaInputSelectForm extends LitElement {
|
||||
<ha-input
|
||||
class="flex-auto"
|
||||
id="option_input"
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add_option"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
@@ -186,7 +189,9 @@ class HaInputSelectForm extends LitElement {
|
||||
appearance="filled"
|
||||
@click=${this._addOption}
|
||||
.disabled=${this.disabled}
|
||||
>${this._localize("ui.dialogs.helper_settings.input_select.add")}
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
@@ -217,10 +222,10 @@ class HaInputSelectForm extends LitElement {
|
||||
const index = (ev.target as any).index;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this._localize(
|
||||
title: this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.confirm_delete.delete"
|
||||
),
|
||||
text: this._localize(
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.confirm_delete.prompt"
|
||||
),
|
||||
destructive: true,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
@@ -13,12 +11,11 @@ import "../../../../components/radio/ha-radio-option";
|
||||
import "../../../../components/input/ha-input";
|
||||
import type { InputText } from "../../../../data/input_text";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-input_text-form")
|
||||
class HaInputTextForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -63,7 +60,7 @@ class HaInputTextForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -73,10 +70,12 @@ class HaInputTextForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -86,11 +85,13 @@ class HaInputTextForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-expansion-panel
|
||||
header=${this._localize(
|
||||
header=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
)}
|
||||
outlined
|
||||
@@ -102,7 +103,7 @@ class HaInputTextForm extends LitElement {
|
||||
min="0"
|
||||
max="255"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.min"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@@ -114,13 +115,13 @@ class HaInputTextForm extends LitElement {
|
||||
max="255"
|
||||
type="number"
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.max"
|
||||
)}
|
||||
></ha-input>
|
||||
<ha-radio-group
|
||||
orientation="horizontal"
|
||||
.label=${this._localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.mode"
|
||||
)}
|
||||
.value=${this._mode}
|
||||
@@ -129,10 +130,12 @@ class HaInputTextForm extends LitElement {
|
||||
@change=${this._modeChanged}
|
||||
>
|
||||
<ha-radio-option value="text">
|
||||
${this._localize("ui.dialogs.helper_settings.input_text.text")}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.text"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="password">
|
||||
${this._localize(
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.password"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
@@ -141,10 +144,10 @@ class HaInputTextForm extends LitElement {
|
||||
.value=${this._pattern || ""}
|
||||
.configValue=${"pattern"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.pattern_label"
|
||||
)}
|
||||
.hint=${this._localize(
|
||||
.hint=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.pattern_helper"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -9,7 +9,6 @@ import allLocales from "fullcalendar/locales-all";
|
||||
import interactionPlugin from "fullcalendar/interaction";
|
||||
import timeGridPlugin from "fullcalendar/timegrid";
|
||||
import type { Day } from "date-fns";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import { addDays, isSameDay, isSameWeek, nextDay } from "date-fns";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -17,15 +16,9 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { firstWeekdayIndex } from "../../../../common/datetime/first_weekday";
|
||||
import { formatTime24h } from "../../../../common/datetime/format_time";
|
||||
import { useAmPm } from "../../../../common/datetime/use_am_pm";
|
||||
import { consume } from "../../../../common/decorators/consume";
|
||||
import { transform } from "../../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/input/ha-input";
|
||||
import {
|
||||
configContext,
|
||||
internationalizationContext,
|
||||
} from "../../../../data/context";
|
||||
import type { Schedule, ScheduleDay } from "../../../../data/schedule";
|
||||
import { weekdays } from "../../../../data/schedule";
|
||||
import { TimeZone } from "../../../../data/translation";
|
||||
@@ -34,10 +27,7 @@ import {
|
||||
fullCalendarTheme,
|
||||
} from "../../../../resources/fullcalendar-theme";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type {
|
||||
HomeAssistantConfig,
|
||||
HomeAssistantInternationalization,
|
||||
} from "../../../../types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showScheduleBlockInfoDialog } from "./show-dialog-schedule-block-info";
|
||||
|
||||
const defaultFullCalendarConfig: CalendarOptions = {
|
||||
@@ -62,15 +52,7 @@ const defaultFullCalendarConfig: CalendarOptions = {
|
||||
|
||||
@customElement("ha-schedule-form")
|
||||
class HaScheduleForm extends LitElement {
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: HomeAssistantInternationalization;
|
||||
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
@transform<HomeAssistantConfig, HassConfig>({
|
||||
transformer: ({ config }) => config,
|
||||
})
|
||||
private _hassConfig!: HassConfig;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -143,7 +125,7 @@ class HaScheduleForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._i18n) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -153,12 +135,12 @@ class HaScheduleForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._i18n.localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._i18n.localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -168,7 +150,7 @@ class HaScheduleForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._i18n.localize(
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@@ -198,11 +180,10 @@ class HaScheduleForm extends LitElement {
|
||||
this.calendar.addEventSource(this._events);
|
||||
}
|
||||
|
||||
const oldI18n = changedProps.get("_i18n") as
|
||||
HomeAssistantInternationalization | undefined;
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant;
|
||||
|
||||
if (oldI18n && oldI18n.language !== this._i18n.language) {
|
||||
this.calendar.setOption("locale", this._i18n.language);
|
||||
if (oldHass && oldHass.language !== this.hass.language) {
|
||||
this.calendar.setOption("locale", this.hass.language);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,19 +196,19 @@ class HaScheduleForm extends LitElement {
|
||||
private _setupCalendar(): void {
|
||||
const config: CalendarOptions = {
|
||||
...defaultFullCalendarConfig,
|
||||
locale: this._i18n.language,
|
||||
firstDay: firstWeekdayIndex(this._i18n.locale),
|
||||
locale: this.hass.language,
|
||||
firstDay: firstWeekdayIndex(this.hass.locale),
|
||||
slotHeaderFormat: {
|
||||
hour: "numeric",
|
||||
minute: undefined,
|
||||
hour12: useAmPm(this._i18n.locale),
|
||||
meridiem: useAmPm(this._i18n.locale) ? "narrow" : false,
|
||||
hour12: useAmPm(this.hass.locale),
|
||||
meridiem: useAmPm(this.hass.locale) ? "narrow" : false,
|
||||
},
|
||||
eventTimeFormat: {
|
||||
hour: useAmPm(this._i18n.locale) ? "numeric" : "2-digit",
|
||||
minute: useAmPm(this._i18n.locale) ? "numeric" : "2-digit",
|
||||
hour12: useAmPm(this._i18n.locale),
|
||||
meridiem: useAmPm(this._i18n.locale) ? "narrow" : false,
|
||||
hour: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
|
||||
minute: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
|
||||
hour12: useAmPm(this.hass.locale),
|
||||
meridiem: useAmPm(this.hass.locale) ? "narrow" : false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -256,7 +237,7 @@ class HaScheduleForm extends LitElement {
|
||||
let date = nextDay(new Date(), i as Day);
|
||||
if (
|
||||
!isSameWeek(date, new Date(), {
|
||||
weekStartsOn: firstWeekdayIndex(this._i18n.locale),
|
||||
weekStartsOn: firstWeekdayIndex(this.hass.locale),
|
||||
})
|
||||
) {
|
||||
date = addDays(date, -7);
|
||||
@@ -295,14 +276,14 @@ class HaScheduleForm extends LitElement {
|
||||
// Schedule is timezone unaware, we need to format it in local time
|
||||
const endFormatted = formatTime24h(
|
||||
end,
|
||||
{ ...this._i18n.locale, time_zone: TimeZone.local },
|
||||
this._hassConfig
|
||||
{ ...this.hass.locale, time_zone: TimeZone.local },
|
||||
this.hass.config
|
||||
);
|
||||
value.push({
|
||||
from: formatTime24h(
|
||||
start,
|
||||
{ ...this._i18n.locale, time_zone: TimeZone.local },
|
||||
this._hassConfig
|
||||
{ ...this.hass.locale, time_zone: TimeZone.local },
|
||||
this.hass.config
|
||||
),
|
||||
to:
|
||||
!isSameDay(start, end) || endFormatted === "0:00"
|
||||
@@ -328,11 +309,7 @@ class HaScheduleForm extends LitElement {
|
||||
const value = this[`_${day}`][parseInt(index)];
|
||||
const newValue = { ...this._item };
|
||||
|
||||
const endFormatted = formatTime24h(
|
||||
end,
|
||||
this._i18n.locale,
|
||||
this._hassConfig
|
||||
);
|
||||
const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config);
|
||||
newValue[day] = [...newValue[day]];
|
||||
newValue[day][index] = {
|
||||
...newValue[day][index],
|
||||
@@ -360,14 +337,10 @@ class HaScheduleForm extends LitElement {
|
||||
const newDay = weekdays[start.getDay()];
|
||||
const newValue = { ...this._item };
|
||||
|
||||
const endFormatted = formatTime24h(
|
||||
end,
|
||||
this._i18n.locale,
|
||||
this._hassConfig
|
||||
);
|
||||
const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config);
|
||||
const event = {
|
||||
...newValue[day][index],
|
||||
from: formatTime24h(start, this._i18n.locale, this._hassConfig),
|
||||
from: formatTime24h(start, this.hass.locale, this.hass.config),
|
||||
to:
|
||||
!isSameDay(start, end) || endFormatted === "0:00"
|
||||
? "24:00"
|
||||
|
||||
@@ -2,9 +2,7 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { createDurationData } from "../../../../common/datetime/create_duration_data";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-checkbox";
|
||||
import "../../../../components/ha-duration-input";
|
||||
import type { HaDurationData } from "../../../../components/ha-duration-input";
|
||||
@@ -13,12 +11,11 @@ import "../../../../components/input/ha-input";
|
||||
import type { ForDict } from "../../../../data/automation";
|
||||
import type { DurationDict, Timer } from "../../../../data/timer";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-timer-form")
|
||||
class HaTimerForm extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@@ -60,7 +57,7 @@ class HaTimerForm extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._localize) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -70,10 +67,12 @@ class HaTimerForm extends LitElement {
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
auto-validate
|
||||
required
|
||||
.validationMessage=${this._localize(
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -83,7 +82,9 @@ class HaTimerForm extends LitElement {
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this._localize("ui.dialogs.helper_settings.generic.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-duration-input
|
||||
@@ -98,7 +99,7 @@ class HaTimerForm extends LitElement {
|
||||
@change=${this._toggleRestore}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this._localize("ui.dialogs.helper_settings.timer.restore")}
|
||||
${this.hass.localize("ui.dialogs.helper_settings.timer.restore")}
|
||||
</ha-checkbox>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -747,7 +747,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
<ha-filter-floor-areas
|
||||
.type=${"entity"}
|
||||
include-disabled-entities
|
||||
.value=${this._filters["ha-filter-floor-areas"]}
|
||||
@data-table-filter-changed=${this._filterChanged}
|
||||
slot="filter-pane"
|
||||
@@ -1163,7 +1162,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
|
||||
this._selected.forEach((entityId) => {
|
||||
promises.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(this.hass, entityId, {
|
||||
categories: { helpers: category },
|
||||
})
|
||||
);
|
||||
@@ -1201,7 +1200,7 @@ ${rejected
|
||||
this._selected.forEach((entityId) => {
|
||||
const labels = this._labelsForEntity(entityId);
|
||||
promises.push(
|
||||
updateEntityRegistryEntry(this.hass.callWS, entityId, {
|
||||
updateEntityRegistryEntry(this.hass, entityId, {
|
||||
labels:
|
||||
action === "add"
|
||||
? labels.concat(label)
|
||||
@@ -1307,11 +1306,11 @@ ${rejected
|
||||
});
|
||||
return;
|
||||
}
|
||||
const handlers = await getConfigFlowHandlers(this.hass.callApi, "helper");
|
||||
const handlers = await getConfigFlowHandlers(this.hass, "helper");
|
||||
|
||||
if (!handlers.includes(domain)) {
|
||||
const integrations = await getConfigFlowHandlers(
|
||||
this.hass.callApi,
|
||||
this.hass,
|
||||
"integration"
|
||||
);
|
||||
if (integrations.includes(domain)) {
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCheckCircle,
|
||||
mdiInformation,
|
||||
mdiAlert,
|
||||
} from "@mdi/js";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
import { consumeEntityStates } from "../../../common/decorators/consume-context-entry";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/item/ha-list-item-base";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import type { ConfigEntry } from "../../../data/config_entries";
|
||||
import { fetchConfigFlowInProgress } from "../../../data/config_flow";
|
||||
import {
|
||||
apiContext,
|
||||
configEntriesContext,
|
||||
connectionContext,
|
||||
internationalizationContext,
|
||||
} from "../../../data/context";
|
||||
import type {
|
||||
ConfigEntryHealth,
|
||||
HealthStatus,
|
||||
} from "../../../data/config_entry_health";
|
||||
import { computeConfigEntryHealth } from "../../../data/config_entry_health";
|
||||
import {
|
||||
domainToName,
|
||||
fetchIntegrationSetups,
|
||||
} from "../../../data/integration";
|
||||
import type { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||
import type { IntegrationSetup } from "../../../data/integration";
|
||||
import type { RepairsIssue } from "../../../data/repairs";
|
||||
import { fetchRepairsIssues } from "../../../data/repairs";
|
||||
import type { LoggedError } from "../../../data/system_log";
|
||||
import { fetchSystemLog } from "../../../data/system_log";
|
||||
import { DialogMixin } from "../../../dialogs/dialog-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { renderConfigEntryError } from "./ha-config-integration-page";
|
||||
import type { ConfigEntryHealthDialogParams } from "./show-dialog-config-entry-health";
|
||||
|
||||
const STATUS_ICONS: Record<HealthStatus, string> = {
|
||||
ok: mdiCheckCircle,
|
||||
warning: mdiAlert,
|
||||
error: mdiAlertCircle,
|
||||
info: mdiInformation,
|
||||
};
|
||||
|
||||
const MAX_LISTED = 5;
|
||||
|
||||
interface HealthData {
|
||||
issues: RepairsIssue[];
|
||||
log: LoggedError[];
|
||||
flows: DataEntryFlowProgress[];
|
||||
setups: IntegrationSetup[];
|
||||
}
|
||||
|
||||
@customElement("dialog-config-entry-health")
|
||||
class DialogConfigEntryHealth extends DialogMixin<ConfigEntryHealthDialogParams>(
|
||||
LitElement
|
||||
) {
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@consume({ context: connectionContext, subscribe: true })
|
||||
private _connection!: ContextType<typeof connectionContext>;
|
||||
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
// Read by the @consumeEntityStates decorator below.
|
||||
@state() protected _entityIds: string[] = [];
|
||||
|
||||
// Only this entry's entities, so availability stays live while open.
|
||||
@state()
|
||||
@consumeEntityStates({ entityIdPath: ["_entityIds"] })
|
||||
private _states: HassEntities = {};
|
||||
|
||||
// params.entry is a snapshot; updates replace the entry object.
|
||||
@state()
|
||||
@consume({ context: configEntriesContext, subscribe: true })
|
||||
@transform<ConfigEntry[], ConfigEntry | undefined>({
|
||||
transformer: function (this: DialogConfigEntryHealth, entries) {
|
||||
return entries?.find(
|
||||
(entry) => entry.entry_id === this.params?.entry.entry_id
|
||||
);
|
||||
},
|
||||
})
|
||||
private _entry?: ConfigEntry;
|
||||
|
||||
@state() private _data?: HealthData;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _computeHealth = memoizeOne(
|
||||
(
|
||||
entry: ConfigEntry,
|
||||
entities: ConfigEntryHealthDialogParams["entities"],
|
||||
loggers: string[] | undefined,
|
||||
states: HassEntities,
|
||||
data: HealthData
|
||||
) =>
|
||||
computeConfigEntryHealth(
|
||||
entry,
|
||||
entities,
|
||||
states,
|
||||
data.issues,
|
||||
data.log,
|
||||
data.flows,
|
||||
data.setups,
|
||||
loggers
|
||||
)
|
||||
);
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._entityIds = this.params!.entities.map((entity) => entity.entity_id);
|
||||
this._load();
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
try {
|
||||
const [{ issues }, log, flows, setups] = await Promise.all([
|
||||
fetchRepairsIssues(this._connection.connection),
|
||||
fetchSystemLog(this._api.callWS),
|
||||
fetchConfigFlowInProgress(this._connection.connection),
|
||||
fetchIntegrationSetups(this._api.callWS),
|
||||
]);
|
||||
this._data = { issues, log, flows, setups };
|
||||
} catch (err: any) {
|
||||
// WebSocket errors nest their message.
|
||||
this._error =
|
||||
err?.error?.message ||
|
||||
err?.message ||
|
||||
this._i18n.localize(
|
||||
"ui.panel.config.integrations.config_entry.health.load_failed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.params) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
header-title=${this._i18n.localize(
|
||||
"ui.panel.config.integrations.config_entry.health.title",
|
||||
{ title: this._currentEntry.title }
|
||||
)}
|
||||
>
|
||||
${
|
||||
this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: this._data
|
||||
? this._renderHealth(
|
||||
this._computeHealth(
|
||||
this._currentEntry,
|
||||
this.params.entities,
|
||||
this.params.manifest?.loggers,
|
||||
this._states,
|
||||
this._data
|
||||
)
|
||||
)
|
||||
: html`<ha-spinner></ha-spinner>`
|
||||
}
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this._i18n.localize("ui.common.close")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _currentEntry(): ConfigEntry {
|
||||
return this._entry ?? this.params!.entry;
|
||||
}
|
||||
|
||||
private _renderHealth(health: ConfigEntryHealth) {
|
||||
const entry = this._currentEntry;
|
||||
const key = "ui.panel.config.integrations.config_entry.health";
|
||||
// Repairs and log lines are only known per integration, not per entry.
|
||||
const integration = domainToName(this._i18n.localize, entry.domain);
|
||||
const entityName = (entityId: string) =>
|
||||
this._states[entityId]?.attributes.friendly_name ?? entityId;
|
||||
const more = (count: number) =>
|
||||
count > MAX_LISTED
|
||||
? html`<li>
|
||||
${this._i18n.localize(`${key}.and_more`, {
|
||||
count: count - MAX_LISTED,
|
||||
})}
|
||||
</li>`
|
||||
: nothing;
|
||||
|
||||
return html`
|
||||
<ha-alert
|
||||
.alertType=${
|
||||
health.status === "ok"
|
||||
? "success"
|
||||
: health.status === "error"
|
||||
? "error"
|
||||
: "warning"
|
||||
}
|
||||
>
|
||||
${this._i18n.localize(`${key}.summary.${health.status}`)}
|
||||
</ha-alert>
|
||||
<ha-list-base>
|
||||
${this._row(
|
||||
health.state,
|
||||
this._i18n.localize(
|
||||
entry.disabled_by && entry.state !== "failed_unload"
|
||||
? `${key}.disabled`
|
||||
: `ui.panel.config.integrations.config_entry.state.${entry.state}`
|
||||
),
|
||||
entry.state === "failed_unload"
|
||||
? html`<p>
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
)}
|
||||
</p>`
|
||||
: health.state === "warning" || health.state === "error"
|
||||
? html`<p>${renderConfigEntryError(this._i18n, entry)}</p>`
|
||||
: nothing
|
||||
)}
|
||||
${
|
||||
health.flows.flows.length
|
||||
? this._row(
|
||||
health.flows.status,
|
||||
this._i18n.localize(`${key}.flows`),
|
||||
html`<a href="/config/integrations/dashboard">
|
||||
${this._i18n.localize(`${key}.flows_link`)}
|
||||
</a>`
|
||||
)
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
health.entities.total
|
||||
? this._row(
|
||||
health.entities.status,
|
||||
this._i18n.localize(`${key}.entities`, {
|
||||
available:
|
||||
health.entities.total - health.entities.unavailable.length,
|
||||
total: health.entities.total,
|
||||
}),
|
||||
health.entities.unavailable.length
|
||||
? html`<ul>
|
||||
${health.entities.unavailable
|
||||
.slice(0, MAX_LISTED)
|
||||
.map(
|
||||
(item) =>
|
||||
html`<li>${entityName(item.entity_id)}</li>`
|
||||
)}
|
||||
${more(health.entities.unavailable.length)}
|
||||
</ul>
|
||||
<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${entry.entry_id}`}
|
||||
>
|
||||
${this._i18n.localize(`${key}.entities_link`)}
|
||||
</a>`
|
||||
: nothing
|
||||
)
|
||||
: nothing
|
||||
}
|
||||
${this._row(
|
||||
health.repairs.status,
|
||||
this._i18n.localize(`${key}.repairs`, {
|
||||
count: health.repairs.issues.length,
|
||||
integration,
|
||||
}),
|
||||
health.repairs.issues.length
|
||||
? html`<a href="/config/repairs">
|
||||
${this._i18n.localize(`${key}.repairs_link`)}
|
||||
</a>`
|
||||
: nothing
|
||||
)}
|
||||
${this._row(
|
||||
health.logs.status,
|
||||
this._i18n.localize(`${key}.logs`, {
|
||||
errors: health.logs.errors,
|
||||
warnings: health.logs.warnings,
|
||||
integration,
|
||||
}),
|
||||
health.logs.errors || health.logs.warnings
|
||||
? html`<a
|
||||
href=${`/config/logs?integration=${encodeURIComponent(entry.domain)}`}
|
||||
>
|
||||
${this._i18n.localize(`${key}.logs_link`)}
|
||||
</a>`
|
||||
: nothing
|
||||
)}
|
||||
${
|
||||
health.setupSeconds !== undefined
|
||||
? this._row(
|
||||
"info",
|
||||
health.setupSeconds < 0.1
|
||||
? this._i18n.localize(`${key}.setup_time_fast`, {
|
||||
integration,
|
||||
})
|
||||
: this._i18n.localize(`${key}.setup_time`, {
|
||||
integration,
|
||||
seconds: health.setupSeconds.toFixed(1),
|
||||
})
|
||||
)
|
||||
: nothing
|
||||
}
|
||||
</ha-list-base>
|
||||
`;
|
||||
}
|
||||
|
||||
private _row(
|
||||
status: HealthStatus,
|
||||
text: string,
|
||||
details: TemplateResult | typeof nothing = nothing
|
||||
) {
|
||||
return html`<ha-list-item-base>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
class=${status}
|
||||
.path=${STATUS_ICONS[status]}
|
||||
></ha-svg-icon>
|
||||
<span slot="headline">${text}</span>
|
||||
${
|
||||
details === nothing
|
||||
? nothing
|
||||
: html`<div slot="supporting-text">${details}</div>`
|
||||
}
|
||||
</ha-list-item-base>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-spinner {
|
||||
display: block;
|
||||
margin: var(--ha-space-6) auto;
|
||||
}
|
||||
ha-list-base {
|
||||
margin-top: var(--ha-space-2);
|
||||
}
|
||||
/* Row items keep text on one line; errors and counts need to wrap. */
|
||||
[slot="headline"],
|
||||
[slot="supporting-text"] {
|
||||
white-space: normal;
|
||||
}
|
||||
[slot="supporting-text"] p,
|
||||
[slot="supporting-text"] ul {
|
||||
margin: 0;
|
||||
}
|
||||
[slot="supporting-text"] ul {
|
||||
padding-inline-start: var(--ha-space-5);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-svg-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
ha-svg-icon.ok {
|
||||
color: var(--success-color);
|
||||
}
|
||||
ha-svg-icon.warning {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
ha-svg-icon.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-svg-icon.info {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-config-entry-health": DialogConfigEntryHealth;
|
||||
}
|
||||
}
|
||||
@@ -255,11 +255,7 @@ class HaConfigEntryDeviceRow extends LitElement {
|
||||
showDeviceRegistryDetailDialog(this, {
|
||||
device: this.device,
|
||||
updateEntry: async (updates) => {
|
||||
await updateDeviceRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this.device.id,
|
||||
updates
|
||||
);
|
||||
await updateDeviceRegistryEntry(this.hass, this.device.id, updates);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -343,7 +339,7 @@ class HaConfigEntryDeviceRow extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
await updateDeviceRegistryEntry(this.hass.callWS, this.device.id, {
|
||||
await updateDeviceRegistryEntry(this.hass, this.device.id, {
|
||||
disabled_by: disable ? "user" : null,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
mdiHandExtendedOutline,
|
||||
mdiHeartPulse,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlus,
|
||||
mdiProgressHelper,
|
||||
@@ -49,7 +48,6 @@ import {
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import { groupDevicesByParent } from "../../../data/device/device_registry";
|
||||
import { showConfigEntryHealthDialog } from "./show-dialog-config-entry-health";
|
||||
import type { DiagnosticInfo } from "../../../data/diagnostics";
|
||||
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
@@ -337,12 +335,6 @@ export class HaConfigEntryRow extends LitElement {
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-dropdown-item value="health">
|
||||
<ha-svg-icon slot="icon" .path=${mdiHeartPulse}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.health.menu"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
${
|
||||
!item.disabled_by &&
|
||||
RECOVERABLE_STATES.includes(item.state) &&
|
||||
@@ -567,14 +559,6 @@ export class HaConfigEntryRow extends LitElement {
|
||||
this._handleReload();
|
||||
return;
|
||||
}
|
||||
if (value === "health") {
|
||||
showConfigEntryHealthDialog(this, {
|
||||
entry: this.data.entry,
|
||||
entities: this._getEntities(),
|
||||
manifest: this.manifest,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (value === "rename") {
|
||||
this._handleRename();
|
||||
return;
|
||||
|
||||
@@ -21,17 +21,13 @@ import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { QueryParamsController } from "../../../common/controllers/query-params-controller";
|
||||
import { ContextSubscriptionController } from "../../../common/decorators/consume";
|
||||
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
protocolIntegrationPicked,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import {
|
||||
caseInsensitiveStringCompare,
|
||||
stringCompare,
|
||||
} from "../../../common/string/compare";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
integrationQueryParamConfig,
|
||||
@@ -43,9 +39,7 @@ import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/input/ha-input-search";
|
||||
import type { HaInputSearch } from "../../../components/input/ha-input-search";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/item/ha-list-item-base";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
|
||||
@@ -65,9 +59,7 @@ import {
|
||||
getCoreLogFileDownloadUnavailableReason,
|
||||
getErrorLogDownloadUrl,
|
||||
} from "../../../data/error_log";
|
||||
import { manifestsContext } from "../../../data/context";
|
||||
import type {
|
||||
DomainManifestLookup,
|
||||
IntegrationLogInfo,
|
||||
IntegrationManifest,
|
||||
} from "../../../data/integration";
|
||||
@@ -117,7 +109,7 @@ export interface ConfigEntryData {
|
||||
const AI_SETTINGS_DOMAINS = ["mcp", "mcp_server"];
|
||||
|
||||
export const renderConfigEntryError = (
|
||||
hass: Pick<HomeAssistant, "localize" | "loadBackendTranslation">,
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry
|
||||
): TemplateResult => {
|
||||
if (entry.reason) {
|
||||
@@ -175,11 +167,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _manifest?: IntegrationManifest;
|
||||
|
||||
/** The manifests of all loaded integrations, to find the dependents. */
|
||||
@state() private _loadedManifests?: DomainManifestLookup;
|
||||
|
||||
private _manifestsConsumer?: ContextSubscriptionController<DomainManifestLookup>;
|
||||
|
||||
@state() private _extraConfigEntries?: ConfigEntry[];
|
||||
|
||||
@state() private _diagnosticHandler?: DiagnosticInfo;
|
||||
@@ -276,7 +263,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
this.hass.loadBackendTranslation("config", [this.domain]);
|
||||
this.hass.loadBackendTranslation("config_subentries", [this.domain]);
|
||||
this._extraConfigEntries = undefined;
|
||||
this._manifest = undefined;
|
||||
this._resetDebugLogReady();
|
||||
this._filter = "";
|
||||
this._subEntries = {};
|
||||
@@ -429,25 +415,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
this.domain
|
||||
);
|
||||
|
||||
// Without the dependents, a dependency looks like a YAML integration.
|
||||
const dependents =
|
||||
this._manifest && !this._manifest.config_flow
|
||||
? this._getDependents(
|
||||
this.domain,
|
||||
this._loadedManifests,
|
||||
this.hass.localize,
|
||||
this.hass.locale.language
|
||||
)
|
||||
: [];
|
||||
const showDependents = normalEntries.length === 0 && dependents.length > 0;
|
||||
// Hold back the YAML texts and "Add entry" until the manifest and the
|
||||
// dependents are known. Entries do not wait, so a failed request cannot
|
||||
// hide them.
|
||||
const dependentsLoading =
|
||||
normalEntries.length === 0 &&
|
||||
(!this._manifest ||
|
||||
(!this._manifest.config_flow && !this._loadedManifests));
|
||||
|
||||
const configPanel = isComponentLoaded(this.hass.config, this.domain)
|
||||
? getConfigPanelPath(this.domain, this.hass.panels)
|
||||
: undefined;
|
||||
@@ -458,8 +425,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const showAddEntry =
|
||||
this._manifest?.integration_type !== "hardware" &&
|
||||
!showDependents &&
|
||||
!dependentsLoading &&
|
||||
!setUpInPanel &&
|
||||
(!this._manifest?.single_config_entry ||
|
||||
(normalData.length === 0 && attentionData.length === 0));
|
||||
@@ -649,8 +614,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
${
|
||||
normalEntries.length === 0 &&
|
||||
!showDependents &&
|
||||
!dependentsLoading &&
|
||||
!setUpInPanel &&
|
||||
this._manifest &&
|
||||
!this._manifest.config_flow &&
|
||||
@@ -763,9 +726,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
${
|
||||
configPanel
|
||||
? html`<ha-button
|
||||
.appearance=${
|
||||
showAddEntry || canAddDevice ? "outlined" : "accent"
|
||||
}
|
||||
.appearance=${setUpInPanel ? "accent" : "outlined"}
|
||||
href=${`/${configPanel}`}
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -1014,21 +975,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
showDependents
|
||||
? html`<div class="section">
|
||||
<h3 class="section-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.integration_page.used_by"
|
||||
)}
|
||||
</h3>
|
||||
${this._renderDependents(dependents)}
|
||||
</div>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
showDependents ||
|
||||
dependentsLoading ||
|
||||
(setUpInPanel && normalEntries.length === 0)
|
||||
setUpInPanel && normalEntries.length === 0
|
||||
? nothing
|
||||
: html`<div class="section">
|
||||
<h3 class="section-header">
|
||||
@@ -1085,76 +1032,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
// Only an integration without a config flow needs the loaded manifests, so
|
||||
// other integration pages do not make the context fetch them.
|
||||
private _watchLoadedManifests(): void {
|
||||
if (this._manifestsConsumer) {
|
||||
return;
|
||||
}
|
||||
this._manifestsConsumer = new ContextSubscriptionController(
|
||||
this,
|
||||
manifestsContext,
|
||||
(manifests) => {
|
||||
this._loadedManifests = manifests;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _getDependents = memoizeOne(
|
||||
(
|
||||
domain: string,
|
||||
manifests: DomainManifestLookup | undefined,
|
||||
localize: LocalizeFunc,
|
||||
language: string
|
||||
) =>
|
||||
Object.values(manifests ?? {})
|
||||
.filter(
|
||||
// System integrations, like Default Configuration, are not useful to list.
|
||||
(manifest) =>
|
||||
manifest.integration_type !== "system" &&
|
||||
manifest.dependencies?.includes(domain)
|
||||
)
|
||||
// The manifests come in no fixed order.
|
||||
.sort((a, b) =>
|
||||
stringCompare(
|
||||
domainToName(localize, a.domain, a),
|
||||
domainToName(localize, b.domain, b),
|
||||
language
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private _renderDependents(dependents: IntegrationManifest[]) {
|
||||
return html`<ha-list-base>
|
||||
${dependents.map(
|
||||
(manifest) =>
|
||||
html`<ha-list-item-button
|
||||
class="dependent"
|
||||
href=${`/config/integrations/integration/${manifest.domain}`}
|
||||
>
|
||||
<img
|
||||
slot="start"
|
||||
alt=""
|
||||
src=${brandsUrl(
|
||||
{
|
||||
domain: manifest.domain,
|
||||
type: "icon@2x",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
},
|
||||
this.hass.auth.data.hassUrl
|
||||
)}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div slot="headline">
|
||||
${domainToName(this.hass.localize, manifest.domain, manifest)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>`
|
||||
)}
|
||||
</ha-list-base>`;
|
||||
}
|
||||
|
||||
private _onImageLoad(ev) {
|
||||
ev.target.style.display = "inline-block";
|
||||
}
|
||||
@@ -1195,9 +1072,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
this._manifest = await fetchIntegrationManifest(this.hass, this.domain);
|
||||
if (!this._manifest.config_flow) {
|
||||
this._watchLoadedManifests();
|
||||
}
|
||||
if (
|
||||
this._manifest.integration_type &&
|
||||
!["device", "hub", "service"].includes(this._manifest.integration_type)
|
||||
@@ -1874,17 +1748,6 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
ha-list-item-base.discovered {
|
||||
--ha-row-item-min-height: 72px;
|
||||
}
|
||||
ha-list-item-button.dependent {
|
||||
--ha-row-item-min-height: 72px;
|
||||
}
|
||||
ha-list-item-button.dependent + ha-list-item-button.dependent {
|
||||
border-top: var(--ha-border-width-sm) solid var(--divider-color);
|
||||
}
|
||||
ha-list-item-button.dependent img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
}
|
||||
ha-list-item-base.config_entry {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -243,14 +243,6 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
] => {
|
||||
const entryDomains = new Set(configEntries.map((entry) => entry.domain));
|
||||
|
||||
// An integration loaded as a dependency of another is not set up in YAML.
|
||||
const dependencies = new Set<string>();
|
||||
for (const component of components) {
|
||||
manifests[component.split(".")[0]]?.dependencies?.forEach((domain) =>
|
||||
dependencies.add(domain)
|
||||
);
|
||||
}
|
||||
|
||||
const domains = new Set<string>();
|
||||
|
||||
for (const component of components) {
|
||||
@@ -258,7 +250,6 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
const manifest = manifests[componentDomain];
|
||||
if (
|
||||
!entryDomains.has(componentDomain) &&
|
||||
!dependencies.has(componentDomain) &&
|
||||
manifest &&
|
||||
!manifest.config_flow &&
|
||||
(!manifest.integration_type ||
|
||||
|
||||
+8
-67
@@ -33,7 +33,6 @@ import {
|
||||
getConfigEntries,
|
||||
} from "../../../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import { domainToName } from "../../../../../data/integration";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
@@ -52,10 +51,6 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
|
||||
@state() private _configEntries: ConfigEntry[] = [];
|
||||
|
||||
// Config entries of the integrations that provide remote scanners
|
||||
// (for example ESPHome Bluetooth proxies), keyed by entry ID.
|
||||
@state() private _sourceConfigEntries: Record<string, ConfigEntry> = {};
|
||||
|
||||
@state() private _connectionAllocationData: BluetoothAllocationsData[] = [];
|
||||
|
||||
@state() private _scannerStates: Record<string, BluetoothScannerState> = {};
|
||||
@@ -79,45 +74,9 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadConfigEntries(): Promise<void> {
|
||||
const configEntries = await getConfigEntries(this.hass);
|
||||
const bluetoothEntries: ConfigEntry[] = [];
|
||||
const sourceEntries: Record<string, ConfigEntry> = {};
|
||||
for (const configEntry of configEntries) {
|
||||
if (configEntry.domain === "bluetooth") {
|
||||
bluetoothEntries.push(configEntry);
|
||||
} else {
|
||||
sourceEntries[configEntry.entry_id] = configEntry;
|
||||
}
|
||||
}
|
||||
this._configEntries = bluetoothEntries;
|
||||
this._sourceConfigEntries = sourceEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote scanners (such as ESPHome Bluetooth proxies) are configured by the
|
||||
* integration that provides them. The Bluetooth integration creates its own
|
||||
* device for a remote scanner, linked through `via_device_id` to the device
|
||||
* of the integration that provides it, so look there for that
|
||||
* integration's config entry.
|
||||
*/
|
||||
private _getSourceConfigEntry(
|
||||
device: DeviceRegistryEntry
|
||||
): ConfigEntry | undefined {
|
||||
const sourceDevice = device.via_device_id
|
||||
? this.hass.devices[device.via_device_id]
|
||||
: undefined;
|
||||
if (!sourceDevice) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
sourceDevice.primary_config_entry &&
|
||||
sourceDevice.primary_config_entry in this._sourceConfigEntries
|
||||
) {
|
||||
return this._sourceConfigEntries[sourceDevice.primary_config_entry];
|
||||
}
|
||||
return sourceDevice.config_entries
|
||||
.map((id) => this._sourceConfigEntries[id])
|
||||
.find((e) => e !== undefined);
|
||||
this._configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "bluetooth",
|
||||
});
|
||||
}
|
||||
|
||||
private async _subscribeBluetoothConnectionAllocations(): Promise<void> {
|
||||
@@ -330,10 +289,6 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
const scannerType: HaScannerType =
|
||||
scannerDetails?.scanner_type ?? "unknown";
|
||||
const isRemoteScanner = scannerType === "remote";
|
||||
// Remote scanners are configured through the integration providing them.
|
||||
const optionsEntry = isRemoteScanner
|
||||
? this._getSourceConfigEntry(device)
|
||||
: entry;
|
||||
const hasMismatch = scannerState && isScannerStateMismatch(scannerState);
|
||||
|
||||
const allocations = scannerDetails
|
||||
@@ -368,30 +323,16 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
<div slot="headline">${deviceName}</div>
|
||||
<div slot="supporting-text">${supportingParts.join(" · ")}</div>
|
||||
${
|
||||
optionsEntry &&
|
||||
(!isRemoteScanner ||
|
||||
(optionsEntry.supports_options && !optionsEntry.disabled_by))
|
||||
!isRemoteScanner
|
||||
? html`<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiCogOutline}
|
||||
.entry=${optionsEntry}
|
||||
.entry=${entry}
|
||||
@click=${this._openOptionFlow}
|
||||
@keydown=${stopKeydownEnterSpacePropagation}
|
||||
.label=${
|
||||
isRemoteScanner
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.bluetooth.source_option_flow",
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
optionsEntry.domain
|
||||
),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.bluetooth.option_flow"
|
||||
)
|
||||
}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.option_flow"
|
||||
)}
|
||||
></ha-icon-button>`
|
||||
: nothing
|
||||
}
|
||||
|
||||
+4
-7
@@ -309,7 +309,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
const areaChanged = area !== origArea;
|
||||
|
||||
if (nameChanged || areaChanged) {
|
||||
await updateDeviceRegistryEntry(this.hass.callWS, device.id, {
|
||||
await updateDeviceRegistryEntry(this.hass, device.id, {
|
||||
...(nameChanged && { name_by_user: name || null }),
|
||||
...(areaChanged && { area_id: area || null }),
|
||||
}).catch((err: Error) =>
|
||||
@@ -328,15 +328,12 @@ class DialogMatterAddDevice extends LitElement {
|
||||
.map((e) => e.entity_id);
|
||||
|
||||
if (entityIds.length) {
|
||||
const mapping = await getAutomaticEntityIds(
|
||||
this.hass.callWS,
|
||||
entityIds
|
||||
);
|
||||
const mapping = await getAutomaticEntityIds(this.hass, entityIds);
|
||||
await Promise.allSettled(
|
||||
Object.entries(mapping)
|
||||
.filter((entry): entry is [string, string] => !!entry[1])
|
||||
.map(([oldId, newId]) =>
|
||||
updateEntityRegistryEntry(this.hass.callWS, oldId, {
|
||||
updateEntityRegistryEntry(this.hass, oldId, {
|
||||
new_entity_id: newId,
|
||||
}).catch((err: Error) =>
|
||||
showAlertDialog(this, {
|
||||
@@ -358,7 +355,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
undefined;
|
||||
if (deviceClass !== origClass) {
|
||||
await updateEntityRegistryEntry(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
this._mainEntity.entity_id,
|
||||
{ device_class: deviceClass || null }
|
||||
).catch((err: Error) =>
|
||||
|
||||
+4
-1
@@ -64,7 +64,10 @@ class ThreadNetworkInfoPage extends LitElement {
|
||||
|
||||
this._otbrLoading = true;
|
||||
const otbrInfo = await getOTBRInfo(this.hass).catch(() => undefined);
|
||||
this._otbrInfo = findOTBRInfoForDataset(otbrInfo, this._dataset);
|
||||
const otbr = findOTBRInfoForDataset(otbrInfo, this._dataset);
|
||||
if (otbr?.active_dataset_tlvs?.includes(this._dataset.extended_pan_id)) {
|
||||
this._otbrInfo = otbr;
|
||||
}
|
||||
this._otbrLoading = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import { computeAreaName } from "../../../../../common/entity/compute_area_name";
|
||||
import { getDeviceArea } from "../../../../../common/entity/context/get_device_context";
|
||||
import type { Cluster, ZHADevice, ZHAGroup } from "../../../../../data/zha";
|
||||
import { ZHADeviceType } from "../../../../../data/zha";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
export const getZHADeviceAreaName = (
|
||||
device: ZHADevice,
|
||||
areas: HomeAssistant["areas"],
|
||||
devices: HomeAssistant["devices"]
|
||||
): string | undefined => {
|
||||
// The device registry is authoritative: an entry with no effective
|
||||
// area means the device has no area, even if ZHA still reports one
|
||||
// from its cached response. Resolve via the registry so a child
|
||||
// device inherits its parent's area, matching the ZHA network
|
||||
// visualization.
|
||||
const haDevice = devices[device.device_reg_id];
|
||||
const area = haDevice ? getDeviceArea(haDevice, areas, devices) : undefined;
|
||||
return area ? computeAreaName(area) : undefined;
|
||||
};
|
||||
|
||||
export const formatAsPaddedHex = (value: string | number): string => {
|
||||
let hex = value;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -43,11 +42,6 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
|
||||
private _wakeLock?: Promise<WakeLockSentinel>;
|
||||
|
||||
private _connection?: Connection;
|
||||
|
||||
// Set for the search in flight, so a pending subscription can tell it ended
|
||||
private _search?: object;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.route && this.route.path && this.route.path !== "") {
|
||||
@@ -200,34 +194,28 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
// The backend ends the subscription when the connection drops, and it is not
|
||||
// sent again, so end the search here and show "Search again"
|
||||
private _connectionLost = () => {
|
||||
this._unsubscribe();
|
||||
};
|
||||
|
||||
private _unsubscribe(): Promise<void> {
|
||||
this._active = false;
|
||||
this._search = undefined;
|
||||
this._connection?.removeEventListener("disconnected", this._connectionLost);
|
||||
this._connection = undefined;
|
||||
if (this._addDevicesTimeoutHandle) {
|
||||
clearTimeout(this._addDevicesTimeoutHandle);
|
||||
}
|
||||
let unsubscribed = Promise.resolve();
|
||||
if (this._subscribed) {
|
||||
unsubscribed = this._subscribed.then(
|
||||
(unsub) => unsub(),
|
||||
// A subscription that was cancelled or lost with the connection has
|
||||
// nothing to unsubscribe
|
||||
() => undefined
|
||||
);
|
||||
unsubscribed = this._subscribed.then((unsub) => unsub());
|
||||
this._subscribed = undefined;
|
||||
}
|
||||
this._wakeLock?.then((wakeLock) => wakeLock.release());
|
||||
return unsubscribed;
|
||||
}
|
||||
|
||||
private _deactivate(): void {
|
||||
this._active = false;
|
||||
if (this._addDevicesTimeoutHandle) {
|
||||
clearTimeout(this._addDevicesTimeoutHandle);
|
||||
}
|
||||
this._wakeLock?.then((wakeLock) => wakeLock.release());
|
||||
}
|
||||
|
||||
private _subscribe(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
@@ -237,35 +225,22 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
// session before starting the next one
|
||||
const unsubscribed = this._unsubscribe();
|
||||
this._active = true;
|
||||
const search = {};
|
||||
this._search = search;
|
||||
const data: any = { type: "zha/devices/permit", duration: 254 };
|
||||
if (this._ieeeAddress) {
|
||||
data.ieee = this._ieeeAddress;
|
||||
}
|
||||
const connection = this.hass.connection;
|
||||
this._connection = connection;
|
||||
connection.addEventListener("disconnected", this._connectionLost);
|
||||
this._subscribed = unsubscribed
|
||||
// A failed unsubscribe leaves nothing to wait for
|
||||
.catch(() => undefined)
|
||||
.then(() =>
|
||||
// Each subscription opens the network for joining and turns on ZHA
|
||||
// debug logging, so don't send it again after a reconnect
|
||||
connection.subscribeMessage(
|
||||
(message) => this._handleMessage(message),
|
||||
data,
|
||||
{
|
||||
resubscribe: false,
|
||||
// While offline, subscribeMessage waits for the connection. Don't
|
||||
// open the network if this search ended in the meantime
|
||||
preCheck: () => this._search === search,
|
||||
}
|
||||
data
|
||||
)
|
||||
);
|
||||
this._addDevicesTimeoutHandle = setTimeout(
|
||||
// End the subscription with the search, so debug logging ends too
|
||||
() => this._unsubscribe(),
|
||||
() => this._deactivate(),
|
||||
254000
|
||||
);
|
||||
if ("wakeLock" in navigator) {
|
||||
|
||||
Loaded 100 of 183 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user