mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 02:39:26 +00:00
Compare commits
391 Commits
20220322.0
...
fix-button
Author | SHA1 | Date | |
---|---|---|---|
![]() |
026d027386 | ||
![]() |
d0ead1fdb8 | ||
![]() |
b0e6c41238 | ||
![]() |
2c1550b10f | ||
![]() |
ffc4ca5b56 | ||
![]() |
85ad6619b7 | ||
![]() |
7358faf88e | ||
![]() |
19d014307a | ||
![]() |
5217f5c50c | ||
![]() |
c4624faa71 | ||
![]() |
b35ba4d673 | ||
![]() |
f8303bff76 | ||
![]() |
e61aa266a6 | ||
![]() |
d7971c69ad | ||
![]() |
966a624ef6 | ||
![]() |
7cc576a616 | ||
![]() |
2dec8e70ec | ||
![]() |
97663aef42 | ||
![]() |
3f1a2526b3 | ||
![]() |
e7517a8b61 | ||
![]() |
e3d394eb32 | ||
![]() |
536ea822b3 | ||
![]() |
8e4e22b6f8 | ||
![]() |
2eaa246a03 | ||
![]() |
e841bf89be | ||
![]() |
36e1203fb1 | ||
![]() |
3acab5a39c | ||
![]() |
49cfde1fe7 | ||
![]() |
49c018c000 | ||
![]() |
b71b230bfd | ||
![]() |
e1fd7244a5 | ||
![]() |
067c2fdfa8 | ||
![]() |
a02b817d7f | ||
![]() |
7db6e0b779 | ||
![]() |
1d5cc91a2d | ||
![]() |
0623e7dce4 | ||
![]() |
da106d278c | ||
![]() |
51c5ab33f0 | ||
![]() |
8ac4a6d900 | ||
![]() |
fae1bcf0e0 | ||
![]() |
9a9eec40b2 | ||
![]() |
6ab19d66d5 | ||
![]() |
a0a7ce014f | ||
![]() |
bfeb90780f | ||
![]() |
1f105b6c15 | ||
![]() |
5b7b0ea326 | ||
![]() |
32a991989f | ||
![]() |
788f76ab9c | ||
![]() |
f6411dce66 | ||
![]() |
6f19ea1d84 | ||
![]() |
448609533f | ||
![]() |
6c48ace41e | ||
![]() |
c41e100c1c | ||
![]() |
8216b522c2 | ||
![]() |
82035d587a | ||
![]() |
2796c3570a | ||
![]() |
f4f51e1de5 | ||
![]() |
af6b0d3266 | ||
![]() |
7d1c77a38f | ||
![]() |
f807618f75 | ||
![]() |
4cfb6713cb | ||
![]() |
d32f84f28d | ||
![]() |
5fb1504211 | ||
![]() |
c37e1f0c9d | ||
![]() |
90c234ffad | ||
![]() |
dd3a3ec586 | ||
![]() |
6f67da09c0 | ||
![]() |
ba27c184f6 | ||
![]() |
b37f97128a | ||
![]() |
ee0de942f7 | ||
![]() |
ae2d48f2f4 | ||
![]() |
1bd760b455 | ||
![]() |
3d66a68791 | ||
![]() |
01a53439c4 | ||
![]() |
09ee8dbeb6 | ||
![]() |
f36c91550d | ||
![]() |
6be6c711d0 | ||
![]() |
72a36fb1cd | ||
![]() |
4c982b3323 | ||
![]() |
c9c3be71cc | ||
![]() |
f1b965dcc5 | ||
![]() |
a08a23a93d | ||
![]() |
2040a49458 | ||
![]() |
df94f4f907 | ||
![]() |
96d375cb84 | ||
![]() |
7a9c2f56c5 | ||
![]() |
5ec7193e5c | ||
![]() |
d89e4337f2 | ||
![]() |
2e192d5021 | ||
![]() |
7db28c0156 | ||
![]() |
f09c842981 | ||
![]() |
b295bbd706 | ||
![]() |
8d3132fefc | ||
![]() |
00c5d3dbbb | ||
![]() |
ca37aff47d | ||
![]() |
9ed069ef6a | ||
![]() |
6faa3eb848 | ||
![]() |
6c73ae5bf7 | ||
![]() |
ce77ddf365 | ||
![]() |
cf05fbaa9d | ||
![]() |
552c474feb | ||
![]() |
a4f8e886bc | ||
![]() |
cc0c96b8b4 | ||
![]() |
445f0e23fe | ||
![]() |
6f240297d1 | ||
![]() |
6da4981b70 | ||
![]() |
cfadf4d700 | ||
![]() |
7e60de0531 | ||
![]() |
aaef6d7b91 | ||
![]() |
58c5ce2638 | ||
![]() |
a9d01c7b55 | ||
![]() |
c5de8a4361 | ||
![]() |
b53645ce92 | ||
![]() |
de34a5a597 | ||
![]() |
bd8e15bdd1 | ||
![]() |
45c7e0eeeb | ||
![]() |
a35a380ec7 | ||
![]() |
02e67d1146 | ||
![]() |
a5411f7ac4 | ||
![]() |
e8da203fe1 | ||
![]() |
10aa0a8829 | ||
![]() |
85a37e2d2f | ||
![]() |
ba8621fa2c | ||
![]() |
43e80f1a2e | ||
![]() |
3a305a44b6 | ||
![]() |
e99143139e | ||
![]() |
f0c7232704 | ||
![]() |
b2186592df | ||
![]() |
e51e3e79d5 | ||
![]() |
3b6b4d7664 | ||
![]() |
239e71b414 | ||
![]() |
080cad0ccd | ||
![]() |
dd49fd2788 | ||
![]() |
a571fb5528 | ||
![]() |
1369c1ae8c | ||
![]() |
f5864181af | ||
![]() |
a4a0d7cf19 | ||
![]() |
092dfd1e87 | ||
![]() |
a29ac33810 | ||
![]() |
1421df2a5a | ||
![]() |
591b8cc503 | ||
![]() |
011467ece0 | ||
![]() |
f52e8c3392 | ||
![]() |
c8b87b65bd | ||
![]() |
98cc82db44 | ||
![]() |
f510e2a8e0 | ||
![]() |
3438912ba5 | ||
![]() |
671c8e387f | ||
![]() |
0108ec65cf | ||
![]() |
39f7034578 | ||
![]() |
bf8affaf2b | ||
![]() |
e16a61eb53 | ||
![]() |
cadbe45bab | ||
![]() |
51f971337d | ||
![]() |
1f3c23de29 | ||
![]() |
bdfb17d957 | ||
![]() |
8c97aee1fe | ||
![]() |
38b4090daa | ||
![]() |
b8c55f2f65 | ||
![]() |
7ca379e0a1 | ||
![]() |
1617a9dfed | ||
![]() |
2c9411c6c3 | ||
![]() |
67626d4a06 | ||
![]() |
8135611688 | ||
![]() |
3ccbf6983e | ||
![]() |
e4f91195d8 | ||
![]() |
2751f8f33b | ||
![]() |
57f2df3b3e | ||
![]() |
6822f0d067 | ||
![]() |
cfba957313 | ||
![]() |
3149ffbf19 | ||
![]() |
4cd8b76d7e | ||
![]() |
4b644d8bc5 | ||
![]() |
307cd5ad8c | ||
![]() |
ebc807a6a4 | ||
![]() |
66adecdfc9 | ||
![]() |
2cc6432a0f | ||
![]() |
a2c0c0474a | ||
![]() |
27884b9a54 | ||
![]() |
293df61872 | ||
![]() |
f82dada3e5 | ||
![]() |
e5824c4794 | ||
![]() |
186550229c | ||
![]() |
7877dd8e6b | ||
![]() |
b03abc249b | ||
![]() |
fda03918b9 | ||
![]() |
6747375a1b | ||
![]() |
53b6e31881 | ||
![]() |
fa004de2d1 | ||
![]() |
3605f7b70f | ||
![]() |
5348c54c91 | ||
![]() |
684e4421bc | ||
![]() |
28f5611df5 | ||
![]() |
8da73d49d7 | ||
![]() |
049ddd5f84 | ||
![]() |
8ae2d4e93a | ||
![]() |
824bb9ba35 | ||
![]() |
d550b1a18e | ||
![]() |
dea6c0e761 | ||
![]() |
9caee357c0 | ||
![]() |
35d892c418 | ||
![]() |
9572a2a46b | ||
![]() |
8996361b26 | ||
![]() |
02ee731602 | ||
![]() |
bb1e6bf35b | ||
![]() |
c1b65285c1 | ||
![]() |
8b8d6e5fa3 | ||
![]() |
c34fe184e8 | ||
![]() |
7363838f86 | ||
![]() |
3081425ccd | ||
![]() |
95d494a54c | ||
![]() |
145e5d7bc6 | ||
![]() |
876fd9e85a | ||
![]() |
e8c30cabca | ||
![]() |
490f84a7b1 | ||
![]() |
ca28178b86 | ||
![]() |
2fceb0aeee | ||
![]() |
86f39d1d43 | ||
![]() |
1faf60444d | ||
![]() |
e927091d21 | ||
![]() |
cff2f856b3 | ||
![]() |
a743e3bbba | ||
![]() |
f8a52d250e | ||
![]() |
b70a523bdf | ||
![]() |
8f2ed747e6 | ||
![]() |
5deccefb15 | ||
![]() |
3f04abfa9d | ||
![]() |
8e55c83996 | ||
![]() |
dee59486ba | ||
![]() |
77ef509aea | ||
![]() |
bfa7bccfa6 | ||
![]() |
a8c365edc8 | ||
![]() |
94953ddf6c | ||
![]() |
6b67546daf | ||
![]() |
3e188d1f87 | ||
![]() |
f69eb15a90 | ||
![]() |
dfe348187f | ||
![]() |
9706c56c5c | ||
![]() |
3677c5be2c | ||
![]() |
bd339fa963 | ||
![]() |
28f1b6bdf4 | ||
![]() |
c5aac3b81d | ||
![]() |
70836597e9 | ||
![]() |
958a1de2fd | ||
![]() |
36d30266e3 | ||
![]() |
558ab9761d | ||
![]() |
269ef370e4 | ||
![]() |
ba2958ecd2 | ||
![]() |
3b8b6eb315 | ||
![]() |
4f13db3178 | ||
![]() |
ee7aa54ab4 | ||
![]() |
c305dd4cd5 | ||
![]() |
6865791596 | ||
![]() |
2099259393 | ||
![]() |
27ca45dc70 | ||
![]() |
d290c11219 | ||
![]() |
cabe10ffdb | ||
![]() |
aa562c21a8 | ||
![]() |
22175a7271 | ||
![]() |
1e0647c0d1 | ||
![]() |
58d94da8b3 | ||
![]() |
d97763a3e8 | ||
![]() |
aa129aa123 | ||
![]() |
f648317206 | ||
![]() |
0685fdf7c6 | ||
![]() |
6fd4cda534 | ||
![]() |
511368da13 | ||
![]() |
76e1721c58 | ||
![]() |
bad5a389b5 | ||
![]() |
85d1f49763 | ||
![]() |
7723d47ac1 | ||
![]() |
30b130ca74 | ||
![]() |
a124ec0717 | ||
![]() |
323d98ecf7 | ||
![]() |
125a601ae3 | ||
![]() |
3c549c6b31 | ||
![]() |
9c1494c74d | ||
![]() |
e751abd775 | ||
![]() |
714f2447b7 | ||
![]() |
d900e40d04 | ||
![]() |
8b82383790 | ||
![]() |
5a2cc2646c | ||
![]() |
16a0902989 | ||
![]() |
8f67aa38af | ||
![]() |
34184cf2ab | ||
![]() |
611cd2818e | ||
![]() |
0a4e8fd5d0 | ||
![]() |
11f0361f48 | ||
![]() |
cfa048ea4e | ||
![]() |
bbca7b762b | ||
![]() |
1dba849567 | ||
![]() |
aff1ec10bf | ||
![]() |
351ec08a71 | ||
![]() |
a1a6a2cd30 | ||
![]() |
4e82c23b29 | ||
![]() |
59595aabde | ||
![]() |
358f91c2a9 | ||
![]() |
e0e01e68b4 | ||
![]() |
61dc4eaaea | ||
![]() |
65c4d02452 | ||
![]() |
f78ce2c844 | ||
![]() |
4d1ab83b30 | ||
![]() |
fb4b40b828 | ||
![]() |
db0c4ef941 | ||
![]() |
c5b60b826b | ||
![]() |
718f0330a7 | ||
![]() |
89e31486c5 | ||
![]() |
717eec1860 | ||
![]() |
b6e51352e3 | ||
![]() |
2ade728bc3 | ||
![]() |
62f227da83 | ||
![]() |
9557b604da | ||
![]() |
b45c355c9f | ||
![]() |
0b47d2c687 | ||
![]() |
8baa0b2a9b | ||
![]() |
c68a1d21ff | ||
![]() |
419d659311 | ||
![]() |
ba8b20d877 | ||
![]() |
8de542388f | ||
![]() |
e6c580aadc | ||
![]() |
11696c566a | ||
![]() |
edc15940a2 | ||
![]() |
bf35ee549d | ||
![]() |
4c3baa678c | ||
![]() |
0bb2767696 | ||
![]() |
80a4852325 | ||
![]() |
9c3e0fc997 | ||
![]() |
9e4bee123f | ||
![]() |
c2c09b1284 | ||
![]() |
bad776b979 | ||
![]() |
396791b805 | ||
![]() |
2b1457e1cd | ||
![]() |
b5861869e3 | ||
![]() |
9444228907 | ||
![]() |
86afd883a5 | ||
![]() |
062f21aa91 | ||
![]() |
ba235ac797 | ||
![]() |
505c22248b | ||
![]() |
624cb48f78 | ||
![]() |
7ab54ee5ce | ||
![]() |
f5af63a50e | ||
![]() |
ff80ab34ee | ||
![]() |
cfc1999a28 | ||
![]() |
7ca28469b7 | ||
![]() |
ac670614b4 | ||
![]() |
e263b57296 | ||
![]() |
c7050e4676 | ||
![]() |
00cbd1d9e6 | ||
![]() |
2a12172eeb | ||
![]() |
85d3011625 | ||
![]() |
ca22ec6340 | ||
![]() |
61f6e8855b | ||
![]() |
a44b8981e1 | ||
![]() |
b080bca9ce | ||
![]() |
d30e8ee9d8 | ||
![]() |
637e4203e5 | ||
![]() |
2648a53bbc | ||
![]() |
b3fa0cccb4 | ||
![]() |
dd963be723 | ||
![]() |
224df896a1 | ||
![]() |
a58b4fb262 | ||
![]() |
27ca61ec85 | ||
![]() |
859f49f3eb | ||
![]() |
40d878689f | ||
![]() |
420e8fe1ff | ||
![]() |
df96199433 | ||
![]() |
f493280f0a | ||
![]() |
cbd030a379 | ||
![]() |
95b80accc9 | ||
![]() |
c522670815 | ||
![]() |
7b6d3c0e36 | ||
![]() |
504b043159 | ||
![]() |
dffc66ccc3 | ||
![]() |
c7e9ee785d | ||
![]() |
079cc39a6e | ||
![]() |
d6a1d5af79 | ||
![]() |
c0dce08e19 | ||
![]() |
f5f8be8276 | ||
![]() |
f9ccfa00a2 | ||
![]() |
070e11a2db | ||
![]() |
fc1c6cea24 | ||
![]() |
c6a103bd30 | ||
![]() |
9d1618024e | ||
![]() |
307aa161a6 | ||
![]() |
affa6a92e7 | ||
![]() |
1e5ec241d5 | ||
![]() |
27a98a32fc | ||
![]() |
2c8ac58f97 | ||
![]() |
6b995969b1 | ||
![]() |
72c107484a | ||
![]() |
d1085b6657 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
<!--
|
||||
Provide details about the versions you are using, which helps us reproducing
|
||||
and finding the issue quicker. Version information is found in the
|
||||
Home Assistant frontend: Configuration -> Info.
|
||||
Home Assistant frontend: Settings -> About.
|
||||
|
||||
Browser version and operating system is important! Please try to replicate
|
||||
your issue in a different browser and be sure to include your findings.
|
||||
|
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Report a bug with the UI, Frontend or Lovelace
|
||||
name: Report a bug with the UI / Dashboards
|
||||
description: Report an issue related to the Home Assistant frontend.
|
||||
labels: bug
|
||||
body:
|
||||
@@ -9,7 +9,7 @@ body:
|
||||
|
||||
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
|
||||
|
||||
**Please not not report issues for custom Lovelace cards.**
|
||||
**Please not not report issues for custom cards.**
|
||||
|
||||
[fr]: https://github.com/home-assistant/frontend/discussions
|
||||
[releases]: https://github.com/home-assistant/home-assistant/releases
|
||||
@@ -64,7 +64,7 @@ body:
|
||||
label: What version of Home Assistant Core has the issue?
|
||||
placeholder: core-
|
||||
description: >
|
||||
Can be found in the Configuration panel -> Info.
|
||||
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
|
||||
- type: input
|
||||
attributes:
|
||||
label: What was the last working version of Home Assistant Core?
|
||||
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,17 +1,17 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Request a feature for the UI, Frontend or Lovelace
|
||||
- name: Request a feature for the UI / Dashboards
|
||||
url: https://github.com/home-assistant/frontend/discussions/category_choices
|
||||
about: Request an new feature for the Home Assistant frontend.
|
||||
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
|
||||
- name: Report a bug that is NOT related to the UI / Dashboards
|
||||
url: https://github.com/home-assistant/core/issues
|
||||
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
|
||||
about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository.
|
||||
- name: Report incorrect or missing information on our website
|
||||
url: https://github.com/home-assistant/home-assistant.io/issues
|
||||
about: Our documentation has its own issue tracker. Please report issues with the website there.
|
||||
- name: I have a question or need support
|
||||
url: https://www.home-assistant.io/help
|
||||
about: We use GitHub for tracking bugs, check our website for resources on getting help.
|
||||
about: We use GitHub for tracking bugs. Check our website for resources on getting help.
|
||||
- name: I'm unsure where to go
|
||||
url: https://www.home-assistant.io/join-chat
|
||||
about: If you are unsure where to go, then joining our chat is recommended; Just ask!
|
||||
|
@@ -26,8 +26,8 @@ module.exports = {
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
|
||||
.match(/version\W+=\W(\d{8}\.\d)/);
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
||||
.match(/version\W+=\W"(\d{8}\.\d)"/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
|
@@ -3,10 +3,10 @@ const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle.js");
|
||||
const log = require("fancy-log");
|
||||
const WebpackBar = require("webpackbar");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle.js");
|
||||
|
||||
class LogStartCompilePlugin {
|
||||
ignoredFirst = false;
|
||||
@@ -138,6 +138,8 @@ const createWebpackConfig = ({
|
||||
"lit/directives/cache$": "lit/directives/cache.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/polyfill-support$": "lit/polyfill-support.js",
|
||||
"@lit-labs/virtualizer/layouts/grid":
|
||||
"@lit-labs/virtualizer/layouts/grid.js",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
|
@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
type: "state-icon",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "group.downstairs_lights",
|
||||
},
|
||||
service: "homeassistant.toggle",
|
||||
|
@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "script.air_cleaner_quiet",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "script.air_cleaner_auto",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "script.air_cleaner_turbo",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "script.ac_off",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "script.ac_on",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "scene.morning_lights",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "scene.morning_lights",
|
||||
},
|
||||
service: "scene.turn_on",
|
||||
@@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "scene.movie_time",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "scene.movie_time",
|
||||
},
|
||||
service: "scene.turn_on",
|
||||
@@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "light.downstairs_lights",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "light.downstairs_lights",
|
||||
},
|
||||
service: "light.toggle",
|
||||
@@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "light.upstairs_lights",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service_data: {
|
||||
data: {
|
||||
entity_id: "light.upstairs_lights",
|
||||
},
|
||||
service: "light.toggle",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { format, startOfToday, startOfTomorrow } from "date-fns";
|
||||
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
|
||||
import { EnergySolarForecasts } from "../../../src/data/energy";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
|
@@ -31,7 +31,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
|
||||
version_latest: "3.6.2",
|
||||
update_available: false,
|
||||
repository: "a0d7b954",
|
||||
icon: true,
|
||||
icon: false,
|
||||
logo: true,
|
||||
},
|
||||
{
|
||||
|
@@ -4,7 +4,7 @@ import {
|
||||
addMonths,
|
||||
differenceInHours,
|
||||
endOfDay,
|
||||
} from "date-fns";
|
||||
} from "date-fns/esm";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { StatisticValue } from "../../../src/data/history";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
BIN
gallery/public/api/hassio/addons/core_zwave_js/icon
Normal file
BIN
gallery/public/api/hassio/addons/core_zwave_js/icon
Normal file
Binary file not shown.
@@ -42,10 +42,11 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
category: "user-test",
|
||||
header: "User Tests",
|
||||
header: "Users",
|
||||
pages: ["user-types", "configuration-menu"],
|
||||
},
|
||||
{
|
||||
category: "design.home-assistant.io",
|
||||
header: "Design Documentation",
|
||||
header: "About",
|
||||
},
|
||||
];
|
||||
|
@@ -53,13 +53,19 @@ class DemoBlackWhiteRow extends LitElement {
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
handleSubmit(ev) {
|
||||
|
11
gallery/src/data/text.ts
Normal file
11
gallery/src/data/text.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const LONG_TEXT = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
||||
|
||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
||||
|
||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
||||
|
||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
||||
|
||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
||||
`;
|
@@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
service_data: {},
|
||||
data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_4"],
|
||||
},
|
||||
@@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
service_data: {},
|
||||
data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_2"],
|
||||
},
|
||||
@@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
service_data: {},
|
||||
data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_3"],
|
||||
},
|
||||
@@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
service_data: {},
|
||||
data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_4"],
|
||||
},
|
||||
@@ -298,11 +298,11 @@ export const basicTrace: DemoTrace = {
|
||||
source: "state of input_boolean.toggle_1",
|
||||
entity_id: "automation.toggle_toggles",
|
||||
context_id: "6cfcae368e7b3686fad6c59e83ae76c9",
|
||||
when: "2021-03-25T04:36:51.240832+00:00",
|
||||
when: 1616647011.240832,
|
||||
domain: "automation",
|
||||
},
|
||||
{
|
||||
when: "2021-03-25T04:36:51.249828+00:00",
|
||||
when: 1616647011.249828,
|
||||
name: "Toggle 4",
|
||||
state: "on",
|
||||
entity_id: "input_boolean.toggle_4",
|
||||
@@ -313,7 +313,7 @@ export const basicTrace: DemoTrace = {
|
||||
context_name: "Ensure Party mode",
|
||||
},
|
||||
{
|
||||
when: "2021-03-25T04:36:51.258947+00:00",
|
||||
when: 1616647011.258947,
|
||||
name: "Toggle 2",
|
||||
state: "on",
|
||||
entity_id: "input_boolean.toggle_2",
|
||||
@@ -324,7 +324,7 @@ export const basicTrace: DemoTrace = {
|
||||
context_name: "Ensure Party mode",
|
||||
},
|
||||
{
|
||||
when: "2021-03-25T04:36:51.261806+00:00",
|
||||
when: 1616647011.261806,
|
||||
name: "Toggle 3",
|
||||
state: "off",
|
||||
entity_id: "input_boolean.toggle_3",
|
||||
@@ -335,7 +335,7 @@ export const basicTrace: DemoTrace = {
|
||||
context_name: "Ensure Party mode",
|
||||
},
|
||||
{
|
||||
when: "2021-03-25T04:36:51.265246+00:00",
|
||||
when: 1616647011.265246,
|
||||
name: "Toggle 4",
|
||||
state: "off",
|
||||
entity_id: "input_boolean.toggle_4",
|
||||
|
@@ -185,11 +185,11 @@ export const motionLightTrace: DemoTrace = {
|
||||
"has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
entity_id: "automation.auto_elgato",
|
||||
when: "2021-03-14T06:07:01.768492+00:00",
|
||||
when: 1615702021.768492,
|
||||
domain: "automation",
|
||||
},
|
||||
{
|
||||
when: "2021-03-14T06:07:01.872187+00:00",
|
||||
when: 1615702021.872187,
|
||||
name: "Elgato Key Light Air",
|
||||
state: "on",
|
||||
entity_id: "light.elgato_key_light_air",
|
||||
@@ -200,7 +200,7 @@ export const motionLightTrace: DemoTrace = {
|
||||
context_name: "Auto Elgato",
|
||||
},
|
||||
{
|
||||
when: "2021-03-14T06:07:53.284505+00:00",
|
||||
when: 1615702073.284505,
|
||||
name: "Elgato Key Light Air",
|
||||
state: "off",
|
||||
entity_id: "light.elgato_key_light_air",
|
||||
|
@@ -45,6 +45,10 @@ class HaGallery extends LitElement {
|
||||
for (const page of group.pages!) {
|
||||
const key = `${group.category}/${page}`;
|
||||
const active = this._page === key;
|
||||
if (!(key in PAGES)) {
|
||||
console.error("Undefined page referenced in sidebar.js:", key);
|
||||
continue;
|
||||
}
|
||||
const title = PAGES[key].metadata.title || page;
|
||||
links.push(html`
|
||||
<a ?active=${active} href=${`#${group.category}/${page}`}>${title}</a>
|
||||
|
@@ -62,6 +62,45 @@ const ACTIONS = [
|
||||
entity_id: "input_boolean.toggle_4",
|
||||
},
|
||||
},
|
||||
{
|
||||
parallel: [
|
||||
{ scene: "scene.kitchen_morning" },
|
||||
{
|
||||
service: "media_player.play_media",
|
||||
target: { entity_id: "media_player.living_room" },
|
||||
data: { media_content_id: "", media_content_type: "" },
|
||||
metadata: { title: "Happy Song" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stop: "No one is home!",
|
||||
},
|
||||
{ repeat: { count: 3, sequence: [{ delay: "00:00:01" }] } },
|
||||
{
|
||||
repeat: {
|
||||
for_each: ["bread", "butter", "cheese"],
|
||||
sequence: [{ delay: "00:00:01" }],
|
||||
},
|
||||
},
|
||||
{
|
||||
if: [{ condition: "state" }],
|
||||
then: [{ delay: "00:00:01" }],
|
||||
else: [{ delay: "00:00:05" }],
|
||||
},
|
||||
{
|
||||
choose: [
|
||||
{
|
||||
conditions: [{ condition: "state" }],
|
||||
sequence: [{ delay: "00:00:01" }],
|
||||
},
|
||||
{
|
||||
conditions: [{ condition: "sun" }],
|
||||
sequence: [{ delay: "00:00:05" }],
|
||||
},
|
||||
],
|
||||
default: [{ delay: "00:00:03" }],
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-automation-describe-action")
|
||||
|
@@ -20,6 +20,10 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
|
||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||
import { Action } from "../../../../src/data/script";
|
||||
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
|
||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
|
||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
|
||||
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
|
||||
import { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media";
|
||||
|
||||
const SCHEMAS: { name: string; actions: Action[] }[] = [
|
||||
{ name: "Event", actions: [HaEventAction.defaultConfig] },
|
||||
@@ -28,11 +32,15 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
||||
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
|
||||
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
|
||||
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
|
||||
{ name: "Play media", actions: [HaPlayMediaAction.defaultConfig] },
|
||||
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
|
||||
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
|
||||
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
|
||||
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
|
||||
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
|
||||
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
|
||||
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
|
||||
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
|
||||
];
|
||||
|
||||
@customElement("demo-automation-editor-action")
|
||||
@@ -86,6 +94,6 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-ha-automation-editor-action": DemoHaAutomationEditorAction;
|
||||
"demo-automation-editor-action": DemoHaAutomationEditorAction;
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import type { Condition } from "../../../../src/data/automation";
|
||||
import type { ConditionWithShorthand } from "../../../../src/data/automation";
|
||||
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
||||
import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
|
||||
import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
|
||||
@@ -20,7 +20,7 @@ import { HaTimeCondition } from "../../../../src/panels/config/automation/condit
|
||||
import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
|
||||
import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
|
||||
|
||||
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
|
||||
const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
||||
{
|
||||
name: "State",
|
||||
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
|
||||
@@ -69,6 +69,14 @@ const SCHEMAS: { name: string; conditions: Condition[] }[] = [
|
||||
name: "Trigger",
|
||||
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Shorthand",
|
||||
conditions: [
|
||||
{ and: HaLogicalCondition.defaultConfig.conditions },
|
||||
{ or: HaLogicalCondition.defaultConfig.conditions },
|
||||
{ not: HaLogicalCondition.defaultConfig.conditions },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-automation-editor-condition")
|
||||
|
@@ -159,13 +159,19 @@ export class DemoHaAlert extends LitElement {
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
@@ -3,18 +3,7 @@ import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-faded";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
|
||||
const LONG_TEXT = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
||||
|
||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
||||
|
||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
||||
|
||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
||||
|
||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
||||
`;
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
|
||||
|
@@ -1,18 +1,18 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import "../../components/demo-black-white-row";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
@@ -147,7 +147,9 @@ const SCHEMAS: {
|
||||
{ name: "target", selector: { target: {} } },
|
||||
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
||||
{ name: "boolean", selector: { boolean: {} } },
|
||||
{ name: "time", selector: { time: {} } },
|
||||
{ name: "time", required: true, selector: { time: {} } },
|
||||
{ name: "datetime", required: true, selector: { datetime: {} } },
|
||||
{ name: "date", required: true, selector: { date: {} } },
|
||||
{ name: "action", selector: { action: {} } },
|
||||
{ name: "text", selector: { text: { multiline: false } } },
|
||||
{ name: "text_multiline", selector: { text: { multiline: true } } },
|
||||
|
@@ -1,20 +1,20 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import { LitElement, TemplateResult, css, html } from "lit";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
@@ -109,7 +109,7 @@ const AREAS = [
|
||||
|
||||
const SCHEMAS: {
|
||||
name: string;
|
||||
input: Record<string, BlueprintInput | null>;
|
||||
input: Record<string, (BlueprintInput & { required?: boolean }) | null>;
|
||||
}[] = [
|
||||
{
|
||||
name: "One of each",
|
||||
@@ -166,8 +166,11 @@ const SCHEMAS: {
|
||||
object: { name: "Object", selector: { object: {} } },
|
||||
select_radio: {
|
||||
name: "Select (Radio)",
|
||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||
selector: {
|
||||
select: { options: ["Option 1", "Option 2"], mode: "list" },
|
||||
},
|
||||
},
|
||||
template: { name: "Template", selector: { template: {} } },
|
||||
select: {
|
||||
name: "Select",
|
||||
selector: {
|
||||
@@ -183,6 +186,22 @@ const SCHEMAS: {
|
||||
},
|
||||
},
|
||||
},
|
||||
select_custom: {
|
||||
name: "Select (Custom)",
|
||||
selector: {
|
||||
select: {
|
||||
custom_value: true,
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
icon: { name: "Icon", selector: { icon: {} } },
|
||||
media: { name: "Media", selector: { media: {} } },
|
||||
location: { name: "Location", selector: { location: {} } },
|
||||
@@ -202,6 +221,35 @@ const SCHEMAS: {
|
||||
input: {
|
||||
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
||||
device: { name: "Device", selector: { device: { multiple: true } } },
|
||||
area: { name: "Area", selector: { area: { multiple: true } } },
|
||||
select: {
|
||||
name: "Select Multiple",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
custom_value: true,
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
select_checkbox: {
|
||||
name: "Select Multiple (Checkbox)",
|
||||
required: false,
|
||||
selector: {
|
||||
select: {
|
||||
mode: "list",
|
||||
multiple: true,
|
||||
options: ["Option 1", "Option 2", "Option 3", "Option 4"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -210,6 +258,14 @@ const SCHEMAS: {
|
||||
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
@state() public hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled = false;
|
||||
|
||||
@state() private _required = false;
|
||||
|
||||
@state() private _helper = false;
|
||||
|
||||
@state() private _label = true;
|
||||
|
||||
private data = SCHEMAS.map(() => ({}));
|
||||
|
||||
constructor() {
|
||||
@@ -343,6 +399,36 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Labels">
|
||||
<ha-switch
|
||||
.name=${"label"}
|
||||
.checked=${this._label}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Required">
|
||||
<ha-switch
|
||||
.name=${"required"}
|
||||
.checked=${this._required}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Disabled">
|
||||
<ha-switch
|
||||
.name=${"disabled"}
|
||||
.checked=${this._disabled}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Helper text">
|
||||
<ha-switch
|
||||
.name=${"helper"}
|
||||
.checked=${this._helper}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
const data = this.data[idx];
|
||||
const valueChanged = (ev) => {
|
||||
@@ -365,8 +451,12 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
.hass=${this.hass}
|
||||
.selector=${value!.selector}
|
||||
.key=${key}
|
||||
.label=${this._label ? value!.name : undefined}
|
||||
.value=${data[key] ?? value!.default}
|
||||
.disabled=${this._disabled}
|
||||
.required=${this._required}
|
||||
@value-changed=${valueChanged}
|
||||
.helper=${this._helper ? "Helper text" : undefined}
|
||||
></ha-selector>
|
||||
</ha-settings-row>
|
||||
`
|
||||
@@ -378,10 +468,21 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-selector {
|
||||
width: 60;
|
||||
}
|
||||
.options {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
3
gallery/src/pages/components/ha-tip.markdown
Normal file
3
gallery/src/pages/components/ha-tip.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Tips
|
||||
---
|
73
gallery/src/pages/components/ha-tip.ts
Normal file
73
gallery/src/pages/components/ha-tip.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-tip";
|
||||
import "../../../../src/components/ha-card";
|
||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
||||
|
||||
const tips: (string | TemplateResult)[] = [
|
||||
"Test tip",
|
||||
"Bigger test tip, with some random text just to fill up as much space as possible without it looking like I'm really trying to to that",
|
||||
html`<i>Tip</i> <b>with</b> <sub>HTML</sub>`,
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-tip")
|
||||
export class DemoHaTip extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html` ${["light", "dark"].map(
|
||||
(mode) => html`
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-tip ${mode} demo">
|
||||
<div class="card-content">
|
||||
${tips.map((tip) => html`<ha-tip>${tip}</ha-tip>`)}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
)}`;
|
||||
}
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.dark,
|
||||
.light {
|
||||
display: block;
|
||||
background-color: var(--primary-background-color);
|
||||
padding: 0 50px;
|
||||
}
|
||||
ha-tip {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
ha-card {
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-tip": DemoHaTip;
|
||||
}
|
||||
}
|
@@ -249,7 +249,7 @@ const CONFIGS = [
|
||||
name: Bed light
|
||||
action_name: Toggle light
|
||||
service: light.toggle
|
||||
service_data:
|
||||
data:
|
||||
entity_id: light.bed_light
|
||||
- type: section
|
||||
label: Links
|
||||
|
@@ -199,7 +199,7 @@ const CONFIGS = [
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
service_data:
|
||||
data:
|
||||
entity_id: light.ceiling_lights
|
||||
- entity: sun.sun
|
||||
name: Regular
|
||||
|
@@ -9,7 +9,7 @@ const CONFIGS = [
|
||||
heading: "markdown-it demo",
|
||||
config: `
|
||||
- type: markdown
|
||||
content: >
|
||||
content: >-
|
||||
# h1 Heading 8-)
|
||||
|
||||
## h2 Heading
|
||||
@@ -249,6 +249,17 @@ const CONFIGS = [
|
||||
::: warning
|
||||
*here be dragons*
|
||||
:::
|
||||
|
||||
### ha-alert
|
||||
|
||||
You can use our [\`ha-alert\`](https://design.home-assistant.io/#components/ha-alert) component in markdown content rendered in the Home Assistant Frontend.
|
||||
|
||||
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="warning">This is a warning alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="success">This is a success alert — check it out!</ha-alert>
|
||||
<ha-alert title="Test alert">This is an alert with a title</ha-alert>
|
||||
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
@@ -40,7 +40,7 @@ const CONFIGS = [
|
||||
left: 90%
|
||||
padding: 0px
|
||||
service: light.turn_off
|
||||
service_data:
|
||||
data:
|
||||
entity_id: group.all_lights
|
||||
- type: icon
|
||||
icon: mdi:cctv
|
||||
@@ -88,7 +88,7 @@ const CONFIGS = [
|
||||
left: 90%
|
||||
padding: 0px
|
||||
service: light.turn_off
|
||||
service_data:
|
||||
data:
|
||||
entity_id: group.all_lights
|
||||
- type: icon
|
||||
icon: mdi:cctv
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
UPDATE_SUPPORT_BACKUP,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
UPDATE_SUPPORT_INSTALL,
|
||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
} from "../../../../src/data/update";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
@@ -13,10 +14,11 @@ import {
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const base_attributes = {
|
||||
title: "Awesome",
|
||||
current_version: "1.2.2",
|
||||
installed_version: "1.2.2",
|
||||
latest_version: "1.2.3",
|
||||
release_url: "https://home-assistant.io",
|
||||
supported_features: UPDATE_SUPPORT_INSTALL,
|
||||
@@ -48,7 +50,7 @@ const ENTITIES = [
|
||||
}),
|
||||
getEntity("update", "update5", "off", {
|
||||
...base_attributes,
|
||||
current_version: "1.2.3",
|
||||
installed_version: "1.2.3",
|
||||
friendly_name: "No update",
|
||||
}),
|
||||
getEntity("update", "update6", "off", {
|
||||
@@ -100,14 +102,43 @@ const ENTITIES = [
|
||||
}),
|
||||
getEntity("update", "update14", "off", {
|
||||
...base_attributes,
|
||||
current_version: null,
|
||||
friendly_name: "Update without current_version",
|
||||
installed_version: null,
|
||||
friendly_name: "Update without installed_version",
|
||||
}),
|
||||
getEntity("update", "update15", "off", {
|
||||
...base_attributes,
|
||||
latest_version: null,
|
||||
friendly_name: "Update without latest_version",
|
||||
}),
|
||||
getEntity("update", "update16", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update17", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes error",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update18", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes loading",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update19", "on", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with auto update",
|
||||
auto_update: true,
|
||||
}),
|
||||
getEntity("update", "update20", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
title: undefined,
|
||||
friendly_name: "Installing without title",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-update")
|
||||
@@ -130,6 +161,24 @@ class DemoMoreInfoUpdate extends LitElement {
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
hass.mockWS(
|
||||
"update/release_notes",
|
||||
(msg: { type: string; entity_id: string }) => {
|
||||
if (msg.entity_id === "update.update16") {
|
||||
return LONG_TEXT;
|
||||
}
|
||||
if (msg.entity_id === "update.update17") {
|
||||
return Promise.reject({
|
||||
code: "error",
|
||||
message: "Could not fetch release notes",
|
||||
});
|
||||
}
|
||||
if (msg.entity_id === "update.update18") {
|
||||
return undefined;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
17
gallery/src/pages/user-test/user-types.markdown
Normal file
17
gallery/src/pages/user-test/user-types.markdown
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "User types"
|
||||
---
|
||||
|
||||
We have defined three user types for Home Assistant. They are a lean segmentation of users that helps us make decisions throughout the product. User types differ from traditional personas in that the segmentation criteria aren’t demographic and don’t personify a group into a single character with a fictitious background story.
|
||||
|
||||
# Outgrowers
|
||||
|
||||
Users that outgrow big tech smart home solutions. It just needs to work with easy setup via an app.
|
||||
|
||||
# Tinkerers
|
||||
|
||||
Technoid users in home networking and development that know how to code.
|
||||
|
||||
# Questioner
|
||||
|
||||
Users who want more advanced home automation, but need support to make it work.
|
@@ -68,6 +68,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
${addons.map(
|
||||
(addon) => html`
|
||||
<ha-card
|
||||
outlined
|
||||
.addon=${addon}
|
||||
class=${addon.available ? "" : "not_available"}
|
||||
@click=${this._addonTapped}
|
||||
|
@@ -50,6 +50,7 @@ class HassioAddonAudio extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.supervisor.localize("addon.configuration.audio.header")}
|
||||
>
|
||||
<div class="card-content">
|
||||
|
@@ -39,7 +39,14 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
|
||||
const SUPPORTED_UI_TYPES = [
|
||||
"string",
|
||||
"select",
|
||||
"boolean",
|
||||
"integer",
|
||||
"float",
|
||||
"schema",
|
||||
];
|
||||
|
||||
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
new Type("!secret", {
|
||||
@@ -48,6 +55,8 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
}),
|
||||
]);
|
||||
|
||||
const MASKED_FIELDS = ["password", "secret", "token"];
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
@@ -75,19 +84,66 @@ class HassioAddonConfig extends LitElement {
|
||||
public computeLabel = (entry: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||
?.name ||
|
||||
this.addon.translations.en?.configuration?.[entry.name].name ||
|
||||
this.addon.translations.en?.configuration?.[entry.name]?.name ||
|
||||
entry.name;
|
||||
|
||||
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
|
||||
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
...entry,
|
||||
options: entry.options.map((option) => [option, option]),
|
||||
}
|
||||
: entry
|
||||
)
|
||||
public computeHelper = (entry: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||
?.description ||
|
||||
this.addon.translations.en?.configuration?.[entry.name]?.description ||
|
||||
"";
|
||||
|
||||
private _convertSchema = memoizeOne(
|
||||
// Convert supervisor schema to selectors
|
||||
(schema: Record<string, any>): HaFormSchema[] =>
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { select: { options: entry.options } },
|
||||
}
|
||||
: entry.type === "string"
|
||||
? entry.multiple
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
select: { options: [], multiple: true, custom_value: true },
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry.type === "boolean"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { boolean: {} },
|
||||
}
|
||||
: entry.type === "schema"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { object: {} },
|
||||
}
|
||||
: entry.type === "float" || entry.type === "integer"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { number: { mode: "box" } },
|
||||
}
|
||||
: entry
|
||||
)
|
||||
);
|
||||
|
||||
private _filteredShchema = memoizeOne(
|
||||
@@ -106,7 +162,7 @@ class HassioAddonConfig extends LitElement {
|
||||
);
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
<div class="header">
|
||||
<h2>
|
||||
${this.supervisor.localize("addon.configuration.options.header")}
|
||||
@@ -140,7 +196,8 @@ class HassioAddonConfig extends LitElement {
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.schema=${this._schema(
|
||||
.computeHelper=${this.computeHelper}
|
||||
.schema=${this._convertSchema(
|
||||
this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredShchema(
|
||||
@@ -197,8 +254,9 @@ class HassioAddonConfig extends LitElement {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._canShowSchema = !this.addon.schema!.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
(entry) =>
|
||||
// @ts-ignore
|
||||
!SUPPORTED_UI_TYPES.includes(entry.type)
|
||||
);
|
||||
this._yamlMode = !this._canShowSchema;
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -8,10 +7,13 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -24,16 +26,6 @@ import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
interface NetworkItem {
|
||||
description: string;
|
||||
container: string;
|
||||
host: number | null;
|
||||
}
|
||||
|
||||
interface NetworkItemInput extends PaperInputElement {
|
||||
container: string;
|
||||
}
|
||||
|
||||
@customElement("hassio-addon-network")
|
||||
class HassioAddonNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -42,9 +34,13 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@state() private _showOptional = false;
|
||||
|
||||
@state() private _configHasChanged = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _config?: NetworkItem[];
|
||||
@state() private _config?: Record<string, any>;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -56,59 +52,61 @@ class HassioAddonNetwork extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const hasHiddenOptions = Object.keys(this._config).find(
|
||||
(entry) => this._config![entry] === null
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.supervisor.localize(
|
||||
"addon.configuration.network.header"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.introduction"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.container"
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.host"
|
||||
)}
|
||||
</th>
|
||||
<th>${this.supervisor.localize("common.description")}</th>
|
||||
</tr>
|
||||
${this._config!.map(
|
||||
(item) => html`
|
||||
<tr>
|
||||
<td>${item.container}</td>
|
||||
<td>
|
||||
<paper-input
|
||||
@value-changed=${this._configChanged}
|
||||
placeholder=${this.supervisor.localize(
|
||||
"addon.configuration.network.disabled"
|
||||
)}
|
||||
.value=${item.host ? String(item.host) : ""}
|
||||
.container=${item.container}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
</td>
|
||||
<td>${this._computeDescription(item)}</td>
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<ha-form
|
||||
.data=${this._config}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
.schema=${this._createSchema(
|
||||
this._config,
|
||||
this._showOptional,
|
||||
this.hass.userData?.showAdvanced || false
|
||||
)}
|
||||
></ha-form>
|
||||
</div>
|
||||
${hasHiddenOptions
|
||||
? html`<ha-formfield
|
||||
class="show-optional"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.network.show_disabled"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._toggleOptional}
|
||||
.checked=${this._showOptional}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>`
|
||||
: ""}
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
${this.supervisor.localize("common.reset_defaults")}
|
||||
</ha-progress-button>
|
||||
<ha-progress-button @click=${this._saveTapped}>
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged}
|
||||
>
|
||||
${this.supervisor.localize("common.save")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -123,50 +121,60 @@ class HassioAddonNetwork extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _computeDescription = (item: NetworkItem): string =>
|
||||
this.addon.translations[this.hass.language]?.network?.[item.container]
|
||||
?.description ||
|
||||
this.addon.translations.en?.network?.[item.container]?.description ||
|
||||
item.description;
|
||||
private _createSchema = memoizeOne(
|
||||
(
|
||||
config: Record<string, number>,
|
||||
showOptional: boolean,
|
||||
advanced: boolean
|
||||
): HaFormSchema[] =>
|
||||
(showOptional
|
||||
? Object.keys(config)
|
||||
: Object.keys(config).filter((entry) => config[entry] !== null)
|
||||
).map((entry) => ({
|
||||
name: entry,
|
||||
selector: {
|
||||
number: {
|
||||
mode: "box",
|
||||
min: 0,
|
||||
max: 65535,
|
||||
unit_of_measurement: advanced ? entry : undefined,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
private _computeLabel = (_: HaFormSchema): string => "";
|
||||
|
||||
private _computeHelper = (item: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.network?.[item.name] ||
|
||||
this.addon.translations.en?.network?.[item.name] ||
|
||||
this.addon.network_description?.[item.name] ||
|
||||
item.name;
|
||||
|
||||
private _setNetworkConfig(): void {
|
||||
const network = this.addon.network || {};
|
||||
const description = this.addon.network_description || {};
|
||||
const items: NetworkItem[] = Object.keys(network).map((key) => ({
|
||||
container: key,
|
||||
host: network[key],
|
||||
description: description[key],
|
||||
}));
|
||||
this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1));
|
||||
this._config = this.addon.network || {};
|
||||
}
|
||||
|
||||
private async _configChanged(ev: Event): Promise<void> {
|
||||
const target = ev.target as NetworkItemInput;
|
||||
this._config!.forEach((item) => {
|
||||
if (
|
||||
item.container === target.container &&
|
||||
item.host !== parseInt(String(target.value), 10)
|
||||
) {
|
||||
item.host = target.value ? parseInt(String(target.value), 10) : null;
|
||||
}
|
||||
});
|
||||
private async _configChanged(ev: CustomEvent): Promise<void> {
|
||||
this._configHasChanged = true;
|
||||
this._config! = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
network: null,
|
||||
};
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
@@ -177,19 +185,21 @@ class HassioAddonNetwork extends LitElement {
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
button.actionError();
|
||||
}
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
private _toggleOptional() {
|
||||
this._showOptional = !this._showOptional;
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
this._error = undefined;
|
||||
const networkconfiguration = {};
|
||||
this._config!.forEach((item) => {
|
||||
networkconfiguration[item.container] = parseInt(String(item.host), 10);
|
||||
Object.entries(this._config!).forEach(([key, value]) => {
|
||||
networkconfiguration[key] = value ?? null;
|
||||
});
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
@@ -198,11 +208,13 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
@@ -213,8 +225,8 @@ class HassioAddonNetwork extends LitElement {
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
button.actionError();
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -232,6 +244,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.show-optional {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
@@ -17,7 +17,12 @@ import {
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSupervisorInfo,
|
||||
setSupervisorOption,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-error-screen";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
@@ -166,6 +171,44 @@ class HassioAddonDashboard extends LitElement {
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
if (this.route.path === "") {
|
||||
const requestedAddon = extractSearchParam("addon");
|
||||
const requestedAddonRepository = extractSearchParam("repository_url");
|
||||
if (requestedAddonRepository) {
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
if (
|
||||
!supervisorInfo.addons_repositories.find(
|
||||
(repo) => repo === requestedAddonRepository
|
||||
)
|
||||
) {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize("my.add_addon_repository_title"),
|
||||
text: this.supervisor.localize(
|
||||
"my.add_addon_repository_description",
|
||||
{ addon: requestedAddon, repository: requestedAddonRepository }
|
||||
),
|
||||
confirmText: this.supervisor.localize("common.add"),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
}))
|
||||
) {
|
||||
this._error = this.supervisor.localize(
|
||||
"my.error_repository_not_found"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await setSupervisorOption(this.hass, {
|
||||
addons_repositories: [
|
||||
...supervisorInfo.addons_repositories,
|
||||
requestedAddonRepository,
|
||||
],
|
||||
});
|
||||
} catch (err: any) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (requestedAddon) {
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
const validAddon = addonsInfo.addons.some(
|
||||
|
@@ -166,7 +166,7 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="addon-header">
|
||||
${!this.narrow ? this.addon.name : ""}
|
||||
@@ -649,7 +649,7 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
${this.addon.long_description
|
||||
? html`
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<ha-markdown
|
||||
.content=${this.addon.long_description}
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-ansi-to-html";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
fetchHassioAddonLogs,
|
||||
@@ -11,7 +12,6 @@ import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-logs")
|
||||
@@ -34,15 +34,15 @@ class HassioAddonLogs extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html
|
||||
? html`<ha-ansi-to-html
|
||||
.content=${this._content}
|
||||
></hassio-ansi-to-html>`
|
||||
></ha-ansi-to-html>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -166,7 +166,15 @@ export class HassioBackups extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.tabs=${supervisorTabs(this.hass)}
|
||||
.tabs=${atLeastVersion(this.hass.config.version, 2022, 5)
|
||||
? [
|
||||
{
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
]
|
||||
: supervisorTabs(this.hass)}
|
||||
.hass=${this.hass}
|
||||
.localizeFunc=${this.supervisor.localize}
|
||||
.searchLabel=${this.supervisor.localize("search")}
|
||||
@@ -182,7 +190,9 @@ export class HassioBackups extends LitElement {
|
||||
selectable
|
||||
hasFab
|
||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||
back-path="/config"
|
||||
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
|
||||
? "/config/system"
|
||||
: "/config"}
|
||||
supervisor
|
||||
>
|
||||
<ha-button-menu
|
||||
|
@@ -32,13 +32,6 @@ interface AddonCheckboxItem extends CheckboxItem {
|
||||
|
||||
const _computeFolders = (folders): CheckboxItem[] => {
|
||||
const list: CheckboxItem[] = [];
|
||||
if (folders.includes("homeassistant")) {
|
||||
list.push({
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
checked: false,
|
||||
});
|
||||
}
|
||||
if (folders.includes("ssl")) {
|
||||
list.push({ slug: "ssl", name: "SSL", checked: false });
|
||||
}
|
||||
@@ -100,7 +93,7 @@ export class SupervisorBackupContent extends LitElement {
|
||||
this.folders = _computeFolders(
|
||||
this.backup
|
||||
? this.backup.folders
|
||||
: ["homeassistant", "ssl", "share", "media", "addons/local"]
|
||||
: ["ssl", "share", "media", "addons/local"]
|
||||
);
|
||||
this.addons = _computeAddons(
|
||||
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
|
||||
@@ -187,7 +180,7 @@ export class SupervisorBackupContent extends LitElement {
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
@change=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
|
@@ -26,7 +26,7 @@ class HassioAddons extends LitElement {
|
||||
<div class="card-group">
|
||||
${!this.supervisor.supervisor.addons?.length
|
||||
? html`
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<button class="link" @click=${this._openStore}>
|
||||
${this.supervisor.localize("dashboard.no_addons")}
|
||||
@@ -38,7 +38,11 @@ class HassioAddons extends LitElement {
|
||||
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
|
||||
.map(
|
||||
(addon) => html`
|
||||
<ha-card .addon=${addon} @click=${this._addonTapped}>
|
||||
<ha-card
|
||||
outlined
|
||||
.addon=${addon}
|
||||
@click=${this._addonTapped}
|
||||
>
|
||||
<div class="card-content">
|
||||
<hassio-card-content
|
||||
.hass=${this.hass}
|
||||
|
@@ -10,6 +10,7 @@ import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
|
||||
@customElement("hassio-dashboard")
|
||||
class HassioDashboard extends LitElement {
|
||||
@@ -22,6 +23,31 @@ class HassioDashboard extends LitElement {
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (atLeastVersion(this.hass.config.version, 2022, 5)) {
|
||||
return html`<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.header=${this.supervisor.localize("panel.addons")}
|
||||
>
|
||||
<hassio-addons
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-addons>
|
||||
<a href="/hassio/store">
|
||||
<ha-fab
|
||||
.label=${this.supervisor.localize("panel.store")}
|
||||
extended
|
||||
class="non-tabs"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiStorePlus}
|
||||
></ha-svg-icon> </ha-fab
|
||||
></a>
|
||||
</hass-subpage>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -74,6 +100,12 @@ class HassioDashboard extends LitElement {
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
ha-fab.non-tabs {
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ export class HassioUpdate extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="icon">
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
|
@@ -3,8 +3,8 @@ import { customElement, property } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { mainWindow } from "../../src/common/dom/get_main_window";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
@@ -73,6 +73,18 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
});
|
||||
});
|
||||
|
||||
// Forward keydown events to the main window for quickbar access
|
||||
document.body.addEventListener("keydown", (ev: KeyboardEvent) => {
|
||||
if (ev.altKey || ev.ctrlKey || ev.shiftKey || ev.metaKey) {
|
||||
// Ignore if modifier keys are pressed
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
fireEvent(mainWindow, "hass-quick-bar-trigger", ev, {
|
||||
bubbles: false,
|
||||
});
|
||||
});
|
||||
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
}
|
||||
|
||||
|
@@ -15,7 +15,7 @@ import {
|
||||
} from "../../src/panels/my/ha-panel-my";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
|
||||
const REDIRECTS: Redirects = {
|
||||
export const REDIRECTS: Redirects = {
|
||||
supervisor: {
|
||||
redirect: "/hassio/dashboard",
|
||||
},
|
||||
@@ -42,6 +42,9 @@ const REDIRECTS: Redirects = {
|
||||
params: {
|
||||
addon: "string",
|
||||
},
|
||||
optional_params: {
|
||||
repository_url: "url",
|
||||
},
|
||||
},
|
||||
supervisor_ingress: {
|
||||
redirect: "/hassio/ingress",
|
||||
@@ -124,6 +127,14 @@ class HassioMyRedirect extends LitElement {
|
||||
}
|
||||
resultParams[key] = params[key];
|
||||
});
|
||||
Object.entries(redirect.optional_params || {}).forEach(([key, type]) => {
|
||||
if (params[key]) {
|
||||
if (!this._checkParamType(type, params[key])) {
|
||||
throw Error();
|
||||
}
|
||||
resultParams[key] = params[key];
|
||||
}
|
||||
});
|
||||
return `?${createSearchParam(resultParams)}`;
|
||||
}
|
||||
|
||||
|
@@ -8,24 +8,27 @@ import { atLeastVersion } from "../../src/common/config/version";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant } from "../../src/types";
|
||||
|
||||
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] => [
|
||||
{
|
||||
translationKey: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? "panel.addons"
|
||||
: "panel.dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? mdiPuzzle
|
||||
: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.system",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] =>
|
||||
atLeastVersion(hass.config.version, 2022, 5)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
translationKey: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? "panel.addons"
|
||||
: "panel.dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? mdiPuzzle
|
||||
: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.system",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
|
@@ -48,7 +48,7 @@ class HassioCoreInfo extends LitElement {
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-card header="Core">
|
||||
<ha-card header="Core" outlined>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
<ha-settings-row>
|
||||
|
@@ -66,7 +66,7 @@ class HassioHostInfo extends LitElement {
|
||||
},
|
||||
];
|
||||
return html`
|
||||
<ha-card header="Host">
|
||||
<ha-card header="Host" outlined>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
${this.supervisor.host.features.includes("hostname")
|
||||
|
@@ -23,6 +23,10 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
UNHEALTHY_REASON_URL,
|
||||
UNSUPPORTED_REASON_URL,
|
||||
} from "../../../src/panels/config/system-health/ha-config-system-health";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
@@ -30,11 +34,6 @@ import { documentationUrl } from "../../../src/util/documentation-url";
|
||||
import "../components/supervisor-metric";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
const UNSUPPORTED_REASON_URL = {};
|
||||
const UNHEALTHY_REASON_URL = {
|
||||
privileged: "/more-info/unsupported/privileged",
|
||||
};
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
class HassioSupervisorInfo extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -58,7 +57,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
},
|
||||
];
|
||||
return html`
|
||||
<ha-card header="Supervisor">
|
||||
<ha-card header="Supervisor" outlined>
|
||||
<div class="card-content">
|
||||
<div>
|
||||
<ha-settings-row>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "../../../src/components/ha-ansi-to-html";
|
||||
import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -11,7 +12,6 @@ import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface LogProvider {
|
||||
@@ -65,7 +65,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-card outlined>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
@@ -89,8 +89,8 @@ class HassioSupervisorLog extends LitElement {
|
||||
|
||||
<div class="card-content" id="content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html .content=${this._content}>
|
||||
</hassio-ansi-to-html>`
|
||||
? html`<ha-ansi-to-html .content=${this._content}>
|
||||
</ha-ansi-to-html>`
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
|
@@ -45,7 +45,6 @@ import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { SUPERVISOR_UPDATE_NAMES } from "../../../src/panels/config/dashboard/ha-config-updates";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { addonArchIsSupported, extractChangelog } from "../util/addon";
|
||||
|
||||
@@ -55,6 +54,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const SUPERVISOR_UPDATE_NAMES = {
|
||||
core: "Home Assistant Core",
|
||||
os: "Home Assistant Operating System",
|
||||
supervisor: "Home Assistant Supervisor",
|
||||
};
|
||||
|
||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||
|
||||
const changelogUrl = (
|
||||
@@ -123,6 +128,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.supervisor.localize("update_available.update_name", {
|
||||
name: this._name,
|
||||
})}
|
||||
|
@@ -72,8 +72,8 @@
|
||||
"@material/mwc-textfield": "0.25.3",
|
||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||
"@mdi/js": "6.5.95",
|
||||
"@mdi/svg": "6.5.95",
|
||||
"@mdi/js": "6.7.96",
|
||||
"@mdi/svg": "6.7.96",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
@@ -108,7 +108,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.1.5",
|
||||
"home-assistant-js-websocket": "^7.0.1",
|
||||
"home-assistant-js-websocket": "^7.0.3",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
@@ -1,3 +1,30 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
|
||||
requires = ["setuptools~=62.3", "wheel~=0.37.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220525.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
||||
]
|
||||
requires-python = ">=3.4.0"
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||
|
||||
[tool.setuptools]
|
||||
platforms = ["any"]
|
||||
zip-safe = false
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["hass_frontend*"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = true
|
||||
strict = true
|
||||
|
@@ -15,7 +15,7 @@ if [ -z $(which hass) ]; then
|
||||
echo "Installing Home Asstant core from dev."
|
||||
python3 -m pip install --upgrade \
|
||||
colorlog \
|
||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||
git+https://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "${WD}/config" ]; then
|
||||
|
@@ -50,14 +50,14 @@ async function main(args) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setup = fs.readFileSync("setup.cfg", "utf8");
|
||||
const version = setup.match(/\d{8}\.\d+/)[0];
|
||||
const setup = fs.readFileSync("pyproject.toml", "utf8");
|
||||
const version = setup.match(/version\W+=\W"(\d{8}\.\d)"/)[1];
|
||||
const newVersion = method(version);
|
||||
|
||||
console.log("Current version:", version);
|
||||
console.log("New version:", newVersion);
|
||||
|
||||
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
|
||||
fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
|
26
setup.cfg
26
setup.cfg
@@ -1,26 +0,0 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220322.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
platforms = any
|
||||
description = The Home Assistant frontend
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/home-assistant/frontend
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
zip_safe = False
|
||||
include_package_data = True
|
||||
python_requires = >= 3.4.0
|
||||
|
||||
[options.packages.find]
|
||||
include =
|
||||
hass_frontend*
|
||||
|
||||
[mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = True
|
||||
strict = True
|
7
setup.py
7
setup.py
@@ -1,7 +0,0 @@
|
||||
"""
|
||||
Entry point for setuptools. Required for editable installs.
|
||||
TODO: Remove file after updating to pip 21.3
|
||||
"""
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
16
src/common/datetime/duration.ts
Normal file
16
src/common/datetime/duration.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import secondsToDuration from "./seconds_to_duration";
|
||||
|
||||
const DAY_IN_SECONDS = 86400;
|
||||
const HOUR_IN_SECONDS = 3600;
|
||||
const MINUTE_IN_SECONDS = 60;
|
||||
|
||||
export const UNIT_TO_SECOND_CONVERT = {
|
||||
s: 1,
|
||||
min: MINUTE_IN_SECONDS,
|
||||
h: HOUR_IN_SECONDS,
|
||||
d: DAY_IN_SECONDS,
|
||||
};
|
||||
|
||||
export const formatDuration = (duration: string, units: string): string =>
|
||||
secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) ||
|
||||
"0";
|
41
src/common/dom/ancestors-with-property.ts
Normal file
41
src/common/dom/ancestors-with-property.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
const DEFAULT_OWN = true;
|
||||
|
||||
// Finds the closest ancestor of an element that has a specific optionally owned property,
|
||||
// traversing slot and shadow root boundaries until the body element is reached
|
||||
export const closestWithProperty = (
|
||||
element: Element | null,
|
||||
property: string | symbol,
|
||||
own = DEFAULT_OWN
|
||||
) => {
|
||||
if (!element || element === document.body) return null;
|
||||
|
||||
element = element.assignedSlot ?? element;
|
||||
if (element.parentElement) {
|
||||
element = element.parentElement;
|
||||
} else {
|
||||
const root = element.getRootNode();
|
||||
element = root instanceof ShadowRoot ? root.host : null;
|
||||
}
|
||||
|
||||
if (
|
||||
own
|
||||
? Object.prototype.hasOwnProperty.call(element, property)
|
||||
: element && property in element
|
||||
)
|
||||
return element;
|
||||
return closestWithProperty(element, property, own);
|
||||
};
|
||||
|
||||
// Finds the set of all such ancestors and includes starting element as first in the set
|
||||
export const ancestorsWithProperty = (
|
||||
element: Element | null,
|
||||
property: string | symbol,
|
||||
own = DEFAULT_OWN
|
||||
) => {
|
||||
const ancestors: Set<Element> = new Set();
|
||||
while (element) {
|
||||
ancestors.add(element);
|
||||
element = closestWithProperty(element, property, own);
|
||||
}
|
||||
return ancestors;
|
||||
};
|
@@ -12,7 +12,7 @@ export const isNavigationClick = (e: MouseEvent) => {
|
||||
|
||||
const anchor = e
|
||||
.composedPath()
|
||||
.filter((n) => (n as HTMLElement).tagName === "A")[0] as
|
||||
.find((n) => (n as HTMLElement).tagName === "A") as
|
||||
| HTMLAnchorElement
|
||||
| undefined;
|
||||
if (
|
||||
|
@@ -29,8 +29,11 @@ import {
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRadioboxBlank,
|
||||
mdiSmoke,
|
||||
mdiSnowflake,
|
||||
mdiSmokeDetector,
|
||||
mdiSmokeDetectorAlert,
|
||||
mdiSmokeDetectorVariant,
|
||||
mdiSmokeDetectorVariantAlert,
|
||||
mdiSquare,
|
||||
mdiSquareOutline,
|
||||
mdiStop,
|
||||
@@ -52,6 +55,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
return is_off ? mdiBattery : mdiBatteryOutline;
|
||||
case "battery_charging":
|
||||
return is_off ? mdiBattery : mdiBatteryCharging;
|
||||
case "carbon_monoxide":
|
||||
return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
|
||||
case "cold":
|
||||
return is_off ? mdiThermometer : mdiSnowflake;
|
||||
case "connectivity":
|
||||
@@ -68,7 +73,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
case "tamper":
|
||||
return is_off ? mdiCheckCircle : mdiAlertCircle;
|
||||
case "smoke":
|
||||
return is_off ? mdiCheckCircle : mdiSmoke;
|
||||
return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
|
||||
case "heat":
|
||||
return is_off ? mdiThermometer : mdiFire;
|
||||
case "light":
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
|
||||
export const computeActiveState = (stateObj: HassEntity): string => {
|
||||
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
|
||||
return stateObj.state;
|
||||
}
|
||||
|
||||
const domain = stateObj.entity_id.split(".")[0];
|
||||
let state = stateObj.state;
|
||||
|
||||
|
@@ -2,50 +2,74 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import {
|
||||
updateIsInstalling,
|
||||
UpdateEntity,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
updateIsInstallingFromAttributes,
|
||||
} from "../../data/update";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
import { formatDateTime } from "../datetime/format_date_time";
|
||||
import { formatTime } from "../datetime/format_time";
|
||||
import { formatNumber, isNumericState } from "../number/format_number";
|
||||
import { formatNumber, isNumericFromAttributes } from "../number/format_number";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { supportsFeature } from "./supports-feature";
|
||||
import { supportsFeatureFromAttributes } from "./supports-feature";
|
||||
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
|
||||
export const computeStateDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
stateObj: HassEntity,
|
||||
locale: FrontendLocaleData,
|
||||
state?: string
|
||||
): string => {
|
||||
const compareState = state !== undefined ? state : stateObj.state;
|
||||
): string =>
|
||||
computeStateDisplayFromEntityAttributes(
|
||||
localize,
|
||||
locale,
|
||||
stateObj.entity_id,
|
||||
stateObj.attributes,
|
||||
state !== undefined ? state : stateObj.state
|
||||
);
|
||||
|
||||
if (compareState === UNKNOWN || compareState === UNAVAILABLE) {
|
||||
return localize(`state.default.${compareState}`);
|
||||
export const computeStateDisplayFromEntityAttributes = (
|
||||
localize: LocalizeFunc,
|
||||
locale: FrontendLocaleData,
|
||||
entityId: string,
|
||||
attributes: any,
|
||||
state: string
|
||||
): string => {
|
||||
if (state === UNKNOWN || state === UNAVAILABLE) {
|
||||
return localize(`state.default.${state}`);
|
||||
}
|
||||
|
||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||
if (isNumericState(stateObj)) {
|
||||
if (stateObj.attributes.device_class === "monetary") {
|
||||
if (isNumericFromAttributes(attributes)) {
|
||||
// state is duration
|
||||
if (
|
||||
attributes.device_class === "duration" &&
|
||||
attributes.unit_of_measurement &&
|
||||
UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement]
|
||||
) {
|
||||
try {
|
||||
return formatNumber(compareState, locale, {
|
||||
return formatDuration(state, attributes.unit_of_measurement);
|
||||
} catch (_err) {
|
||||
// fallback to default
|
||||
}
|
||||
}
|
||||
if (attributes.device_class === "monetary") {
|
||||
try {
|
||||
return formatNumber(state, locale, {
|
||||
style: "currency",
|
||||
currency: stateObj.attributes.unit_of_measurement,
|
||||
currency: attributes.unit_of_measurement,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
} catch (_err) {
|
||||
// fallback to default
|
||||
}
|
||||
}
|
||||
return `${formatNumber(compareState, locale)}${
|
||||
stateObj.attributes.unit_of_measurement
|
||||
? " " + stateObj.attributes.unit_of_measurement
|
||||
: ""
|
||||
return `${formatNumber(state, locale)}${
|
||||
attributes.unit_of_measurement ? " " + attributes.unit_of_measurement : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
const domain = computeStateDomain(stateObj);
|
||||
const domain = computeDomain(entityId);
|
||||
|
||||
if (domain === "input_datetime") {
|
||||
if (state !== undefined) {
|
||||
@@ -80,36 +104,32 @@ export const computeStateDisplay = (
|
||||
} else {
|
||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||
let date: Date;
|
||||
if (stateObj.attributes.has_date && stateObj.attributes.has_time) {
|
||||
if (attributes.has_date && attributes.has_time) {
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
attributes.year,
|
||||
attributes.month - 1,
|
||||
attributes.day,
|
||||
attributes.hour,
|
||||
attributes.minute
|
||||
);
|
||||
return formatDateTime(date, locale);
|
||||
}
|
||||
if (stateObj.attributes.has_date) {
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day
|
||||
);
|
||||
if (attributes.has_date) {
|
||||
date = new Date(attributes.year, attributes.month - 1, attributes.day);
|
||||
return formatDate(date, locale);
|
||||
}
|
||||
if (stateObj.attributes.has_time) {
|
||||
if (attributes.has_time) {
|
||||
date = new Date();
|
||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
||||
date.setHours(attributes.hour, attributes.minute);
|
||||
return formatTime(date, locale);
|
||||
}
|
||||
return stateObj.state;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
if (domain === "humidifier") {
|
||||
if (compareState === "on" && stateObj.attributes.humidity) {
|
||||
return `${stateObj.attributes.humidity} %`;
|
||||
if (state === "on" && attributes.humidity) {
|
||||
return `${attributes.humidity} %`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +139,7 @@ export const computeStateDisplay = (
|
||||
domain === "number" ||
|
||||
domain === "input_number"
|
||||
) {
|
||||
return formatNumber(compareState, locale);
|
||||
return formatNumber(state, locale);
|
||||
}
|
||||
|
||||
// state of button is a timestamp
|
||||
@@ -127,12 +147,12 @@ export const computeStateDisplay = (
|
||||
domain === "button" ||
|
||||
domain === "input_button" ||
|
||||
domain === "scene" ||
|
||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
||||
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||
) {
|
||||
try {
|
||||
return formatDateTime(new Date(compareState), locale);
|
||||
return formatDateTime(new Date(state), locale);
|
||||
} catch (_err) {
|
||||
return compareState;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,30 +163,28 @@ export const computeStateDisplay = (
|
||||
// When the latest version is skipped, show the latest version
|
||||
// When update is not available, show "Up-to-date"
|
||||
// When update is not available and there is no latest_version show "Unavailable"
|
||||
return compareState === "on"
|
||||
? updateIsInstalling(stateObj as UpdateEntity)
|
||||
? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS)
|
||||
return state === "on"
|
||||
? updateIsInstallingFromAttributes(attributes)
|
||||
? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS)
|
||||
? localize("ui.card.update.installing_with_progress", {
|
||||
progress: stateObj.attributes.in_progress,
|
||||
progress: attributes.in_progress,
|
||||
})
|
||||
: localize("ui.card.update.installing")
|
||||
: stateObj.attributes.latest_version
|
||||
: stateObj.attributes.skipped_version ===
|
||||
stateObj.attributes.latest_version
|
||||
? stateObj.attributes.latest_version ??
|
||||
localize("state.default.unavailable")
|
||||
: attributes.latest_version
|
||||
: attributes.skipped_version === attributes.latest_version
|
||||
? attributes.latest_version ?? localize("state.default.unavailable")
|
||||
: localize("ui.card.update.up_to_date");
|
||||
}
|
||||
|
||||
return (
|
||||
// Return device class translation
|
||||
(stateObj.attributes.device_class &&
|
||||
(attributes.device_class &&
|
||||
localize(
|
||||
`component.${domain}.state.${stateObj.attributes.device_class}.${compareState}`
|
||||
`component.${domain}.state.${attributes.device_class}.${state}`
|
||||
)) ||
|
||||
// Return default translation
|
||||
localize(`component.${domain}.state._.${compareState}`) ||
|
||||
localize(`component.${domain}.state._.${state}`) ||
|
||||
// We don't know! Return the raw state.
|
||||
compareState
|
||||
state
|
||||
);
|
||||
};
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeObjectId } from "./compute_object_id";
|
||||
|
||||
export const computeStateNameFromEntityAttributes = (
|
||||
entityId: string,
|
||||
attributes: { [key: string]: any }
|
||||
): string =>
|
||||
attributes.friendly_name === undefined
|
||||
? computeObjectId(entityId).replace(/_/g, " ")
|
||||
: attributes.friendly_name || "";
|
||||
|
||||
export const computeStateName = (stateObj: HassEntity): string =>
|
||||
stateObj.attributes.friendly_name === undefined
|
||||
? computeObjectId(stateObj.entity_id).replace(/_/g, " ")
|
||||
: stateObj.attributes.friendly_name || "";
|
||||
computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);
|
||||
|
@@ -8,29 +8,29 @@ import {
|
||||
mdiCalendar,
|
||||
mdiCast,
|
||||
mdiCastConnected,
|
||||
mdiCheckCircleOutline,
|
||||
mdiClock,
|
||||
mdiCloseCircleOutline,
|
||||
mdiGestureTapButton,
|
||||
mdiLanConnect,
|
||||
mdiLanDisconnect,
|
||||
mdiLightSwitch,
|
||||
mdiLock,
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
mdiLockOpen,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
mdiPackageUp,
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
mdiToggleSwitch,
|
||||
mdiToggleSwitchOff,
|
||||
mdiCheckCircleOutline,
|
||||
mdiCloseCircleOutline,
|
||||
mdiToggleSwitchVariant,
|
||||
mdiToggleSwitchVariantOff,
|
||||
mdiWeatherNight,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||
import { UpdateEntity, updateIsInstalling } from "../../data/update";
|
||||
import { weatherIcon } from "../../data/weather";
|
||||
/**
|
||||
* Return the icon to be used for a domain.
|
||||
*
|
||||
@@ -47,6 +47,20 @@ export const domainIcon = (
|
||||
stateObj?: HassEntity,
|
||||
state?: string
|
||||
): string => {
|
||||
const icon = domainIconWithoutDefault(domain, stateObj, state);
|
||||
if (icon) {
|
||||
return icon;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
console.warn(`Unable to find icon for domain ${domain}`);
|
||||
return DEFAULT_DOMAIN_ICON;
|
||||
};
|
||||
|
||||
export const domainIconWithoutDefault = (
|
||||
domain: string,
|
||||
stateObj?: HassEntity,
|
||||
state?: string
|
||||
): string | undefined => {
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
|
||||
switch (domain) {
|
||||
@@ -88,6 +102,15 @@ export const domainIcon = (
|
||||
? mdiCheckCircleOutline
|
||||
: mdiCloseCircleOutline;
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "lock":
|
||||
switch (compareState) {
|
||||
case "unlocked":
|
||||
@@ -109,9 +132,11 @@ export const domainIcon = (
|
||||
case "outlet":
|
||||
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
|
||||
case "switch":
|
||||
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||
return compareState === "on"
|
||||
? mdiToggleSwitchVariant
|
||||
: mdiToggleSwitchVariantOff;
|
||||
default:
|
||||
return mdiLightSwitch;
|
||||
return mdiToggleSwitchVariant;
|
||||
}
|
||||
|
||||
case "sensor": {
|
||||
@@ -123,15 +148,6 @@ export const domainIcon = (
|
||||
break;
|
||||
}
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "sun":
|
||||
return stateObj?.state === "above_horizon"
|
||||
? FIXED_DOMAIN_ICONS[domain]
|
||||
@@ -143,13 +159,14 @@ export const domainIcon = (
|
||||
? mdiPackageDown
|
||||
: mdiPackageUp
|
||||
: mdiPackage;
|
||||
|
||||
case "weather":
|
||||
return weatherIcon(stateObj?.state);
|
||||
}
|
||||
|
||||
if (domain in FIXED_DOMAIN_ICONS) {
|
||||
return FIXED_DOMAIN_ICONS[domain];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.warn(`Unable to find icon for domain ${domain}`);
|
||||
return DEFAULT_DOMAIN_ICON;
|
||||
return undefined;
|
||||
};
|
||||
|
@@ -3,6 +3,13 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
export const supportsFeature = (
|
||||
stateObj: HassEntity,
|
||||
feature: number
|
||||
): boolean => supportsFeatureFromAttributes(stateObj.attributes, feature);
|
||||
|
||||
export const supportsFeatureFromAttributes = (
|
||||
attributes: {
|
||||
[key: string]: any;
|
||||
},
|
||||
feature: number
|
||||
): boolean =>
|
||||
// eslint-disable-next-line no-bitwise
|
||||
(stateObj.attributes.supported_features! & feature) !== 0;
|
||||
(attributes.supported_features! & feature) !== 0;
|
||||
|
@@ -7,8 +7,11 @@ import { round } from "./round";
|
||||
* @param stateObj The entity state object
|
||||
*/
|
||||
export const isNumericState = (stateObj: HassEntity): boolean =>
|
||||
!!stateObj.attributes.unit_of_measurement ||
|
||||
!!stateObj.attributes.state_class;
|
||||
isNumericFromAttributes(stateObj.attributes);
|
||||
|
||||
export const isNumericFromAttributes = (attributes: {
|
||||
[key: string]: any;
|
||||
}): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
||||
|
||||
export const numberFormatToLocale = (
|
||||
localeOptions: FrontendLocaleData
|
||||
|
@@ -7,6 +7,7 @@ export const iconColorCSS = css`
|
||||
ha-state-icon[data-domain="calendar"][data-state="on"],
|
||||
ha-state-icon[data-domain="camera"][data-state="streaming"],
|
||||
ha-state-icon[data-domain="cover"][data-state="open"],
|
||||
ha-state-icon[data-domain="device_tracker"][data-state="home"],
|
||||
ha-state-icon[data-domain="fan"][data-state="on"],
|
||||
ha-state-icon[data-domain="humidifier"][data-state="on"],
|
||||
ha-state-icon[data-domain="light"][data-state="on"],
|
||||
|
@@ -1,4 +1,4 @@
|
||||
export const promiseTimeout = (ms: number, promise: Promise<any>) => {
|
||||
export const promiseTimeout = (ms: number, promise: Promise<any> | any) => {
|
||||
const timeout = new Promise((_resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(`Timed out in ${ms} ms.`);
|
||||
|
18
src/common/util/subscribe-polling.ts
Normal file
18
src/common/util/subscribe-polling.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export const subscribePollingCollection = (
|
||||
hass: HomeAssistant,
|
||||
updateData: (hass: HomeAssistant) => void,
|
||||
interval: number
|
||||
) => {
|
||||
let timeout;
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
await updateData(hass);
|
||||
} finally {
|
||||
timeout = setTimeout(() => fetchData(), interval);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
return () => clearTimeout(timeout);
|
||||
};
|
@@ -13,7 +13,7 @@ export const throttle = <T extends any[]>(
|
||||
) => {
|
||||
let timeout: number | undefined;
|
||||
let previous = 0;
|
||||
return (...args: T): void => {
|
||||
const throttledFunc = (...args: T): void => {
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
@@ -35,4 +35,10 @@ export const throttle = <T extends any[]>(
|
||||
timeout = window.setTimeout(later, remaining);
|
||||
}
|
||||
};
|
||||
throttledFunc.cancel = () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
previous = 0;
|
||||
};
|
||||
return throttledFunc;
|
||||
};
|
||||
|
53
src/common/util/time-cache-entity-promise-func.ts
Normal file
53
src/common/util/time-cache-entity-promise-func.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
interface ResultCache<T> {
|
||||
[entityId: string]: Promise<T> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a function with result caching per entity.
|
||||
* @param cacheKey key to store the cache on hass object
|
||||
* @param cacheTime time to cache the results
|
||||
* @param func function to fetch the data
|
||||
* @param hass Home Assistant object
|
||||
* @param entityId entity to fetch data for
|
||||
* @param args extra arguments to pass to the function to fetch the data
|
||||
* @returns
|
||||
*/
|
||||
export const timeCacheEntityPromiseFunc = async <T>(
|
||||
cacheKey: string,
|
||||
cacheTime: number,
|
||||
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
...args: any[]
|
||||
): Promise<T> => {
|
||||
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
||||
|
||||
if (!cache) {
|
||||
cache = hass[cacheKey] = {};
|
||||
}
|
||||
|
||||
const lastResult = cache[entityId];
|
||||
|
||||
if (lastResult) {
|
||||
return lastResult;
|
||||
}
|
||||
|
||||
const result = func(hass, entityId, ...args);
|
||||
cache[entityId] = result;
|
||||
|
||||
result.then(
|
||||
// When successful, set timer to clear cache
|
||||
() =>
|
||||
setTimeout(() => {
|
||||
cache![entityId] = undefined;
|
||||
}, cacheTime),
|
||||
// On failure, clear cache right away
|
||||
() => {
|
||||
cache![entityId] = undefined;
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
@@ -1,43 +1,80 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
interface ResultCache<T> {
|
||||
[entityId: string]: Promise<T> | undefined;
|
||||
interface CacheResult<T> {
|
||||
result: T;
|
||||
cacheKey: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a result of a promise for X time. Allows optional extra validation
|
||||
* check to invalidate the cache.
|
||||
* @param cacheKey the key to store the cache
|
||||
* @param cacheTime the time to cache the result
|
||||
* @param func the function to fetch the data
|
||||
* @param generateCacheKey optional function to generate a cache key based on current hass + cached result. Cache is invalid if generates a different cache key.
|
||||
* @param hass Home Assistant object
|
||||
* @param args extra arguments to pass to the function to fetch the data
|
||||
* @returns
|
||||
*/
|
||||
export const timeCachePromiseFunc = async <T>(
|
||||
cacheKey: string,
|
||||
cacheTime: number,
|
||||
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
|
||||
func: (hass: HomeAssistant, ...args: any[]) => Promise<T>,
|
||||
generateCacheKey:
|
||||
| ((hass: HomeAssistant, lastResult: T) => unknown)
|
||||
| undefined,
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
...args: any[]
|
||||
): Promise<T> => {
|
||||
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
||||
const anyHass = hass as any;
|
||||
const lastResult: Promise<CacheResult<T>> | CacheResult<T> | undefined =
|
||||
anyHass[cacheKey];
|
||||
|
||||
if (!cache) {
|
||||
cache = hass[cacheKey] = {};
|
||||
}
|
||||
const checkCachedResult = (result: CacheResult<T>): T | Promise<T> => {
|
||||
if (
|
||||
!generateCacheKey ||
|
||||
generateCacheKey(hass, result.result) === result.cacheKey
|
||||
) {
|
||||
return result.result;
|
||||
}
|
||||
|
||||
const lastResult = cache[entityId];
|
||||
anyHass[cacheKey] = undefined;
|
||||
return timeCachePromiseFunc(
|
||||
cacheKey,
|
||||
cacheTime,
|
||||
func,
|
||||
generateCacheKey,
|
||||
hass,
|
||||
...args
|
||||
);
|
||||
};
|
||||
|
||||
// If we have a cached result, return it if it's still valid
|
||||
if (lastResult) {
|
||||
return lastResult;
|
||||
return lastResult instanceof Promise
|
||||
? lastResult.then(checkCachedResult)
|
||||
: checkCachedResult(lastResult);
|
||||
}
|
||||
|
||||
const result = func(hass, entityId, ...args);
|
||||
cache[entityId] = result;
|
||||
const resultPromise = func(hass, ...args);
|
||||
anyHass[cacheKey] = resultPromise;
|
||||
|
||||
result.then(
|
||||
resultPromise.then(
|
||||
// When successful, set timer to clear cache
|
||||
() =>
|
||||
(result) => {
|
||||
anyHass[cacheKey] = {
|
||||
result,
|
||||
cacheKey: generateCacheKey?.(hass, result),
|
||||
};
|
||||
setTimeout(() => {
|
||||
cache![entityId] = undefined;
|
||||
}, cacheTime),
|
||||
anyHass[cacheKey] = undefined;
|
||||
}, cacheTime);
|
||||
},
|
||||
// On failure, clear cache right away
|
||||
() => {
|
||||
cache![entityId] = undefined;
|
||||
anyHass[cacheKey] = undefined;
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
return resultPromise;
|
||||
};
|
||||
|
@@ -34,7 +34,7 @@ import {
|
||||
endOfMonth,
|
||||
endOfQuarter,
|
||||
endOfYear,
|
||||
} from "date-fns";
|
||||
} from "date-fns/esm";
|
||||
import {
|
||||
formatDate,
|
||||
formatDateMonth,
|
||||
|
@@ -347,8 +347,8 @@ class StatisticsChart extends LitElement {
|
||||
statTypes.forEach((type) => {
|
||||
let val: number | null;
|
||||
if (type === "sum") {
|
||||
if (!initVal) {
|
||||
initVal = val = stat.state;
|
||||
if (initVal === null) {
|
||||
initVal = val = stat.state || 0;
|
||||
prevSum = stat.sum;
|
||||
} else {
|
||||
val = initVal + ((stat.sum || 0) - prevSum!);
|
||||
|
@@ -1,165 +1,167 @@
|
||||
export const currencies = [
|
||||
"AED",
|
||||
"AFN",
|
||||
"ALL",
|
||||
"AMD",
|
||||
"ANG",
|
||||
"AOA",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"AWG",
|
||||
"AZN",
|
||||
"BAM",
|
||||
"BBD",
|
||||
"BDT",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BIF",
|
||||
"BMD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BSD",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BYN",
|
||||
"BYR",
|
||||
"BZD",
|
||||
"CAD",
|
||||
"CDF",
|
||||
"CHF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CUP",
|
||||
"CVE",
|
||||
"CZK",
|
||||
"DJF",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EGP",
|
||||
"ERN",
|
||||
"ETB",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"FKP",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GHS",
|
||||
"GIP",
|
||||
"GMD",
|
||||
"GNF",
|
||||
"GTQ",
|
||||
"GYD",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HTG",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"INR",
|
||||
"IQD",
|
||||
"IRR",
|
||||
"ISK",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KGS",
|
||||
"KHR",
|
||||
"KMF",
|
||||
"KPW",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LAK",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LRD",
|
||||
"LSL",
|
||||
"LTL",
|
||||
"LYD",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MGA",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MNT",
|
||||
"MOP",
|
||||
"MRO",
|
||||
"MUR",
|
||||
"MVR",
|
||||
"MWK",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"MZN",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"RWF",
|
||||
"SAR",
|
||||
"SBD",
|
||||
"SCR",
|
||||
"SDG",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SHP",
|
||||
"SLL",
|
||||
"SOS",
|
||||
"SRD",
|
||||
"SSP",
|
||||
"STD",
|
||||
"SYP",
|
||||
"SZL",
|
||||
"THB",
|
||||
"TJS",
|
||||
"TMT",
|
||||
"TND",
|
||||
"TOP",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VEF",
|
||||
"VND",
|
||||
"VUV",
|
||||
"WST",
|
||||
"XAF",
|
||||
"XCD",
|
||||
"XOF",
|
||||
"XPF",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMK",
|
||||
"ZWL",
|
||||
];
|
||||
|
||||
export const createCurrencyListEl = () => {
|
||||
const list = document.createElement("datalist");
|
||||
list.id = "currencies";
|
||||
for (const currency of [
|
||||
"AED",
|
||||
"AFN",
|
||||
"ALL",
|
||||
"AMD",
|
||||
"ANG",
|
||||
"AOA",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"AWG",
|
||||
"AZN",
|
||||
"BAM",
|
||||
"BBD",
|
||||
"BDT",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BIF",
|
||||
"BMD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BSD",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BYN",
|
||||
"BYR",
|
||||
"BZD",
|
||||
"CAD",
|
||||
"CDF",
|
||||
"CHF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CUP",
|
||||
"CVE",
|
||||
"CZK",
|
||||
"DJF",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EGP",
|
||||
"ERN",
|
||||
"ETB",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"FKP",
|
||||
"GBP",
|
||||
"GEL",
|
||||
"GHS",
|
||||
"GIP",
|
||||
"GMD",
|
||||
"GNF",
|
||||
"GTQ",
|
||||
"GYD",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HTG",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"INR",
|
||||
"IQD",
|
||||
"IRR",
|
||||
"ISK",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KGS",
|
||||
"KHR",
|
||||
"KMF",
|
||||
"KPW",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LAK",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LRD",
|
||||
"LSL",
|
||||
"LTL",
|
||||
"LYD",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MGA",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MNT",
|
||||
"MOP",
|
||||
"MRO",
|
||||
"MUR",
|
||||
"MVR",
|
||||
"MWK",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"MZN",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PAB",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"RWF",
|
||||
"SAR",
|
||||
"SBD",
|
||||
"SCR",
|
||||
"SDG",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SHP",
|
||||
"SLL",
|
||||
"SOS",
|
||||
"SRD",
|
||||
"SSP",
|
||||
"STD",
|
||||
"SYP",
|
||||
"SZL",
|
||||
"THB",
|
||||
"TJS",
|
||||
"TMT",
|
||||
"TND",
|
||||
"TOP",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VEF",
|
||||
"VND",
|
||||
"VUV",
|
||||
"WST",
|
||||
"XAF",
|
||||
"XCD",
|
||||
"XOF",
|
||||
"XPF",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMK",
|
||||
"ZWL",
|
||||
]) {
|
||||
for (const currency of currencies) {
|
||||
const option = document.createElement("option");
|
||||
option.value = currency;
|
||||
option.innerHTML = currency;
|
||||
|
@@ -269,8 +269,8 @@ export class HaDataTable extends LitElement {
|
||||
@change=${this._handleHeaderRowCheckboxClick}
|
||||
.indeterminate=${this._checkedRows.length &&
|
||||
this._checkedRows.length !== this._checkableRowsCount}
|
||||
.checked=${this._checkedRows.length ===
|
||||
this._checkableRowsCount}
|
||||
.checked=${this._checkedRows.length &&
|
||||
this._checkedRows.length === this._checkableRowsCount}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</div>
|
||||
|
@@ -5,6 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
DeviceAutomation,
|
||||
deviceAutomationsEqual,
|
||||
sortDeviceAutomations,
|
||||
} from "../../data/device_automation";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-select";
|
||||
@@ -127,7 +128,9 @@ export abstract class HaDeviceAutomationPicker<
|
||||
|
||||
private async _updateDeviceInfo() {
|
||||
this._automations = this.deviceId
|
||||
? await this._fetchDeviceAutomations(this.hass, this.deviceId)
|
||||
? (await this._fetchDeviceAutomations(this.hass, this.deviceId)).sort(
|
||||
sortDeviceAutomations
|
||||
)
|
||||
: // No device, clear the list of automations
|
||||
[];
|
||||
|
||||
@@ -161,8 +164,9 @@ export abstract class HaDeviceAutomationPicker<
|
||||
if (this.value && deviceAutomationsEqual(automation, this.value)) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "change");
|
||||
fireEvent(this, "value-changed", { value: automation });
|
||||
const value = { ...automation };
|
||||
delete value.metadata;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -52,6 +52,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public devices?: DeviceRegistryEntry[];
|
||||
|
||||
@property() public areas?: AreaRegistryEntry[];
|
||||
@@ -86,6 +88,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@state() private _opened?: boolean;
|
||||
|
||||
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||
@@ -194,9 +198,10 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
),
|
||||
area: device.area_id
|
||||
? areaLookup[device.area_id].name
|
||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||
area:
|
||||
device.area_id && areaLookup[device.area_id]
|
||||
? areaLookup[device.area_id].name
|
||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||
}));
|
||||
if (!outputDevices.length) {
|
||||
return [
|
||||
@@ -267,8 +272,10 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
? this.hass.localize("ui.components.device-picker.device")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.helper=${this.helper}
|
||||
.renderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
item-value-path="id"
|
||||
item-label-path="name"
|
||||
@opened-changed=${this._openedChanged}
|
||||
|
@@ -4,6 +4,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-device-picker";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker";
|
||||
|
||||
@customElement("ha-devices-picker")
|
||||
class HaDevicesPicker extends LitElement {
|
||||
@@ -11,6 +12,12 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
@property() public value?: string[];
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {string}
|
||||
@@ -35,6 +42,8 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@@ -49,11 +58,13 @@ class HaDevicesPicker extends LitElement {
|
||||
allow-custom-entity
|
||||
.curValue=${entityId}
|
||||
.hass=${this.hass}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedDeviceLabel}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._deviceChanged}
|
||||
></ha-device-picker>
|
||||
</div>
|
||||
@@ -61,11 +72,16 @@ class HaDevicesPicker extends LitElement {
|
||||
)}
|
||||
<div>
|
||||
<ha-device-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.label=${this.pickDeviceLabel}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !currentDevices.length}
|
||||
@value-changed=${this._addDevice}
|
||||
></ha-device-picker>
|
||||
</div>
|
||||
|
@@ -14,6 +14,12 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
@property({ type: Array }) public value?: string[];
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {string}
|
||||
@@ -92,6 +98,7 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.entityFilter=${this._entityFilter}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedEntityLabel}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._entityChanged}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
@@ -99,6 +106,7 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
)}
|
||||
<div>
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
@@ -108,6 +116,9 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.label=${this.pickEntityLabel}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !currentEntities.length}
|
||||
@value-changed=${this._addEntity}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
@@ -19,6 +19,8 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@@ -26,6 +28,8 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) private _opened = false;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
@@ -61,6 +65,8 @@ class HaEntityAttributePicker extends LitElement {
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
)}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
|
@@ -20,7 +20,7 @@ interface HassEntityWithCachedName extends HassEntity {
|
||||
friendly_name: string;
|
||||
}
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
export type HaEntityPickerEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
|
||||
@@ -39,6 +39,8 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-entity" })
|
||||
public allowCustomEntity;
|
||||
|
||||
@@ -46,6 +48,8 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {Array}
|
||||
@@ -302,9 +306,11 @@ export class HaEntityPicker extends LitElement {
|
||||
.label=${this.label === undefined
|
||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||
: this.label}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._states}
|
||||
.renderer=${rowRenderer}
|
||||
.required=${this.required}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
|
@@ -13,9 +13,12 @@ import { HaComboBox } from "./ha-combo-box";
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
||||
item
|
||||
) => html`<mwc-list-item twoline>
|
||||
) => html`<mwc-list-item twoline graphic="icon">
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.slug}</span>
|
||||
${item.icon
|
||||
? html`<img slot="graphic" .src="/api/hassio/addons/${item.slug}/icon" />`
|
||||
: ""}
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-addon-picker")
|
||||
@@ -26,10 +29,14 @@ class HaAddonPicker extends LitElement {
|
||||
|
||||
@property() public value = "";
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
||||
|
||||
public open() {
|
||||
@@ -55,6 +62,9 @@ class HaAddonPicker extends LitElement {
|
||||
? this.hass.localize("ui.components.addon-picker.addon")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.renderer=${rowRenderer}
|
||||
.items=${this._addons}
|
||||
item-value-path="slug"
|
||||
|
@@ -2,12 +2,12 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { Analytics, AnalyticsPreferences } from "../data/analytics";
|
||||
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-checkbox";
|
||||
import type { HaCheckbox } from "./ha-checkbox";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-settings-row";
|
||||
import "./ha-switch";
|
||||
import type { HaSwitch } from "./ha-switch";
|
||||
|
||||
const ADDITIONAL_PREFERENCES = [
|
||||
{
|
||||
@@ -40,62 +40,62 @@ export class HaAnalytics extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-settings-row>
|
||||
<span slot="prefix">
|
||||
<ha-checkbox
|
||||
@change=${this._handleRowCheckboxClick}
|
||||
.checked=${baseEnabled}
|
||||
.preference=${"base"}
|
||||
.disabled=${loading}
|
||||
name="base"
|
||||
>
|
||||
</ha-checkbox>
|
||||
</span>
|
||||
<span slot="heading" data-for="base"> Basic analytics </span>
|
||||
<span slot="description" data-for="base">
|
||||
This includes information about your system.
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${baseEnabled}
|
||||
.preference=${"base"}
|
||||
.disabled=${loading}
|
||||
name="base"
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
${ADDITIONAL_PREFERENCES.map(
|
||||
(preference) =>
|
||||
html`<ha-settings-row>
|
||||
<span slot="prefix">
|
||||
<ha-checkbox
|
||||
@change=${this._handleRowCheckboxClick}
|
||||
.checked=${this.analytics?.preferences[preference.key]}
|
||||
.preference=${preference.key}
|
||||
name=${preference.key}
|
||||
>
|
||||
</ha-checkbox>
|
||||
${!baseEnabled
|
||||
? html`<paper-tooltip animation-delay="0" position="right">
|
||||
You need to enable basic analytics for this option to be
|
||||
available
|
||||
</paper-tooltip>`
|
||||
: ""}
|
||||
</span>
|
||||
<span slot="heading" data-for=${preference.key}>
|
||||
${preference.title}
|
||||
</span>
|
||||
<span slot="description" data-for=${preference.key}>
|
||||
${preference.description}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
html`
|
||||
<ha-settings-row>
|
||||
<span slot="heading" data-for=${preference.key}>
|
||||
${preference.title}
|
||||
</span>
|
||||
<span slot="description" data-for=${preference.key}>
|
||||
${preference.description}
|
||||
</span>
|
||||
<span>
|
||||
<ha-switch
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${this.analytics?.preferences[preference.key]}
|
||||
.preference=${preference.key}
|
||||
name=${preference.key}
|
||||
>
|
||||
</ha-switch>
|
||||
${!baseEnabled
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0" position="right">
|
||||
You need to enable basic analytics for this option to be
|
||||
available
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
`
|
||||
)}
|
||||
<ha-settings-row>
|
||||
<span slot="prefix">
|
||||
<ha-checkbox
|
||||
@change=${this._handleRowCheckboxClick}
|
||||
.checked=${this.analytics?.preferences.diagnostics}
|
||||
.preference=${"diagnostics"}
|
||||
.disabled=${loading}
|
||||
name="diagnostics"
|
||||
>
|
||||
</ha-checkbox>
|
||||
</span>
|
||||
<span slot="heading" data-for="diagnostics"> Diagnostics </span>
|
||||
<span slot="description" data-for="diagnostics">
|
||||
Share crash reports when unexpected errors occur.
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._handleRowClick}
|
||||
.checked=${this.analytics?.preferences.diagnostics}
|
||||
.preference=${"diagnostics"}
|
||||
.disabled=${loading}
|
||||
name="diagnostics"
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
`;
|
||||
}
|
||||
@@ -120,23 +120,23 @@ export class HaAnalytics extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRowCheckboxClick(ev: Event) {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
const preference = (checkbox as any).preference;
|
||||
private _handleRowClick(ev: Event) {
|
||||
const target = ev.currentTarget as HaSwitch;
|
||||
const preference = (target as any).preference;
|
||||
const preferences = this.analytics ? { ...this.analytics.preferences } : {};
|
||||
|
||||
if (preferences[preference] === checkbox.checked) {
|
||||
if (preferences[preference] === target.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
preferences[preference] = checkbox.checked;
|
||||
preferences[preference] = target.checked;
|
||||
|
||||
if (
|
||||
ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) &&
|
||||
checkbox.checked
|
||||
target.checked
|
||||
) {
|
||||
preferences.base = true;
|
||||
} else if (preference === "base" && !checkbox.checked) {
|
||||
} else if (preference === "base" && !target.checked) {
|
||||
preferences.usage = false;
|
||||
preferences.statistics = false;
|
||||
}
|
||||
|
@@ -10,8 +10,8 @@ interface State {
|
||||
backgroundColor: null | string;
|
||||
}
|
||||
|
||||
@customElement("hassio-ansi-to-html")
|
||||
class HassioAnsiToHtml extends LitElement {
|
||||
@customElement("ha-ansi-to-html")
|
||||
class HaAnsiToHtml extends LitElement {
|
||||
@property() public content!: string;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@@ -241,6 +241,6 @@ class HassioAnsiToHtml extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-ansi-to-html": HassioAnsiToHtml;
|
||||
"ha-ansi-to-html": HaAnsiToHtml;
|
||||
}
|
||||
}
|
@@ -28,8 +28,8 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@@ -49,6 +49,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
@@ -84,6 +86,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
@@ -310,11 +314,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
item-value-path="area_id"
|
||||
item-id-path="area_id"
|
||||
item-label-path="name"
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.area-picker.area")
|
||||
: this.label}
|
||||
@@ -403,7 +409,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
name,
|
||||
});
|
||||
this._areas = [...this._areas!, area];
|
||||
(this.comboBox as any).items = this._getAreas(
|
||||
(this.comboBox as any).filteredItems = this._getAreas(
|
||||
this._areas!,
|
||||
this._devices!,
|
||||
this._entities!,
|
||||
|
166
src/components/ha-areas-picker.ts
Normal file
166
src/components/ha-areas-picker.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { EntityRegistryEntry } from "../data/entity_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-area-picker";
|
||||
|
||||
@customElement("ha-areas-picker")
|
||||
export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string[];
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
public noAdd?: boolean;
|
||||
|
||||
/**
|
||||
* Show only areas with entities from specific domains.
|
||||
* @type {Array}
|
||||
* @attr include-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-domains" })
|
||||
public includeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show no areas with entities of these domains.
|
||||
* @type {Array}
|
||||
* @attr exclude-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-domains" })
|
||||
public excludeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only areas with entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
||||
|
||||
@property({ attribute: "picked-area-label" })
|
||||
public pickedAreaLabel?: string;
|
||||
|
||||
@property({ attribute: "pick-area-label" })
|
||||
public pickAreaLabel?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const currentAreas = this._currentAreas;
|
||||
return html`
|
||||
${currentAreas.map(
|
||||
(area) => html`
|
||||
<div>
|
||||
<ha-area-picker
|
||||
.curValue=${area}
|
||||
.noAdd=${this.noAdd}
|
||||
.hass=${this.hass}
|
||||
.value=${area}
|
||||
.label=${this.pickedAreaLabel}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._areaChanged}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div>
|
||||
<ha-area-picker
|
||||
.noAdd=${this.noAdd}
|
||||
.hass=${this.hass}
|
||||
.label=${this.pickAreaLabel}
|
||||
.helper=${this.helper}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.disabled=${this.disabled}
|
||||
.placeholder=${this.placeholder}
|
||||
.required=${this.required && !currentAreas.length}
|
||||
@value-changed=${this._addArea}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _currentAreas(): string[] {
|
||||
return this.value || [];
|
||||
}
|
||||
|
||||
private async _updateAreas(areas) {
|
||||
this.value = areas;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: areas,
|
||||
});
|
||||
}
|
||||
|
||||
private _areaChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const curValue = (ev.currentTarget as any).curValue;
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue === curValue) {
|
||||
return;
|
||||
}
|
||||
const currentAreas = this._currentAreas;
|
||||
if (!newValue || currentAreas.includes(newValue)) {
|
||||
this._updateAreas(currentAreas.filter((ent) => ent !== curValue));
|
||||
return;
|
||||
}
|
||||
this._updateAreas(
|
||||
currentAreas.map((ent) => (ent === curValue ? newValue : ent))
|
||||
);
|
||||
}
|
||||
|
||||
private _addArea(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
const toAdd = ev.detail.value;
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
(ev.currentTarget as any).value = "";
|
||||
const currentAreas = this._currentAreas;
|
||||
if (currentAreas.includes(toAdd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateAreas([...currentAreas, toAdd]);
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-areas-picker": HaAreasPicker;
|
||||
}
|
||||
}
|
@@ -1,12 +1,14 @@
|
||||
import { LitElement, html, TemplateResult, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "./ha-select";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "./ha-textfield";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import "./ha-select";
|
||||
import "./ha-textfield";
|
||||
import "./ha-input-helper-text";
|
||||
|
||||
export interface TimeChangedEvent {
|
||||
days?: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds: number;
|
||||
@@ -21,6 +23,11 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property() label?: string;
|
||||
|
||||
/**
|
||||
* Helper for the input
|
||||
*/
|
||||
@property() helper?: string;
|
||||
|
||||
/**
|
||||
* auto validate time inputs
|
||||
*/
|
||||
@@ -41,6 +48,11 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
|
||||
/**
|
||||
* day
|
||||
*/
|
||||
@property({ type: Number }) days = 0;
|
||||
|
||||
/**
|
||||
* hour
|
||||
*/
|
||||
@@ -61,6 +73,11 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property({ type: Number }) milliseconds = 0;
|
||||
|
||||
/**
|
||||
* Label for the day input
|
||||
*/
|
||||
@property() dayLabel = "";
|
||||
|
||||
/**
|
||||
* Label for the hour input
|
||||
*/
|
||||
@@ -91,6 +108,11 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property({ type: Boolean }) enableMillisecond = false;
|
||||
|
||||
/**
|
||||
* show the day field
|
||||
*/
|
||||
@property({ type: Boolean }) enableDay = false;
|
||||
|
||||
/**
|
||||
* limit hours input
|
||||
*/
|
||||
@@ -108,8 +130,33 @@ export class HaBaseTimeInput extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||
${this.label
|
||||
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
||||
: ""}
|
||||
<div class="time-input-wrap">
|
||||
${this.enableDay
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="day"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this.days}
|
||||
.label=${this.dayLabel}
|
||||
name="days"
|
||||
@input=${this._valueChanged}
|
||||
@focus=${this._onFocus}
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
suffix=":"
|
||||
class="hasSuffix"
|
||||
>
|
||||
</ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-textfield
|
||||
id="hour"
|
||||
type="number"
|
||||
@@ -207,6 +254,9 @@ export class HaBaseTimeInput extends LitElement {
|
||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||
</ha-select>`}
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -260,6 +310,7 @@ export class HaBaseTimeInput extends LitElement {
|
||||
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
direction: ltr;
|
||||
}
|
||||
ha-textfield {
|
||||
width: 40px;
|
||||
|
@@ -1,17 +1,22 @@
|
||||
import type { Button } from "@material/mwc-button";
|
||||
import "@material/mwc-menu";
|
||||
import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
|
||||
import type { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
@customElement("ha-button-menu")
|
||||
export class HaButtonMenu extends LitElement {
|
||||
protected readonly [FOCUS_TARGET];
|
||||
|
||||
@property() public corner: Corner = "TOP_START";
|
||||
|
||||
@property() public menuCorner: MenuCorner = "START";
|
||||
|
||||
@property({ type: Number }) public x?: number;
|
||||
@property({ type: Number }) public x: number | null = null;
|
||||
|
||||
@property({ type: Number }) public y?: number;
|
||||
@property({ type: Number }) public y: number | null = null;
|
||||
|
||||
@property({ type: Boolean }) public multi = false;
|
||||
|
||||
@@ -31,10 +36,18 @@ export class HaButtonMenu extends LitElement {
|
||||
return this._menu?.selected;
|
||||
}
|
||||
|
||||
public override focus() {
|
||||
if (this._menu?.open) {
|
||||
this._menu.focusItemAtIndex(0);
|
||||
} else {
|
||||
this._triggerButton?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger"></slot>
|
||||
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
|
||||
</div>
|
||||
<mwc-menu
|
||||
.corner=${this.corner}
|
||||
@@ -50,6 +63,21 @@ export class HaButtonMenu extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
if (document.dir === "rtl") {
|
||||
this.updateComplete.then(() => {
|
||||
this.querySelectorAll("mwc-list-item").forEach((item) => {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML =
|
||||
"span.material-icons:first-of-type { margin-left: var(--mdc-list-item-graphic-margin, 32px) !important; margin-right: 0px !important;}";
|
||||
item!.shadowRoot!.appendChild(style);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleClick(): void {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
@@ -58,6 +86,18 @@ export class HaButtonMenu extends LitElement {
|
||||
this._menu!.show();
|
||||
}
|
||||
|
||||
private get _triggerButton() {
|
||||
return this.querySelector(
|
||||
'ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]'
|
||||
) as HaIconButton | Button | null;
|
||||
}
|
||||
|
||||
private _setTriggerAria() {
|
||||
if (this._triggerButton) {
|
||||
this._triggerButton.ariaHasPopup = "menu";
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@@ -117,6 +117,19 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
--mdc-shape-small: 4px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
:host([dir="rtl"]) ha-icon-button:first-child,
|
||||
:host([dir="rtl"]) mwc-button:first-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
--mdc-shape-small: 0 4px 4px 0;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
:host([dir="rtl"]) ha-icon-button:last-child,
|
||||
:host([dir="rtl"]) mwc-button:last-child {
|
||||
--mdc-shape-small: 4px 0 0 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -25,13 +25,7 @@ export class HaChipSet extends LitElement {
|
||||
${unsafeCSS(chipStyles)}
|
||||
|
||||
slot::slotted(ha-chip) {
|
||||
margin: 4px;
|
||||
}
|
||||
slot::slotted(ha-chip:first-of-type) {
|
||||
margin-left: -4px;
|
||||
}
|
||||
slot::slotted(ha-chip:last-of-type) {
|
||||
margin-right: -4px;
|
||||
margin: 4px 4px 4px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ import { customElement, property } from "lit/decorators";
|
||||
export class HaChip extends LitElement {
|
||||
@property({ type: Boolean }) public hasIcon = false;
|
||||
|
||||
@property({ type: Boolean }) public hasTrailingIcon = false;
|
||||
|
||||
@property({ type: Boolean }) public noText = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -30,6 +32,11 @@ export class HaChip extends LitElement {
|
||||
<span class="mdc-chip__text"><slot></slot></span>
|
||||
</span>
|
||||
</span>
|
||||
${this.hasTrailingIcon
|
||||
? html`<div class="mdc-chip__icon mdc-chip__icon--trailing">
|
||||
<slot name="trailing-icon"></slot>
|
||||
</div>`
|
||||
: null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -53,13 +60,23 @@ export class HaChip extends LitElement {
|
||||
color: var(--ha-chip-text-color, var(--primary-text-color));
|
||||
}
|
||||
|
||||
.mdc-chip__icon--leading {
|
||||
--mdc-icon-size: 20px;
|
||||
.mdc-chip__icon--leading,
|
||||
.mdc-chip__icon--trailing {
|
||||
--mdc-icon-size: 18px;
|
||||
line-height: 14px;
|
||||
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
||||
}
|
||||
.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
|
||||
.mdc-chip.no-text
|
||||
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
|
||||
margin-right: -4px;
|
||||
margin-inline-start: -4px;
|
||||
margin-inline-end: 4px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
span[role="gridcell"] {
|
||||
line-height: 14px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
85
src/components/ha-clickable-list-item.ts
Normal file
85
src/components/ha-clickable-list-item.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
|
||||
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
||||
import { css, CSSResult, html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
|
||||
@customElement("ha-clickable-list-item")
|
||||
export class HaClickableListItem extends ListItemBase {
|
||||
@property() public href?: string;
|
||||
|
||||
@property({ type: Boolean }) public disableHref = false;
|
||||
|
||||
// property used only in css
|
||||
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public openNewTab = false;
|
||||
|
||||
@query("a") private _anchor!: HTMLAnchorElement;
|
||||
|
||||
public render() {
|
||||
const r = super.render();
|
||||
const href = this.href || "";
|
||||
|
||||
return html`${this.disableHref
|
||||
? html`<a aria-role="option">${r}</a>`
|
||||
: html`<a
|
||||
aria-role="option"
|
||||
target=${this.openNewTab ? "_blank" : ""}
|
||||
href=${href}
|
||||
>${r}</a
|
||||
>`}`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
super.firstUpdated();
|
||||
this.addEventListener("keydown", (ev) => {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
this._anchor.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
styles,
|
||||
css`
|
||||
:host {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
:host([graphic="avatar"]:not([twoLine])),
|
||||
:host([graphic="icon"]:not([twoLine])) {
|
||||
height: 48px;
|
||||
}
|
||||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
span.material-icons:first-of-type {
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: var(
|
||||
--mdc-list-item-graphic-margin,
|
||||
16px
|
||||
) !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
span.material-icons:last-of-type {
|
||||
margin-inline-start: auto !important;
|
||||
margin-inline-end: 0px !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-clickable-list-item": HaClickableListItem;
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatNumber } from "../common/number/format_number";
|
||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||
import { UNAVAILABLE_STATES } from "../data/entity";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-climate-state")
|
||||
@@ -15,22 +16,22 @@ class HaClimateState extends LitElement {
|
||||
const currentStatus = this._computeCurrentStatus();
|
||||
|
||||
return html`<div class="target">
|
||||
${this.stateObj.state !== "unknown"
|
||||
${!UNAVAILABLE_STATES.includes(this.stateObj.state)
|
||||
? html`<span class="state-label">
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||
? html`-
|
||||
${this.hass.localize(
|
||||
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||
) || this.stateObj.attributes.preset_mode}`
|
||||
: ""}
|
||||
</span>`
|
||||
: ""}
|
||||
<div class="unit">${this._computeTarget()}</div>
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||
? html`-
|
||||
${this.hass.localize(
|
||||
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||
) || this.stateObj.attributes.preset_mode}`
|
||||
: ""}
|
||||
</span>
|
||||
<div class="unit">${this._computeTarget()}</div>`
|
||||
: this._localizeState()}
|
||||
</div>
|
||||
|
||||
${currentStatus
|
||||
${currentStatus && !UNAVAILABLE_STATES.includes(this.stateObj.state)
|
||||
? html`<div class="current">
|
||||
${this.hass.localize("ui.card.climate.currently")}:
|
||||
<div class="unit">${currentStatus}</div>
|
||||
@@ -108,6 +109,10 @@ class HaClimateState extends LitElement {
|
||||
}
|
||||
|
||||
private _localizeState(): string {
|
||||
if (UNAVAILABLE_STATES.includes(this.stateObj.state)) {
|
||||
return this.hass.localize(`state.default.${this.stateObj.state}`);
|
||||
}
|
||||
|
||||
const stateString = this.hass.localize(
|
||||
`component.climate.state._.${this.stateObj.state}`
|
||||
);
|
||||
|
@@ -64,6 +64,8 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
@property() public validationMessage?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean }) public invalid?: boolean;
|
||||
@@ -87,6 +89,8 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "opened" })
|
||||
private _opened?: boolean;
|
||||
|
||||
@@ -108,17 +112,22 @@ export class HaComboBox extends LitElement {
|
||||
return this._comboBox.selectedItem;
|
||||
}
|
||||
|
||||
public setInputValue(value: string) {
|
||||
this._comboBox.value = value;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
.itemValuePath=${this.itemValuePath}
|
||||
.itemIdPath=${this.itemIdPath}
|
||||
.itemLabelPath=${this.itemLabelPath}
|
||||
.value=${this.value || ""}
|
||||
.items=${this.items}
|
||||
.value=${this.value || ""}
|
||||
.filteredItems=${this.filteredItems}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
${comboBoxRenderer(this.renderer || this._defaultRowRenderer)}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@@ -129,6 +138,7 @@ export class HaComboBox extends LitElement {
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.validationMessage=${this.validationMessage}
|
||||
.errorMessage=${this.errorMessage}
|
||||
class="input"
|
||||
@@ -139,6 +149,8 @@ export class HaComboBox extends LitElement {
|
||||
.suffix=${html`<div style="width: 28px;"></div>`}
|
||||
.icon=${this.icon}
|
||||
.invalid=${this.invalid}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
>
|
||||
<slot name="icon" slot="leadingIcon"></slot>
|
||||
</ha-textfield>
|
||||
@@ -229,6 +241,9 @@ export class HaComboBox extends LitElement {
|
||||
.toggle-button {
|
||||
right: 12px;
|
||||
top: -10px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 12px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
:host([opened]) .toggle-button {
|
||||
color: var(--primary-color);
|
||||
@@ -237,6 +252,9 @@ export class HaComboBox extends LitElement {
|
||||
--mdc-icon-size: 20px;
|
||||
top: -7px;
|
||||
right: 36px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 36px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -35,17 +35,24 @@ export class HaDateInput extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
render() {
|
||||
return html`<ha-textfield
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
iconTrailing
|
||||
helperPersistent
|
||||
@click=${this._openDialog}
|
||||
.value=${this.value
|
||||
? formatDateNumeric(new Date(this.value), this.locale)
|
||||
: ""}
|
||||
.required=${this.required}
|
||||
>
|
||||
<ha-svg-icon slot="trailingIcon" .path=${mdiCalendar}></ha-svg-icon>
|
||||
</ha-textfield>`;
|
||||
|
@@ -140,6 +140,9 @@ export class HaDateRangePicker extends LitElement {
|
||||
return css`
|
||||
ha-svg-icon {
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.date-range-inputs {
|
||||
@@ -166,6 +169,9 @@ export class HaDateRangePicker extends LitElement {
|
||||
|
||||
ha-textfield:last-child {
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user