mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-14 03:39:26 +00:00
Compare commits
380 Commits
20240604.0
...
boolean_se
Author | SHA1 | Date | |
---|---|---|---|
![]() |
932120869b | ||
![]() |
061521a979 | ||
![]() |
d3f73baa36 | ||
![]() |
3037bf494c | ||
![]() |
b4dd953128 | ||
![]() |
430a28f350 | ||
![]() |
0eacf3fdac | ||
![]() |
7f9bf69a08 | ||
![]() |
32cca9e30c | ||
![]() |
d7d62307b8 | ||
![]() |
12bfa5dab2 | ||
![]() |
c0a728bc66 | ||
![]() |
19e8667349 | ||
![]() |
f3688b95d4 | ||
![]() |
01f692f05c | ||
![]() |
d652f6382d | ||
![]() |
c0f96d9473 | ||
![]() |
f1a2af24b3 | ||
![]() |
8501098bd1 | ||
![]() |
a235f76985 | ||
![]() |
968c0de141 | ||
![]() |
77d8aff1f4 | ||
![]() |
5e486d9cf0 | ||
![]() |
f730761b3f | ||
![]() |
46fc9c1a33 | ||
![]() |
8ed68bf295 | ||
![]() |
5622180d42 | ||
![]() |
ef1f9b371d | ||
![]() |
0b79684cf1 | ||
![]() |
bc68d8df11 | ||
![]() |
ebbade2fb7 | ||
![]() |
2b5f778f2e | ||
![]() |
91fc2383cb | ||
![]() |
1080a8c961 | ||
![]() |
6144049f8c | ||
![]() |
4ad3ad6e93 | ||
![]() |
16e84296da | ||
![]() |
3e1ea8d236 | ||
![]() |
8c9996fc81 | ||
![]() |
336b5fb547 | ||
![]() |
3f0f3affb6 | ||
![]() |
6754b8893b | ||
![]() |
6ec4323c76 | ||
![]() |
20408392d2 | ||
![]() |
b030a5d1f0 | ||
![]() |
e26e6d7c2d | ||
![]() |
e0c98e4524 | ||
![]() |
2832f501d1 | ||
![]() |
d2f2d682a9 | ||
![]() |
54b2121273 | ||
![]() |
c5ca731e05 | ||
![]() |
fd9c6c5449 | ||
![]() |
39f4355c1a | ||
![]() |
35fed0b0e2 | ||
![]() |
f0f0aefca1 | ||
![]() |
b60864086f | ||
![]() |
6629f372fc | ||
![]() |
ec9a95dde0 | ||
![]() |
3cd0c49c78 | ||
![]() |
e1ae95dd9f | ||
![]() |
9c6aef033d | ||
![]() |
b23aacef84 | ||
![]() |
7d218c89ae | ||
![]() |
ffa96d789f | ||
![]() |
5eae163796 | ||
![]() |
9e7f01a009 | ||
![]() |
eeffa3561c | ||
![]() |
f096eb2031 | ||
![]() |
3aad7431da | ||
![]() |
a4f167559c | ||
![]() |
44a0582e83 | ||
![]() |
94c28ea534 | ||
![]() |
5b7c20af33 | ||
![]() |
4719775926 | ||
![]() |
5c30c1647c | ||
![]() |
3ba572ee37 | ||
![]() |
0adee7d189 | ||
![]() |
51b6d7758d | ||
![]() |
0f21dfadf1 | ||
![]() |
54e8a34f21 | ||
![]() |
dfbf4abd5d | ||
![]() |
62d8434596 | ||
![]() |
d6df228e17 | ||
![]() |
00faa16349 | ||
![]() |
3c92fe4170 | ||
![]() |
02a9d67c7d | ||
![]() |
88718bca23 | ||
![]() |
33931b29a1 | ||
![]() |
a9310fdde0 | ||
![]() |
d634317438 | ||
![]() |
a41978f647 | ||
![]() |
7e799bf639 | ||
![]() |
73617711ff | ||
![]() |
edbfc22bf8 | ||
![]() |
5e75f6a6c2 | ||
![]() |
6dc80306e8 | ||
![]() |
acd1b04b0a | ||
![]() |
7e9d09e11c | ||
![]() |
038f7ec000 | ||
![]() |
08959cbabf | ||
![]() |
b611674ebd | ||
![]() |
2f696bd511 | ||
![]() |
9e3284a7db | ||
![]() |
0c3471e0b7 | ||
![]() |
1ca0b58aca | ||
![]() |
adceed689b | ||
![]() |
574fc99889 | ||
![]() |
3a83cb36a1 | ||
![]() |
a458ccf995 | ||
![]() |
560e2c9438 | ||
![]() |
78becb5440 | ||
![]() |
da2e530601 | ||
![]() |
a88a7c5236 | ||
![]() |
0a095c6f21 | ||
![]() |
e8dd835eeb | ||
![]() |
e05c66444a | ||
![]() |
d0e61ca31a | ||
![]() |
1f90a0c2e5 | ||
![]() |
dbd84901f8 | ||
![]() |
0aa25dbed9 | ||
![]() |
dd74a35d3f | ||
![]() |
b1d8ec0fe4 | ||
![]() |
cd4af674a3 | ||
![]() |
7a6491a901 | ||
![]() |
8d20303d54 | ||
![]() |
a5786b4761 | ||
![]() |
4ade39543d | ||
![]() |
a85dda3365 | ||
![]() |
e578904ff7 | ||
![]() |
09f0da1ead | ||
![]() |
2faa8fec17 | ||
![]() |
945c4a66b1 | ||
![]() |
5d794e7e88 | ||
![]() |
88a33bee14 | ||
![]() |
35ec9af23f | ||
![]() |
dd331173ad | ||
![]() |
885ccb84cb | ||
![]() |
0358fe5614 | ||
![]() |
89d842c2a8 | ||
![]() |
8911b55316 | ||
![]() |
6791e85625 | ||
![]() |
79618ce114 | ||
![]() |
87ba0e73dd | ||
![]() |
567a2ea019 | ||
![]() |
811c34b489 | ||
![]() |
dd22ae446a | ||
![]() |
d96ddf968c | ||
![]() |
bbb64870a1 | ||
![]() |
f05ddd3fcd | ||
![]() |
0612e25d9f | ||
![]() |
559ecf3eae | ||
![]() |
ddb31a8342 | ||
![]() |
1c978b7cce | ||
![]() |
9d9624c960 | ||
![]() |
179245e1aa | ||
![]() |
345000a0e9 | ||
![]() |
d94d5f96c3 | ||
![]() |
0fa3538db5 | ||
![]() |
1749725229 | ||
![]() |
83e94d32e3 | ||
![]() |
7fed4e6b37 | ||
![]() |
677cffd650 | ||
![]() |
1faa1480e4 | ||
![]() |
8cb63ac36d | ||
![]() |
6e29b77e94 | ||
![]() |
38e7b8c467 | ||
![]() |
d078807255 | ||
![]() |
82d84de426 | ||
![]() |
729a12af0c | ||
![]() |
ce43774b5f | ||
![]() |
e63d82d291 | ||
![]() |
d997cfcef0 | ||
![]() |
219f548261 | ||
![]() |
ee2b10912c | ||
![]() |
e3b0630797 | ||
![]() |
30d0293a4b | ||
![]() |
7468ab985a | ||
![]() |
ef3758da55 | ||
![]() |
2a970b8416 | ||
![]() |
f70126eb62 | ||
![]() |
f87296d978 | ||
![]() |
2890d5c8cf | ||
![]() |
15a7ace278 | ||
![]() |
71a2c40dd7 | ||
![]() |
a08bbcd1b4 | ||
![]() |
5ec257fa18 | ||
![]() |
9b01c0b2f0 | ||
![]() |
7dd860c539 | ||
![]() |
dbc2db2591 | ||
![]() |
29aa57229c | ||
![]() |
8d74174be1 | ||
![]() |
a60242f042 | ||
![]() |
db314522d7 | ||
![]() |
7b66ee06eb | ||
![]() |
3f34dacec9 | ||
![]() |
277650e1c1 | ||
![]() |
e59c04c685 | ||
![]() |
d9583582e6 | ||
![]() |
5ead5ed058 | ||
![]() |
f2993602f9 | ||
![]() |
11b490d145 | ||
![]() |
cd4937b539 | ||
![]() |
daa36788e0 | ||
![]() |
7edc4efc95 | ||
![]() |
144d278e4a | ||
![]() |
541453c245 | ||
![]() |
ca53af5c41 | ||
![]() |
bd54eb40a7 | ||
![]() |
8ff2396a53 | ||
![]() |
c85e29f2bb | ||
![]() |
e7a749ef7d | ||
![]() |
bef53aef57 | ||
![]() |
877d0db1bb | ||
![]() |
aa49d6ef6b | ||
![]() |
d646ce4995 | ||
![]() |
d6e6844f23 | ||
![]() |
66e26e1a27 | ||
![]() |
b7473b58fb | ||
![]() |
42b5fbec9b | ||
![]() |
f7072c247e | ||
![]() |
f995f19f06 | ||
![]() |
7f50504908 | ||
![]() |
dc93a0ce54 | ||
![]() |
3e4d06fca3 | ||
![]() |
050bef0564 | ||
![]() |
1abebdae21 | ||
![]() |
b411ae0286 | ||
![]() |
202bd148ef | ||
![]() |
15589927c8 | ||
![]() |
df7b5b08cf | ||
![]() |
8b9fa9bc39 | ||
![]() |
c07e1122e1 | ||
![]() |
1ceef7c3d3 | ||
![]() |
e332364ec0 | ||
![]() |
97c4cf9391 | ||
![]() |
522f66423b | ||
![]() |
57e48e2561 | ||
![]() |
37af77dabe | ||
![]() |
2b5fba4a30 | ||
![]() |
d833910796 | ||
![]() |
81c796beb4 | ||
![]() |
19ee150395 | ||
![]() |
82329833f5 | ||
![]() |
ab3b8593f4 | ||
![]() |
094203f0b4 | ||
![]() |
9a2051a679 | ||
![]() |
09accb3071 | ||
![]() |
7d432cd11a | ||
![]() |
7258e31348 | ||
![]() |
5707ca0016 | ||
![]() |
76abfea6ed | ||
![]() |
d01377da3c | ||
![]() |
e97be57e3b | ||
![]() |
c71a051b6d | ||
![]() |
f41fab6968 | ||
![]() |
bda61da666 | ||
![]() |
93445ced74 | ||
![]() |
b81314fc1f | ||
![]() |
9beb4c39ff | ||
![]() |
18a6f8d64d | ||
![]() |
beec720b9b | ||
![]() |
85865af0c3 | ||
![]() |
d33cf4f199 | ||
![]() |
4a1087c969 | ||
![]() |
cbc95a5e2d | ||
![]() |
dcd4c39978 | ||
![]() |
11d832c2ea | ||
![]() |
3b15d26500 | ||
![]() |
df65038341 | ||
![]() |
da2865d8bf | ||
![]() |
fd64d17d88 | ||
![]() |
5273293cd6 | ||
![]() |
49c42fc757 | ||
![]() |
7603fa3aa8 | ||
![]() |
7aa005e0ce | ||
![]() |
b2a55dd737 | ||
![]() |
ccad1afcf0 | ||
![]() |
231c923776 | ||
![]() |
b08b67179e | ||
![]() |
d9f1b06199 | ||
![]() |
4b7526c8a3 | ||
![]() |
6267ab5ed3 | ||
![]() |
ae94231800 | ||
![]() |
7d28f3f585 | ||
![]() |
adea384f40 | ||
![]() |
55b66250f4 | ||
![]() |
182111912c | ||
![]() |
8a0924bf1f | ||
![]() |
94dc9308ea | ||
![]() |
f42a9ac070 | ||
![]() |
1acada625f | ||
![]() |
128dbbcfef | ||
![]() |
57d8544dbf | ||
![]() |
76daa2bb7f | ||
![]() |
9cbb51549b | ||
![]() |
bd1ede4145 | ||
![]() |
321a085c0e | ||
![]() |
6a3041988a | ||
![]() |
23fcdf876c | ||
![]() |
00f325e961 | ||
![]() |
d00b3cfc61 | ||
![]() |
4cc9e74ea8 | ||
![]() |
a56b9a96ce | ||
![]() |
d4b5f4bc14 | ||
![]() |
cf1523ee73 | ||
![]() |
f5d571ca84 | ||
![]() |
362e92f313 | ||
![]() |
5ddf72b973 | ||
![]() |
6e78c28f51 | ||
![]() |
772f0bb669 | ||
![]() |
846c2a848f | ||
![]() |
8495757005 | ||
![]() |
9960d38b91 | ||
![]() |
d3222f8bb0 | ||
![]() |
2e5cce5409 | ||
![]() |
f78946447f | ||
![]() |
eb0579ddc5 | ||
![]() |
686424fc70 | ||
![]() |
039e9b40bd | ||
![]() |
8272bef890 | ||
![]() |
62528b2413 | ||
![]() |
fa24f529e0 | ||
![]() |
43a54f6cda | ||
![]() |
9c153bbd58 | ||
![]() |
27afe9ecb7 | ||
![]() |
72f989e2bd | ||
![]() |
a6ef46565f | ||
![]() |
a35ac09688 | ||
![]() |
27024135ea | ||
![]() |
8759ed740a | ||
![]() |
bb3e8ae33d | ||
![]() |
b5b60c9bf0 | ||
![]() |
3b6a2cf7d8 | ||
![]() |
7e10e14102 | ||
![]() |
a580abab4a | ||
![]() |
11523c08c4 | ||
![]() |
7a8988528b | ||
![]() |
2a6380f083 | ||
![]() |
f43319a3ae | ||
![]() |
e8eefaf1d3 | ||
![]() |
06d82a4925 | ||
![]() |
4991d52fcc | ||
![]() |
0b391eafcf | ||
![]() |
0bb34830f8 | ||
![]() |
29881c8bb4 | ||
![]() |
56254ddf03 | ||
![]() |
007ba70641 | ||
![]() |
3e1227b064 | ||
![]() |
067e179f26 | ||
![]() |
9a3f7df25e | ||
![]() |
c7b4e8f37c | ||
![]() |
bfa8b886ab | ||
![]() |
433c00b73a | ||
![]() |
a497f42f73 | ||
![]() |
165723cb5b | ||
![]() |
42b5fa696a | ||
![]() |
59062d96a8 | ||
![]() |
d36bbfe07d | ||
![]() |
4a8bb5034d | ||
![]() |
0d489213a4 | ||
![]() |
c54acc9369 | ||
![]() |
562bc084f0 | ||
![]() |
6fce2f35a5 | ||
![]() |
f4e24bed2e | ||
![]() |
09969c0e2d | ||
![]() |
4b0181774b | ||
![]() |
272db5e9e8 | ||
![]() |
9ae3a824d9 | ||
![]() |
9db55c9391 | ||
![]() |
59697127c0 | ||
![]() |
565600e945 | ||
![]() |
721eebf367 | ||
![]() |
f5ae842167 | ||
![]() |
3575734ed0 | ||
![]() |
4e8de1f64d | ||
![]() |
a8366c6416 | ||
![]() |
cd73b8ac29 | ||
![]() |
74eca6b1f5 | ||
![]() |
9ef0bd6e46 | ||
![]() |
53eb7f771f |
@@ -1,28 +1,25 @@
|
|||||||
[modern]
|
[modern]
|
||||||
# Support for dynamic import is the main litmus test for serving modern builds.
|
# Modern builds target recent browsers supporting the latest features to minimize transpilation, polyfills, etc.
|
||||||
# Although officially a ES2020 feature, browsers implemented it early, so this
|
# It is served to browsers meeting the following requirements:
|
||||||
# enables all of ES2017 and some features in ES2018.
|
# - released in the last year + current alpha/beta versions
|
||||||
supports es6-module-dynamic-import
|
# - Firefox extended support release (ESR)
|
||||||
|
# - with global utilization at or above 0.5%
|
||||||
# Exclude Safari 11-12 because of a bug in tagged template literals
|
# - must support dynamic import of ES modules
|
||||||
# https://bugs.webkit.org/show_bug.cgi?id=190756
|
# - exclude browsers no longer being maintained
|
||||||
# Note: Dropping version 11 also enables several more ES2018 features
|
# - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
||||||
not Safari < 13
|
unreleased versions
|
||||||
not iOS < 13
|
last 1 year
|
||||||
|
Firefox ESR
|
||||||
# Exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
>= 0.5% and supports es6-module-dynamic-import
|
||||||
# Babel ignores these automatically, but we need here for Webpack to output ESM with dynamic imports
|
not dead
|
||||||
not KaiOS > 0
|
not KaiOS > 0
|
||||||
not QQAndroid > 0
|
not QQAndroid > 0
|
||||||
not UCAndroid > 0
|
not UCAndroid > 0
|
||||||
|
|
||||||
# Exclude unsupported browsers
|
|
||||||
not dead
|
|
||||||
|
|
||||||
[legacy]
|
[legacy]
|
||||||
# Legacy builds are served when modern requirements are not met and support browsers:
|
# Legacy builds are served when modern requirements are not met and support browsers:
|
||||||
# - released in the last 7 years + current alpha/beta versionss
|
# - released in the last 7 years + current alpha/beta versionss
|
||||||
# - with global utilization above 0.05%
|
# - with global utilization at or above 0.05%
|
||||||
# The lattermost query ensures that support for popular old browsers is not dropped too early
|
# The lattermost query ensures that support for popular old browsers is not dropped too early
|
||||||
# (e.g. IE 11, Android 4.4, or Samsung 4).
|
# (e.g. IE 11, Android 4.4, or Samsung 4).
|
||||||
#
|
#
|
||||||
@@ -36,4 +33,10 @@ not dead
|
|||||||
# As of May 2023, only web sockets must be added to the query.
|
# As of May 2023, only web sockets must be added to the query.
|
||||||
unreleased versions
|
unreleased versions
|
||||||
last 7 years
|
last 7 years
|
||||||
> 0.05% and supports websockets
|
>= 0.05% and supports websockets
|
||||||
|
|
||||||
|
[legacy-sw]
|
||||||
|
# Same as legacy plus supports service workers
|
||||||
|
unreleased versions
|
||||||
|
last 7 years
|
||||||
|
>= 0.05% and supports websockets and supports serviceworkers
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
||||||
"postStartCommand": "script/bootstrap",
|
"postStartCommand": "script/bootstrap",
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
|
"DEV_CONTAINER": "1",
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
8
.github/workflows/cast_deployment.yaml
vendored
8
.github/workflows/cast_deployment.yaml
vendored
@@ -21,12 +21,12 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -57,12 +57,12 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
20
.github/workflows/ci.yaml
vendored
20
.github/workflows/ci.yaml
vendored
@@ -24,9 +24,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -58,9 +58,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -76,9 +76,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: frontend-bundle-stats
|
name: frontend-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
@@ -100,9 +100,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: supervisor-bundle-stats
|
name: supervisor-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
|
8
.github/workflows/demo_deployment.yaml
vendored
8
.github/workflows/demo_deployment.yaml
vendored
@@ -22,12 +22,12 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -58,12 +58,12 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
4
.github/workflows/design_deployment.yaml
vendored
4
.github/workflows/design_deployment.yaml
vendored
@@ -16,10 +16,10 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
4
.github/workflows/design_preview.yaml
vendored
4
.github/workflows/design_preview.yaml
vendored
@@ -21,10 +21,10 @@ jobs:
|
|||||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
8
.github/workflows/nightly.yaml
vendored
8
.github/workflows/nightly.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -57,14 +57,14 @@ jobs:
|
|||||||
run: tar -czvf translations.tar.gz translations
|
run: tar -czvf translations.tar.gz translations
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels
|
||||||
path: dist/home_assistant_frontend*.whl
|
path: dist/home_assistant_frontend*.whl
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Send bundle stats and build information to RelativeCI
|
- name: Send bundle stats and build information to RelativeCI
|
||||||
uses: relative-ci/agent-action@v2.1.11
|
uses: relative-ci/agent-action@v2.1.12
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
contents: write # Required to upload release assets
|
contents: write # Required to upload release assets
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Verify version
|
- name: Verify version
|
||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@v2.0.5
|
uses: softprops/action-gh-release@v2.0.8
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
@@ -74,9 +74,9 @@ jobs:
|
|||||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2024.01.0
|
uses: home-assistant/wheels@2024.07.1
|
||||||
with:
|
with:
|
||||||
abi: cp311
|
abi: cp312
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
arch: amd64
|
arch: amd64
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
@@ -1,4 +1 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn run lint-staged --relative --shell "/bin/bash"
|
yarn run lint-staged --relative --shell "/bin/bash"
|
||||||
|
55
.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch
Normal file
55
.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
diff --git a/build/inject-manifest.js b/build/inject-manifest.js
|
||||||
|
index 60e3d2bb51c11a19fbbedbad65e101082ec41c36..fed6026630f43f86e25446383982cf6fb694313b 100644
|
||||||
|
--- a/build/inject-manifest.js
|
||||||
|
+++ b/build/inject-manifest.js
|
||||||
|
@@ -104,7 +104,7 @@ async function injectManifest(config) {
|
||||||
|
replaceString: manifestString,
|
||||||
|
searchString: options.injectionPoint,
|
||||||
|
});
|
||||||
|
- filesToWrite[options.swDest] = source;
|
||||||
|
+ filesToWrite[options.swDest] = source.replace(url, encodeURI(upath_1.default.basename(destPath)));
|
||||||
|
filesToWrite[destPath] = map;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
diff --git a/build/lib/translate-url-to-sourcemap-paths.js b/build/lib/translate-url-to-sourcemap-paths.js
|
||||||
|
index 3220c5474eeac6e8a56ca9b2ac2bd9be48529e43..5f003879a904d4840529a42dd056d288fd213771 100644
|
||||||
|
--- a/build/lib/translate-url-to-sourcemap-paths.js
|
||||||
|
+++ b/build/lib/translate-url-to-sourcemap-paths.js
|
||||||
|
@@ -22,7 +22,7 @@ function translateURLToSourcemapPaths(url, swSrc, swDest) {
|
||||||
|
const possibleSrcPath = upath_1.default.resolve(upath_1.default.dirname(swSrc), url);
|
||||||
|
if (fs_extra_1.default.existsSync(possibleSrcPath)) {
|
||||||
|
srcPath = possibleSrcPath;
|
||||||
|
- destPath = upath_1.default.resolve(upath_1.default.dirname(swDest), url);
|
||||||
|
+ destPath = `${swDest}.map`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warning = `${errors_1.errors['cant-find-sourcemap']} ${possibleSrcPath}`;
|
||||||
|
diff --git a/src/inject-manifest.ts b/src/inject-manifest.ts
|
||||||
|
index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f5070cad3 100644
|
||||||
|
--- a/src/inject-manifest.ts
|
||||||
|
+++ b/src/inject-manifest.ts
|
||||||
|
@@ -129,7 +129,10 @@ export async function injectManifest(
|
||||||
|
searchString: options.injectionPoint!,
|
||||||
|
});
|
||||||
|
|
||||||
|
- filesToWrite[options.swDest] = source;
|
||||||
|
+ filesToWrite[options.swDest] = source.replace(
|
||||||
|
+ url!,
|
||||||
|
+ encodeURI(upath.basename(destPath)),
|
||||||
|
+ );
|
||||||
|
filesToWrite[destPath] = map;
|
||||||
|
} else {
|
||||||
|
// If there's no sourcemap associated with swSrc, a simple string
|
||||||
|
diff --git a/src/lib/translate-url-to-sourcemap-paths.ts b/src/lib/translate-url-to-sourcemap-paths.ts
|
||||||
|
index 072eac40d4ef5d095a01cb7f7e392a9e034853bd..f0bbe69e88ef3a415de18a7e9cb264daea273d71 100644
|
||||||
|
--- a/src/lib/translate-url-to-sourcemap-paths.ts
|
||||||
|
+++ b/src/lib/translate-url-to-sourcemap-paths.ts
|
||||||
|
@@ -28,7 +28,7 @@ export function translateURLToSourcemapPaths(
|
||||||
|
const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url);
|
||||||
|
if (fse.existsSync(possibleSrcPath)) {
|
||||||
|
srcPath = possibleSrcPath;
|
||||||
|
- destPath = upath.resolve(upath.dirname(swDest), url);
|
||||||
|
+ destPath = `${swDest}.map`;
|
||||||
|
} else {
|
||||||
|
warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`;
|
||||||
|
}
|
894
.yarn/releases/yarn-4.2.2.cjs
vendored
894
.yarn/releases/yarn-4.2.2.cjs
vendored
File diff suppressed because one or more lines are too long
925
.yarn/releases/yarn-4.4.0.cjs
vendored
Executable file
925
.yarn/releases/yarn-4.4.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.2.2.cjs
|
yarnPath: .yarn/releases/yarn-4.4.0.cjs
|
||||||
|
@@ -47,7 +47,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
|||||||
|
|
||||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||||
__DEV__: !isProdBuild,
|
__DEV__: !isProdBuild,
|
||||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
__BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
|
||||||
__VERSION__: JSON.stringify(env.version()),
|
__VERSION__: JSON.stringify(env.version()),
|
||||||
__DEMO__: false,
|
__DEMO__: false,
|
||||||
__SUPERVISOR__: false,
|
__SUPERVISOR__: false,
|
||||||
@@ -79,7 +79,12 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
|
|||||||
sourceMap: !isTestBuild,
|
sourceMap: !isTestBuild,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
module.exports.babelOptions = ({
|
||||||
|
latestBuild,
|
||||||
|
isProdBuild,
|
||||||
|
isTestBuild,
|
||||||
|
sw,
|
||||||
|
}) => ({
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
assumptions: {
|
assumptions: {
|
||||||
@@ -87,13 +92,13 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
setPublicClassFields: true,
|
setPublicClassFields: true,
|
||||||
setSpreadProperties: true,
|
setSpreadProperties: true,
|
||||||
},
|
},
|
||||||
browserslistEnv: latestBuild ? "modern" : "legacy",
|
browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`,
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
useBuiltIns: latestBuild ? false : "usage",
|
useBuiltIns: "usage",
|
||||||
corejs: latestBuild ? false : dependencies["core-js"],
|
corejs: dependencies["core-js"],
|
||||||
bugfixes: true,
|
bugfixes: true,
|
||||||
shippedProposals: true,
|
shippedProposals: true,
|
||||||
},
|
},
|
||||||
@@ -135,8 +140,14 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
{ version: dependencies["@babel/runtime"] },
|
{ version: dependencies["@babel/runtime"] },
|
||||||
],
|
],
|
||||||
// Support some proposals still in TC39 process
|
// Transpile decorators (still in TC39 process)
|
||||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
// Modern browsers support class fields and private methods, but transform is required with the older decorator version dictated by Lit
|
||||||
|
[
|
||||||
|
"@babel/plugin-proposal-decorators",
|
||||||
|
{ version: "2018-09", decoratorsBeforeExport: true },
|
||||||
|
],
|
||||||
|
"@babel/plugin-transform-class-properties",
|
||||||
|
"@babel/plugin-transform-private-methods",
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
exclude: [
|
exclude: [
|
||||||
// \\ for Windows, / for Mac OS and Linux
|
// \\ for Windows, / for Mac OS and Linux
|
||||||
@@ -157,7 +168,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
exclude: [
|
exclude: [
|
||||||
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
||||||
...[
|
...[
|
||||||
"@formatjs/intl-\\w+",
|
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
|
||||||
"@lit-labs/virtualizer/polyfills",
|
"@lit-labs/virtualizer/polyfills",
|
||||||
"@webcomponents/scoped-custom-element-registry",
|
"@webcomponents/scoped-custom-element-registry",
|
||||||
"element-internals-polyfill",
|
"element-internals-polyfill",
|
||||||
@@ -215,7 +226,13 @@ module.exports.config = {
|
|||||||
return {
|
return {
|
||||||
name: "frontend" + nameSuffix(latestBuild),
|
name: "frontend" + nameSuffix(latestBuild),
|
||||||
entry: {
|
entry: {
|
||||||
service_worker: "./src/entrypoints/service_worker.ts",
|
"service-worker":
|
||||||
|
!env.useRollup() && !latestBuild
|
||||||
|
? {
|
||||||
|
import: "./src/entrypoints/service-worker.ts",
|
||||||
|
layer: "sw",
|
||||||
|
}
|
||||||
|
: "./src/entrypoints/service-worker.ts",
|
||||||
app: "./src/entrypoints/app.ts",
|
app: "./src/entrypoints/app.ts",
|
||||||
authorize: "./src/entrypoints/authorize.ts",
|
authorize: "./src/entrypoints/authorize.ts",
|
||||||
onboarding: "./src/entrypoints/onboarding.ts",
|
onboarding: "./src/entrypoints/onboarding.ts",
|
||||||
|
@@ -32,4 +32,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
return version[1];
|
return version[1];
|
||||||
},
|
},
|
||||||
|
isDevContainer() {
|
||||||
|
return process.env.DEV_CONTAINER === "1";
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@@ -1,19 +1,54 @@
|
|||||||
// Tasks to compress
|
// Tasks to compress
|
||||||
|
|
||||||
|
import { constants } from "node:zlib";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
|
import brotli from "gulp-brotli";
|
||||||
import zopfli from "gulp-zopfli-green";
|
import zopfli from "gulp-zopfli-green";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
|
const filesGlob = "*.{js,json,css,svg,xml}";
|
||||||
|
const brotliOptions = {
|
||||||
|
skipLarger: true,
|
||||||
|
params: {
|
||||||
|
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
|
||||||
|
},
|
||||||
|
};
|
||||||
const zopfliOptions = { threshold: 150 };
|
const zopfliOptions = { threshold: 150 };
|
||||||
|
|
||||||
const compressDist = (rootDir) =>
|
const compressDistBrotli = (rootDir, modernDir) =>
|
||||||
gulp
|
gulp
|
||||||
.src([
|
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
|
||||||
`${rootDir}/**/*.{js,json,css,svg,xml}`,
|
base: rootDir,
|
||||||
`${rootDir}/{authorize,onboarding}.html`,
|
})
|
||||||
])
|
.pipe(brotli(brotliOptions))
|
||||||
|
.pipe(gulp.dest(rootDir));
|
||||||
|
|
||||||
|
const compressDistZopfli = (rootDir, modernDir) =>
|
||||||
|
gulp
|
||||||
|
.src(
|
||||||
|
[
|
||||||
|
`${rootDir}/**/${filesGlob}`,
|
||||||
|
`!${modernDir}/**/${filesGlob}`,
|
||||||
|
`!${rootDir}/{sw-modern,service_worker}.js`,
|
||||||
|
`${rootDir}/{authorize,onboarding}.html`,
|
||||||
|
],
|
||||||
|
{ base: rootDir }
|
||||||
|
)
|
||||||
.pipe(zopfli(zopfliOptions))
|
.pipe(zopfli(zopfliOptions))
|
||||||
.pipe(gulp.dest(rootDir));
|
.pipe(gulp.dest(rootDir));
|
||||||
|
|
||||||
gulp.task("compress-app", () => compressDist(paths.app_output_root));
|
const compressAppBrotli = () =>
|
||||||
gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root));
|
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
||||||
|
const compressHassioBrotli = () =>
|
||||||
|
compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest);
|
||||||
|
|
||||||
|
const compressAppZopfli = () =>
|
||||||
|
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
||||||
|
const compressHassioZopfli = () =>
|
||||||
|
compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest);
|
||||||
|
|
||||||
|
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
||||||
|
gulp.task(
|
||||||
|
"compress-hassio",
|
||||||
|
gulp.parallel(compressHassioBrotli, compressHassioZopfli)
|
||||||
|
);
|
||||||
|
@@ -1,28 +1,76 @@
|
|||||||
// Tasks to generate entry HTML
|
// Tasks to generate entry HTML
|
||||||
|
|
||||||
|
import {
|
||||||
|
applyVersionsToRegexes,
|
||||||
|
compileRegex,
|
||||||
|
getPreUserAgentRegexes,
|
||||||
|
} from "browserslist-useragent-regexp";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import { minify } from "html-minifier-terser";
|
import { minify } from "html-minifier-terser";
|
||||||
import template from "lodash.template";
|
import template from "lodash.template";
|
||||||
import path from "path";
|
import { dirname, extname, resolve } from "node:path";
|
||||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
||||||
import env from "../env.cjs";
|
import env from "../env.cjs";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
|
// macOS companion app has no way to obtain the Safari version used by WKWebView,
|
||||||
|
// and it is not in the default user agent string. So we add an additional regex
|
||||||
|
// to serve modern based on a minimum macOS version. We take the minimum Safari
|
||||||
|
// major version from browserslist and manually map that to a supported macOS
|
||||||
|
// version. Note this assumes the user has kept Safari updated.
|
||||||
|
const HA_MACOS_REGEX =
|
||||||
|
/Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/;
|
||||||
|
const SAFARI_TO_MACOS = {
|
||||||
|
15: [10, 15, 0],
|
||||||
|
16: [11, 0, 0],
|
||||||
|
17: [12, 0, 0],
|
||||||
|
18: [13, 0, 0],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCommonTemplateVars = () => {
|
||||||
|
const browserRegexes = getPreUserAgentRegexes({
|
||||||
|
env: "modern",
|
||||||
|
allowHigherVersions: true,
|
||||||
|
mobileToDesktop: true,
|
||||||
|
throwOnMissing: true,
|
||||||
|
});
|
||||||
|
const minSafariVersion = browserRegexes.find(
|
||||||
|
(regex) => regex.family === "safari"
|
||||||
|
)?.matchedVersions[0][0];
|
||||||
|
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
|
||||||
|
if (!minMacOSVersion) {
|
||||||
|
throw Error(
|
||||||
|
`Could not find minimum MacOS version for Safari ${minSafariVersion}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const haMacOSRegex = applyVersionsToRegexes(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
family: "ha_macos",
|
||||||
|
regex: HA_MACOS_REGEX,
|
||||||
|
matchedVersions: [minMacOSVersion],
|
||||||
|
requestVersions: [minMacOSVersion],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ ignorePatch: true, allowHigherVersions: true }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
useRollup: env.useRollup(),
|
||||||
|
useWDS: env.useWDS(),
|
||||||
|
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const renderTemplate = (templateFile, data = {}) => {
|
const renderTemplate = (templateFile, data = {}) => {
|
||||||
const compiled = template(
|
const compiled = template(
|
||||||
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
||||||
);
|
);
|
||||||
return compiled({
|
return compiled({
|
||||||
...data,
|
...data,
|
||||||
useRollup: env.useRollup(),
|
|
||||||
useWDS: env.useWDS(),
|
|
||||||
// Resolve any child/nested templates relative to the parent and pass the same data
|
// Resolve any child/nested templates relative to the parent and pass the same data
|
||||||
renderTemplate: (childTemplate) =>
|
renderTemplate: (childTemplate) =>
|
||||||
renderTemplate(
|
renderTemplate(resolve(dirname(templateFile), childTemplate), data),
|
||||||
path.resolve(path.dirname(templateFile), childTemplate),
|
|
||||||
data
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,10 +104,12 @@ const genPagesDevTask =
|
|||||||
publicRoot = ""
|
publicRoot = ""
|
||||||
) =>
|
) =>
|
||||||
async () => {
|
async () => {
|
||||||
|
const commonVars = getCommonTemplateVars();
|
||||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||||
const content = renderTemplate(
|
const content = renderTemplate(
|
||||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
{
|
{
|
||||||
|
...commonVars,
|
||||||
latestEntryJS: entries.map((entry) =>
|
latestEntryJS: entries.map((entry) =>
|
||||||
useWDS
|
useWDS
|
||||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||||
@@ -74,7 +124,7 @@ const genPagesDevTask =
|
|||||||
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
fs.outputFileSync(path.resolve(outputRoot, page), content);
|
fs.outputFileSync(resolve(outputRoot, page), content);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,16 +141,18 @@ const genPagesProdTask =
|
|||||||
) =>
|
) =>
|
||||||
async () => {
|
async () => {
|
||||||
const latestManifest = fs.readJsonSync(
|
const latestManifest = fs.readJsonSync(
|
||||||
path.resolve(outputLatest, "manifest.json")
|
resolve(outputLatest, "manifest.json")
|
||||||
);
|
);
|
||||||
const es5Manifest = outputES5
|
const es5Manifest = outputES5
|
||||||
? fs.readJsonSync(path.resolve(outputES5, "manifest.json"))
|
? fs.readJsonSync(resolve(outputES5, "manifest.json"))
|
||||||
: {};
|
: {};
|
||||||
|
const commonVars = getCommonTemplateVars();
|
||||||
const minifiedHTML = [];
|
const minifiedHTML = [];
|
||||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||||
const content = renderTemplate(
|
const content = renderTemplate(
|
||||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
{
|
{
|
||||||
|
...commonVars,
|
||||||
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
||||||
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
||||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||||
@@ -108,8 +160,8 @@ const genPagesProdTask =
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
minifiedHTML.push(
|
minifiedHTML.push(
|
||||||
minifyHtml(content, path.extname(page)).then((minified) =>
|
minifyHtml(content, extname(page)).then((minified) =>
|
||||||
fs.outputFileSync(path.resolve(outputRoot, page), minified)
|
fs.outputFileSync(resolve(outputRoot, page), minified)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,19 @@
|
|||||||
// Generate service worker.
|
// Generate service workers
|
||||||
// Based on manifest, create a file with the content as service_worker.js
|
|
||||||
|
|
||||||
import fs from "fs-extra";
|
import { deleteAsync } from "del";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import path from "path";
|
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
||||||
import sourceMapUrl from "source-map-url";
|
import { basename, join, relative } from "node:path";
|
||||||
import workboxBuild from "workbox-build";
|
import { injectManifest } from "workbox-build";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
const SW_MAP = {
|
||||||
|
[paths.app_output_latest]: "modern",
|
||||||
|
[paths.app_output_es5]: "legacy",
|
||||||
|
};
|
||||||
|
|
||||||
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
|
const SW_DEV =
|
||||||
|
`
|
||||||
gulp.task("gen-service-worker-app-dev", (done) => {
|
|
||||||
writeSW(
|
|
||||||
`
|
|
||||||
console.debug('Service worker disabled in development');
|
console.debug('Service worker disabled in development');
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
@@ -22,72 +21,67 @@ self.addEventListener('install', (event) => {
|
|||||||
// removing any prod service worker the dev might have running
|
// removing any prod service worker the dev might have running
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
`
|
`.trim() + "\n";
|
||||||
|
|
||||||
|
gulp.task("gen-service-worker-app-dev", async () => {
|
||||||
|
await mkdir(paths.app_output_root, { recursive: true });
|
||||||
|
await Promise.all(
|
||||||
|
Object.values(SW_MAP).map((build) =>
|
||||||
|
writeFile(join(paths.app_output_root, `sw-${build}.js`), SW_DEV, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("gen-service-worker-app-prod", async () => {
|
gulp.task("gen-service-worker-app-prod", () =>
|
||||||
// Read bundled source file
|
Promise.all(
|
||||||
const bundleManifestLatest = fs.readJsonSync(
|
Object.entries(SW_MAP).map(async ([outPath, build]) => {
|
||||||
path.resolve(paths.app_output_latest, "manifest.json")
|
const manifest = JSON.parse(
|
||||||
);
|
await readFile(join(outPath, "manifest.json"), "utf-8")
|
||||||
let serviceWorkerContent = fs.readFileSync(
|
);
|
||||||
paths.app_output_root + bundleManifestLatest["service_worker.js"],
|
const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
|
||||||
"utf-8"
|
const swDest = join(paths.app_output_root, `sw-${build}.js`);
|
||||||
);
|
const buildDir = relative(paths.app_output_root, outPath);
|
||||||
|
const { warnings } = await injectManifest({
|
||||||
// Delete old file from frontend_latest so manifest won't pick it up
|
swSrc,
|
||||||
fs.removeSync(
|
swDest,
|
||||||
paths.app_output_root + bundleManifestLatest["service_worker.js"]
|
injectionPoint: "__WB_MANIFEST__",
|
||||||
);
|
// Files that mach this pattern will be considered unique and skip revision check
|
||||||
fs.removeSync(
|
// ignore JS files + translation files
|
||||||
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
|
dontCacheBustURLsMatching: new RegExp(
|
||||||
);
|
`(?:${buildDir}/.+|static/translations/.+)`
|
||||||
|
),
|
||||||
// Remove ES5
|
globDirectory: paths.app_output_root,
|
||||||
const bundleManifestES5 = fs.readJsonSync(
|
globPatterns: [
|
||||||
path.resolve(paths.app_output_es5, "manifest.json")
|
`${buildDir}/*.js`,
|
||||||
);
|
// Cache all English translations because we catch them as fallback
|
||||||
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
|
// Using pattern to match hash instead of * to avoid caching en-GB
|
||||||
fs.removeSync(
|
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
||||||
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
|
"static/translations/**/en-+([a-fv0-9]).json",
|
||||||
);
|
// Icon shown on splash screen
|
||||||
|
"static/icons/favicon-192x192.png",
|
||||||
const workboxManifest = await workboxBuild.getManifest({
|
"static/icons/favicon.ico",
|
||||||
// Files that mach this pattern will be considered unique and skip revision check
|
// Common fonts
|
||||||
// ignore JS files + translation files
|
"static/fonts/roboto/Roboto-Light.woff2",
|
||||||
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
|
"static/fonts/roboto/Roboto-Medium.woff2",
|
||||||
|
"static/fonts/roboto/Roboto-Regular.woff2",
|
||||||
globDirectory: paths.app_output_root,
|
"static/fonts/roboto/Roboto-Bold.woff2",
|
||||||
globPatterns: [
|
],
|
||||||
"frontend_latest/*.js",
|
globIgnores: [`${buildDir}/service-worker*`],
|
||||||
// Cache all English translations because we catch them as fallback
|
});
|
||||||
// Using pattern to match hash instead of * to avoid caching en-GB
|
if (warnings.length > 0) {
|
||||||
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
console.warn(
|
||||||
"static/translations/**/en-+([a-fv0-9]).json",
|
`Problems while injecting ${build} service worker:\n`,
|
||||||
// Icon shown on splash screen
|
warnings.join("\n")
|
||||||
"static/icons/favicon-192x192.png",
|
);
|
||||||
"static/icons/favicon.ico",
|
}
|
||||||
// Common fonts
|
await deleteAsync(`${swSrc}?(.map)`);
|
||||||
"static/fonts/roboto/Roboto-Light.woff2",
|
// Needed to install new SW from a cached HTML
|
||||||
"static/fonts/roboto/Roboto-Medium.woff2",
|
if (build === "modern") {
|
||||||
"static/fonts/roboto/Roboto-Regular.woff2",
|
const swOld = join(paths.app_output_root, "service_worker.js");
|
||||||
"static/fonts/roboto/Roboto-Bold.woff2",
|
await symlink(basename(swDest), swOld);
|
||||||
],
|
}
|
||||||
});
|
})
|
||||||
|
)
|
||||||
for (const warning of workboxManifest.warnings) {
|
);
|
||||||
console.warn(warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove source map and add WB manifest
|
|
||||||
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
|
|
||||||
serviceWorkerContent = serviceWorkerContent.replace(
|
|
||||||
"WB_MANIFEST",
|
|
||||||
JSON.stringify(workboxManifest.manifestEntries)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Write new file to root
|
|
||||||
fs.writeFileSync(swDest, serviceWorkerContent);
|
|
||||||
});
|
|
||||||
|
@@ -244,11 +244,11 @@ const createTranslations = async () => {
|
|||||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||||
// Will be OK for now as long as we don't have anything more complicated
|
// Will be OK for now as long as we don't have anything more complicated
|
||||||
// than a base translation + region.
|
// than a base translation + region.
|
||||||
gulp
|
const masterStream = gulp
|
||||||
.src(`${workDir}/en.json`)
|
.src(`${workDir}/en.json`)
|
||||||
.pipe(new PassThrough({ objectMode: true }))
|
.pipe(new PassThrough({ objectMode: true }));
|
||||||
.pipe(hashStream, { end: false });
|
masterStream.pipe(hashStream, { end: false });
|
||||||
const mergesFinished = [];
|
const mergesFinished = [finished(masterStream)];
|
||||||
for (const translationFile of translationFiles) {
|
for (const translationFile of translationFiles) {
|
||||||
const locale = basename(translationFile, ".json");
|
const locale = basename(translationFile, ".json");
|
||||||
const subtags = locale.split("-");
|
const subtags = locale.split("-");
|
||||||
|
@@ -40,8 +40,12 @@ const runDevServer = async ({
|
|||||||
compiler,
|
compiler,
|
||||||
contentBase,
|
contentBase,
|
||||||
port,
|
port,
|
||||||
listenHost = "localhost",
|
listenHost = undefined,
|
||||||
}) => {
|
}) => {
|
||||||
|
if (listenHost === undefined) {
|
||||||
|
// For dev container, we need to listen on all hosts
|
||||||
|
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
|
||||||
|
}
|
||||||
const server = new WebpackDevServer(
|
const server = new WebpackDevServer(
|
||||||
{
|
{
|
||||||
hot: false,
|
hot: false,
|
||||||
|
@@ -63,17 +63,25 @@ const createWebpackConfig = ({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.m?js$|\.ts$/,
|
test: /\.m?js$|\.ts$/,
|
||||||
use: {
|
use: (info) => ({
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: {
|
options: {
|
||||||
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
|
...bundle.babelOptions({
|
||||||
|
latestBuild,
|
||||||
|
isProdBuild,
|
||||||
|
isTestBuild,
|
||||||
|
sw: info.issuerLayer === "sw",
|
||||||
|
}),
|
||||||
cacheDirectory: !isProdBuild,
|
cacheDirectory: !isProdBuild,
|
||||||
cacheCompression: false,
|
cacheCompression: false,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
resolve: {
|
resolve: {
|
||||||
fullySpecified: false,
|
fullySpecified: false,
|
||||||
},
|
},
|
||||||
|
parser: {
|
||||||
|
worker: ["*context.audioWorklet.addModule()", "..."],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
@@ -92,11 +100,15 @@ const createWebpackConfig = ({
|
|||||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
// Disable splitting for web workers with ESM output
|
// Disable splitting for web workers and worklets because imports of
|
||||||
// Imports of external chunks are broken
|
// external chunks are broken for:
|
||||||
chunks: latestBuild
|
// - ESM output: https://github.com/webpack/webpack/issues/17014
|
||||||
? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name)
|
// - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
|
||||||
: undefined,
|
chunks: (chunk) =>
|
||||||
|
!chunk.canBeInitial() &&
|
||||||
|
!new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test(
|
||||||
|
chunk.name
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -228,6 +240,7 @@ const createWebpackConfig = ({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
experiments: {
|
experiments: {
|
||||||
|
layers: true,
|
||||||
outputModule: true,
|
outputModule: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
5
cast/public/sw-legacy.js
Normal file
5
cast/public/sw-legacy.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
event.respondWith(fetch(event.request));
|
||||||
|
});
|
@@ -36,13 +36,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
<script>
|
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||||
<% for (const entry of latestEntryJS) { %>
|
|
||||||
import("<%= entry %>");
|
|
||||||
<% } %>
|
|
||||||
window.latestJS = true;
|
|
||||||
</script>
|
|
||||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
|
||||||
<hc-layout subtitle="FAQ">
|
<hc-layout subtitle="FAQ">
|
||||||
<style>
|
<style>
|
||||||
a {
|
a {
|
||||||
@@ -232,17 +226,5 @@ http:
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</hc-layout>
|
</hc-layout>
|
||||||
|
|
||||||
<script>
|
|
||||||
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
|
|
||||||
(function (d, t) {
|
|
||||||
var g = d.createElement(t),
|
|
||||||
s = d.getElementsByTagName(t)[0];
|
|
||||||
g.src =
|
|
||||||
("https:" == location.protocol ? "//ssl" : "//www") +
|
|
||||||
".google-analytics.com/ga.js";
|
|
||||||
s.parentNode.insertBefore(g, s);
|
|
||||||
})(document, "script");
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -13,15 +13,9 @@
|
|||||||
<%= renderTemplate("_social_meta.html.template") %>
|
<%= renderTemplate("_social_meta.html.template") %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
|
||||||
<hc-connect></hc-connect>
|
<hc-connect></hc-connect>
|
||||||
<script>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
<% for (const entry of latestEntryJS) { %>
|
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||||
import("<%= entry %>");
|
|
||||||
<% } %>
|
|
||||||
window.latestJS = true;
|
|
||||||
</script>
|
|
||||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
|
||||||
<script>
|
<script>
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
@@ -14,22 +14,10 @@
|
|||||||
--background-color: #41bdf5;
|
--background-color: #41bdf5;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
|
||||||
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
|
||||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
|
||||||
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
|
||||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
|
||||||
<cast-media-player></cast-media-player>
|
<cast-media-player></cast-media-player>
|
||||||
<script>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
<% for (const entry of latestEntryJS) { %>
|
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||||
import("<%= entry %>");
|
|
||||||
<% } %>
|
|
||||||
window.latestJS = true;
|
|
||||||
</script>
|
|
||||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -11,10 +11,4 @@
|
|||||||
font-size: initial;
|
font-size: initial;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
|
||||||
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
|
||||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
|
||||||
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
|
||||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "../../../src/resources/safari-14-attachshadow-patch";
|
|
||||||
import "./layout/hc-connect";
|
import "./layout/hc-connect";
|
||||||
|
|
||||||
import("../../../src/resources/ha-style");
|
import("../../../src/resources/ha-style");
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import { ActionDetail } from "@material/mwc-list/mwc-list";
|
||||||
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
|
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -28,6 +27,7 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
|
|||||||
import "../../../../src/layouts/hass-loading-screen";
|
import "../../../../src/layouts/hass-loading-screen";
|
||||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
||||||
import "./hc-layout";
|
import "./hc-layout";
|
||||||
|
import "../../../../src/components/ha-list-item";
|
||||||
|
|
||||||
@customElement("hc-cast")
|
@customElement("hc-cast")
|
||||||
class HcCast extends LitElement {
|
class HcCast extends LitElement {
|
||||||
@@ -83,37 +83,37 @@ class HcCast extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div class="section-header">PICK A VIEW</div>
|
<div class="section-header">PICK A VIEW</div>
|
||||||
<paper-listbox
|
<mwc-list @action=${this._handlePickView} activatable>
|
||||||
attr-for-selected="data-path"
|
|
||||||
.selected=${this.castManager.status.lovelacePath || ""}
|
|
||||||
>
|
|
||||||
${(
|
${(
|
||||||
this.lovelaceViews ?? [
|
this.lovelaceViews ?? [
|
||||||
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
||||||
]
|
]
|
||||||
).map(
|
).map(
|
||||||
(view, idx) => html`
|
(view, idx) =>
|
||||||
<paper-icon-item
|
html`<ha-list-item
|
||||||
@click=${this._handlePickView}
|
graphic="avatar"
|
||||||
data-path=${view.path || idx}
|
.activated=${this.castManager.status?.lovelacePath ===
|
||||||
|
(view.path ?? idx)}
|
||||||
|
.selected=${this.castManager.status?.lovelacePath ===
|
||||||
|
(view.path ?? idx)}
|
||||||
>
|
>
|
||||||
|
${view.title || view.path || "Unnamed view"}
|
||||||
${view.icon
|
${view.icon
|
||||||
? html`
|
? html`
|
||||||
<ha-icon
|
<ha-icon
|
||||||
.icon=${view.icon}
|
.icon=${view.icon}
|
||||||
slot="item-icon"
|
slot="graphic"
|
||||||
></ha-icon>
|
></ha-icon>
|
||||||
`
|
`
|
||||||
: html`<ha-svg-icon
|
: html`<ha-svg-icon
|
||||||
slot="item-icon"
|
slot="item-icon"
|
||||||
.path=${mdiViewDashboard}
|
.path=${mdiViewDashboard}
|
||||||
></ha-svg-icon>`}
|
></ha-svg-icon>`}</ha-list-item
|
||||||
${view.title || view.path || "Unnamed view"}
|
> `
|
||||||
</paper-icon-item>
|
)}</mwc-list
|
||||||
`
|
>
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
`}
|
`}
|
||||||
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
${this.castManager.status
|
${this.castManager.status
|
||||||
? html`
|
? html`
|
||||||
@@ -185,8 +185,8 @@ class HcCast extends LitElement {
|
|||||||
this.castManager.requestSession();
|
this.castManager.requestSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handlePickView(ev: Event) {
|
private async _handlePickView(ev: CustomEvent<ActionDetail>) {
|
||||||
const path = (ev.currentTarget as any).getAttribute("data-path");
|
const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index;
|
||||||
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
||||||
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
|
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
|
||||||
}
|
}
|
||||||
@@ -249,26 +249,14 @@ class HcCast extends LitElement {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-listbox {
|
ha-list-item ha-icon,
|
||||||
padding-top: 0;
|
ha-list-item ha-svg-icon {
|
||||||
}
|
|
||||||
|
|
||||||
paper-listbox ha-icon,
|
|
||||||
paper-listbox ha-svg-icon {
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-icon-item {
|
:host([hide-icons]) ha-icon {
|
||||||
cursor: pointer;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-item[disabled] {
|
|
||||||
cursor: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([hide-icons]) paper-icon-item {
|
|
||||||
--paper-item-icon-width: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
|
BIN
demo/public/assets/sections/images/media_player_family_room.jpg
Normal file
BIN
demo/public/assets/sections/images/media_player_family_room.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
5
demo/public/sw-legacy.js
Normal file
5
demo/public/sw-legacy.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
event.respondWith(fetch(event.request));
|
||||||
|
});
|
@@ -1,7 +1,7 @@
|
|||||||
import { convertEntities } from "../../../../src/fake_data/entity";
|
import { convertEntities } from "../../../../src/fake_data/entity";
|
||||||
import { DemoConfig } from "../types";
|
import { DemoConfig } from "../types";
|
||||||
|
|
||||||
export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
"cover.living_room_garden_shutter": {
|
"cover.living_room_garden_shutter": {
|
||||||
entity_id: "cover.living_room_garden_shutter",
|
entity_id: "cover.living_room_garden_shutter",
|
||||||
@@ -113,11 +113,30 @@ export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
|||||||
},
|
},
|
||||||
"media_player.living_room_nest_mini": {
|
"media_player.living_room_nest_mini": {
|
||||||
entity_id: "media_player.living_room_nest_mini",
|
entity_id: "media_player.living_room_nest_mini",
|
||||||
state: "off",
|
state: "on",
|
||||||
attributes: {
|
attributes: {
|
||||||
device_class: "speaker",
|
device_class: "speaker",
|
||||||
friendly_name: "Living room Nest Mini",
|
volume_level: 0.18,
|
||||||
supported_features: 152461,
|
is_volume_muted: false,
|
||||||
|
media_content_type: "music",
|
||||||
|
media_duration: 300,
|
||||||
|
media_position: 0,
|
||||||
|
media_position_updated_at: new Date(
|
||||||
|
// 23 seconds in
|
||||||
|
new Date().getTime() - 23000
|
||||||
|
).toISOString(),
|
||||||
|
media_title: "I Wasn't Born To Follow",
|
||||||
|
media_artist: "The Byrds",
|
||||||
|
media_album_name: "The Notorious Byrd Brothers",
|
||||||
|
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
|
||||||
|
shuffle: false,
|
||||||
|
night_sound: false,
|
||||||
|
speech_enhance: false,
|
||||||
|
friendly_name: localize(
|
||||||
|
"ui.panel.page-demo.config.sections.entities.media_player.living_room_nest_mini"
|
||||||
|
),
|
||||||
|
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
|
||||||
|
supported_features: 64063,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"cover.kitchen_shutter": {
|
"cover.kitchen_shutter": {
|
||||||
@@ -168,8 +187,27 @@ export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
|||||||
state: "on",
|
state: "on",
|
||||||
attributes: {
|
attributes: {
|
||||||
device_class: "speaker",
|
device_class: "speaker",
|
||||||
friendly_name: "Kitchen Nest Audio",
|
volume_level: 0.18,
|
||||||
supported_features: 152461,
|
is_volume_muted: false,
|
||||||
|
media_content_type: "music",
|
||||||
|
media_duration: 300,
|
||||||
|
media_position: 0,
|
||||||
|
media_position_updated_at: new Date(
|
||||||
|
// 23 seconds in
|
||||||
|
new Date().getTime() - 23000
|
||||||
|
).toISOString(),
|
||||||
|
media_title: "I Wasn't Born To Follow",
|
||||||
|
media_artist: "The Byrds",
|
||||||
|
media_album_name: "The Notorious Byrd Brothers",
|
||||||
|
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
|
||||||
|
shuffle: false,
|
||||||
|
night_sound: false,
|
||||||
|
speech_enhance: false,
|
||||||
|
friendly_name: localize(
|
||||||
|
"ui.panel.page-demo.config.sections.entities.media_player.kitchen_nest_audio"
|
||||||
|
),
|
||||||
|
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
|
||||||
|
supported_features: 64063,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"binary_sensor.tesla_wall_connector_vehicle_connected": {
|
"binary_sensor.tesla_wall_connector_vehicle_connected": {
|
||||||
@@ -333,8 +371,28 @@ export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
|||||||
entity_id: "media_player.study_nest_hub",
|
entity_id: "media_player.study_nest_hub",
|
||||||
state: "off",
|
state: "off",
|
||||||
attributes: {
|
attributes: {
|
||||||
friendly_name: "Study Nest Hub",
|
device_class: "speaker",
|
||||||
supported_features: 152461,
|
volume_level: 0.18,
|
||||||
|
is_volume_muted: false,
|
||||||
|
media_content_type: "music",
|
||||||
|
media_duration: 300,
|
||||||
|
media_position: 0,
|
||||||
|
media_position_updated_at: new Date(
|
||||||
|
// 23 seconds in
|
||||||
|
new Date().getTime() - 23000
|
||||||
|
).toISOString(),
|
||||||
|
media_title: "I Wasn't Born To Follow",
|
||||||
|
media_artist: "The Byrds",
|
||||||
|
media_album_name: "The Notorious Byrd Brothers",
|
||||||
|
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
|
||||||
|
shuffle: false,
|
||||||
|
night_sound: false,
|
||||||
|
speech_enhance: false,
|
||||||
|
friendly_name: localize(
|
||||||
|
"ui.panel.page-demo.config.sections.entities.media_player.study_nest_hub"
|
||||||
|
),
|
||||||
|
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
|
||||||
|
supported_features: 64063,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"sensor.standing_desk_height": {
|
"sensor.standing_desk_height": {
|
||||||
|
@@ -1,40 +1,25 @@
|
|||||||
|
import { isFrontpageEmbed } from "../../util/is_frontpage";
|
||||||
import { DemoConfig } from "../types";
|
import { DemoConfig } from "../types";
|
||||||
|
|
||||||
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||||
title: "Home Assistant Demo",
|
title: "Home Assistant Demo",
|
||||||
views: [
|
views: [
|
||||||
{
|
{
|
||||||
type: "sections",
|
type: "sections",
|
||||||
title: "Demo",
|
title: isFrontpageEmbed ? "Home Assistant" : "Demo",
|
||||||
path: "home",
|
path: "home",
|
||||||
icon: "mdi:home-assistant",
|
icon: "mdi:home-assistant",
|
||||||
sections: [
|
sections: [
|
||||||
{
|
...(isFrontpageEmbed
|
||||||
title: "Welcome 👋",
|
? []
|
||||||
cards: [{ type: "custom:ha-demo-card" }],
|
: [
|
||||||
},
|
{
|
||||||
|
title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`,
|
||||||
|
cards: [{ type: "custom:ha-demo-card" }],
|
||||||
|
},
|
||||||
|
]),
|
||||||
{
|
{
|
||||||
cards: [
|
cards: [
|
||||||
{
|
|
||||||
type: "tile",
|
|
||||||
entity: "cover.living_room_garden_shutter",
|
|
||||||
name: "Garden",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "tile",
|
|
||||||
entity: "cover.living_room_graveyard_shutter",
|
|
||||||
name: "Rear",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "tile",
|
|
||||||
entity: "cover.living_room_left_shutter",
|
|
||||||
name: "Left",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "tile",
|
|
||||||
entity: "cover.living_room_right_shutter",
|
|
||||||
name: "Right",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "light.floor_lamp",
|
entity: "light.floor_lamp",
|
||||||
@@ -60,13 +45,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
detail: 1,
|
detail: 1,
|
||||||
name: "Temperature",
|
name: "Temperature",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "tile",
|
||||||
|
entity: "cover.living_room_garden_shutter",
|
||||||
|
name: "Blinds",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.living_room_nest_mini",
|
entity: "media_player.living_room_nest_mini",
|
||||||
name: "Nest Mini",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "🛋️ Living room ",
|
title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -99,10 +88,9 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.kitchen_nest_audio",
|
entity: "media_player.kitchen_nest_audio",
|
||||||
name: "Nest Audio",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "👩🍳 Kitchen",
|
title: `👩🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -144,7 +132,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
color: "dark-grey",
|
color: "dark-grey",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "⚡️ Energy",
|
title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -181,7 +169,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
state_content: ["preset_mode", "current_temperature"],
|
state_content: ["preset_mode", "current_temperature"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "🌤️ Climate",
|
title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -199,7 +187,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.study_nest_hub",
|
entity: "media_player.study_nest_hub",
|
||||||
name: "Nest Hub",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
@@ -209,7 +196,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
icon: "mdi:desk",
|
icon: "mdi:desk",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "🧑💻 Study",
|
title: `🧑💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -243,7 +230,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
name: "Illuminance",
|
name: "Illuminance",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "🌳 Outdoor",
|
title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -273,7 +260,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
icon: "mdi:home-assistant",
|
icon: "mdi:home-assistant",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: "🎉 Updates",
|
title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { until } from "lit/directives/until";
|
import { until } from "lit/directives/until";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-button";
|
||||||
import "../../../src/components/ha-circular-progress";
|
import "../../../src/components/ha-circular-progress";
|
||||||
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
@@ -11,7 +12,6 @@ import {
|
|||||||
demoConfigs,
|
demoConfigs,
|
||||||
selectedDemoConfig,
|
selectedDemoConfig,
|
||||||
selectedDemoConfigIndex,
|
selectedDemoConfigIndex,
|
||||||
setDemoConfig,
|
|
||||||
} from "../configs/demo-configs";
|
} from "../configs/demo-configs";
|
||||||
|
|
||||||
@customElement("ha-demo-card")
|
@customElement("ha-demo-card")
|
||||||
@@ -64,9 +64,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
|
<ha-button @click=${this._nextConfig} .disabled=${this._switching}>
|
||||||
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
|
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
|
||||||
</mwc-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p class="small-hidden">
|
<p class="small-hidden">
|
||||||
@@ -87,9 +87,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
<div class="actions small-hidden">
|
<div class="actions small-hidden">
|
||||||
<a href="https://www.home-assistant.io" target="_blank">
|
<a href="https://www.home-assistant.io" target="_blank">
|
||||||
<mwc-button>
|
<ha-button>
|
||||||
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
|
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
|
||||||
</mwc-button>
|
</ha-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@@ -113,13 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
private async _updateConfig(index: number) {
|
private async _updateConfig(index: number) {
|
||||||
this._switching = true;
|
this._switching = true;
|
||||||
try {
|
fireEvent(this, "set-demo-config" as any, { index });
|
||||||
await setDemoConfig(this.hass, this.lovelace!, index);
|
|
||||||
} catch (err: any) {
|
|
||||||
alert("Failed to switch config :-(");
|
|
||||||
} finally {
|
|
||||||
this._switching = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@@ -149,7 +143,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker mwc-button {
|
.picker ha-button {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "./util/is_frontpage";
|
||||||
import "./ha-demo";
|
import "./ha-demo";
|
||||||
|
|
||||||
import("../../src/resources/ha-style");
|
import("../../src/resources/ha-style");
|
||||||
|
@@ -82,6 +82,8 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
has_entity_name: false,
|
has_entity_name: false,
|
||||||
unique_id: "co2_intensity",
|
unique_id: "co2_intensity",
|
||||||
options: null,
|
options: null,
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config_entry_id: "co2signal",
|
config_entry_id: "co2signal",
|
||||||
@@ -100,6 +102,8 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
has_entity_name: false,
|
has_entity_name: false,
|
||||||
unique_id: "grid_fossil_fuel_percentage",
|
unique_id: "grid_fossil_fuel_percentage",
|
||||||
options: null,
|
options: null,
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -63,46 +63,47 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
#ha-launch-screen svg {
|
#ha-launch-screen svg {
|
||||||
width: 170px;
|
width: 112px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
#ha-launch-screen .ha-launch-screen-spacer {
|
#ha-launch-screen .ha-launch-screen-spacer-top {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px );
|
||||||
|
padding-top: 48px;
|
||||||
|
}
|
||||||
|
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 48px;
|
||||||
|
}
|
||||||
|
.ohf-logo {
|
||||||
|
margin: max(env(safe-area-inset-bottom), 48px) 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
opacity: .66;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.ohf-logo {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="ha-launch-screen">
|
<div id="ha-launch-screen">
|
||||||
<div class="ha-launch-screen-spacer"></div>
|
<div class="ha-launch-screen-spacer-top"></div>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
||||||
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
|
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
|
||||||
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
|
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer-bottom"></div>
|
||||||
|
<div class="ohf-logo">
|
||||||
|
<img src="/static/images/ohf-badge.svg" alt="Home Assistant is a project by the Open Home Foundation" height="46">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ha-demo></ha-demo>
|
<ha-demo></ha-demo>
|
||||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
|
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
|
||||||
<script>
|
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||||
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
|
||||||
if (!isS11_12) {
|
|
||||||
<% for (const entry of latestEntryJS) { %>
|
|
||||||
import("<%= entry %>");
|
|
||||||
<% } %>
|
|
||||||
window.latestJS = true;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
|
||||||
<script>
|
|
||||||
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
|
||||||
(function (d, t) {
|
|
||||||
var g = d.createElement(t),
|
|
||||||
s = d.getElementsByTagName(t)[0];
|
|
||||||
g.src =
|
|
||||||
("https:" == location.protocol ? "//ssl" : "//www") +
|
|
||||||
".google-analytics.com/ga.js";
|
|
||||||
s.parentNode.insertBefore(g, s);
|
|
||||||
})(document, "script");
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,5 +1,55 @@
|
|||||||
import { convertEntities } from "../../../src/fake_data/entity";
|
import { convertEntities } from "../../../src/fake_data/entity";
|
||||||
|
|
||||||
|
export const mapEntities = () =>
|
||||||
|
convertEntities({
|
||||||
|
"zone.home": {
|
||||||
|
entity_id: "zone.home",
|
||||||
|
state: "zoning",
|
||||||
|
attributes: {
|
||||||
|
hidden: true,
|
||||||
|
latitude: 52.3631339,
|
||||||
|
longitude: 4.8903147,
|
||||||
|
radius: 200,
|
||||||
|
friendly_name: "Home",
|
||||||
|
icon: "hademo:home",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"zone.uva": {
|
||||||
|
entity_id: "zone.buckhead",
|
||||||
|
state: "zoning",
|
||||||
|
attributes: {
|
||||||
|
hidden: true,
|
||||||
|
radius: 400,
|
||||||
|
friendly_name: "UvA",
|
||||||
|
icon: "hademo:school",
|
||||||
|
latitude: 52.3558182,
|
||||||
|
longitude: 4.9535376,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"person.arsaboo": {
|
||||||
|
entity_id: "person.arsaboo",
|
||||||
|
state: "not_home",
|
||||||
|
attributes: {
|
||||||
|
radius: 50,
|
||||||
|
friendly_name: "Arsaboo",
|
||||||
|
latitude: 52.3579946,
|
||||||
|
longitude: 4.8664597,
|
||||||
|
entity_picture: "/assets/arsaboo/images/arsaboo.jpg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"person.melody": {
|
||||||
|
entity_id: "person.melody",
|
||||||
|
state: "not_home",
|
||||||
|
attributes: {
|
||||||
|
radius: 50,
|
||||||
|
friendly_name: "Melody",
|
||||||
|
latitude: 52.3408927,
|
||||||
|
longitude: 4.8711073,
|
||||||
|
entity_picture: "/assets/arsaboo/images/melody.jpg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const energyEntities = () =>
|
export const energyEntities = () =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
"sensor.grid_fossil_fuel_percentage": {
|
"sensor.grid_fossil_fuel_percentage": {
|
||||||
|
@@ -1,35 +1,52 @@
|
|||||||
import type { LocalizeFunc } from "../../../src/common/translations/localize";
|
import type { LocalizeFunc } from "../../../src/common/translations/localize";
|
||||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
import { selectedDemoConfig } from "../configs/demo-configs";
|
import {
|
||||||
|
selectedDemoConfig,
|
||||||
|
selectedDemoConfigIndex,
|
||||||
|
setDemoConfig,
|
||||||
|
} from "../configs/demo-configs";
|
||||||
import "../custom-cards/cast-demo-row";
|
import "../custom-cards/cast-demo-row";
|
||||||
import "../custom-cards/ha-demo-card";
|
import "../custom-cards/ha-demo-card";
|
||||||
import type { HADemoCard } from "../custom-cards/ha-demo-card";
|
import { mapEntities } from "./entities";
|
||||||
|
|
||||||
export const mockLovelace = (
|
export const mockLovelace = (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
localizePromise: Promise<LocalizeFunc>
|
localizePromise: Promise<LocalizeFunc>
|
||||||
) => {
|
) => {
|
||||||
hass.mockWS("lovelace/config", () =>
|
hass.mockWS("lovelace/config", ({ url_path }) => {
|
||||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
if (url_path === "map") {
|
||||||
|
hass.addEntities(mapEntities());
|
||||||
|
return {
|
||||||
|
strategy: {
|
||||||
|
type: "map",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||||
([config, localize]) => config.lovelace(localize)
|
([config, localize]) => config.lovelace(localize)
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||||
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
||||||
};
|
};
|
||||||
|
|
||||||
customElements.whenDefined("hui-card").then(() => {
|
customElements.whenDefined("hui-root").then(() => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const HUIView = customElements.get("hui-card");
|
const HUIRoot = customElements.get("hui-root")!;
|
||||||
// Patch HUI-VIEW to make the lovelace object available to the demo card
|
|
||||||
const oldCreateCard = HUIView!.prototype.createElement;
|
|
||||||
|
|
||||||
HUIView!.prototype.createElement = function (config) {
|
const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
|
||||||
const el = oldCreateCard.call(this, config);
|
|
||||||
if (config.type === "custom:ha-demo-card") {
|
HUIRoot.prototype.firstUpdated = function (changedProperties) {
|
||||||
(el as HADemoCard).lovelace = this.lovelace;
|
oldFirstUpdated.call(this, changedProperties);
|
||||||
}
|
this.addEventListener("set-demo-config", async (ev) => {
|
||||||
return el;
|
const index = (ev as CustomEvent).detail.index;
|
||||||
|
try {
|
||||||
|
await setDemoConfig(this.hass, this.lovelace!, index);
|
||||||
|
} catch (err: any) {
|
||||||
|
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
|
||||||
|
alert("Failed to switch config :-(");
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
1
demo/src/util/is_frontpage.ts
Normal file
1
demo/src/util/is_frontpage.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const isFrontpageEmbed = document.location.search === "?frontpage";
|
BIN
gallery/public/images/paulus.jpg
Normal file
BIN
gallery/public/images/paulus.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
@@ -1,7 +1,9 @@
|
|||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { html, css, LitElement, PropertyValues } from "lit";
|
import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
|
import memoizeOne from "memoize-one";
|
||||||
|
import "../../../src/panels/lovelace/cards/hui-card";
|
||||||
|
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
|
||||||
export interface DemoCardConfig {
|
export interface DemoCardConfig {
|
||||||
@@ -19,7 +21,12 @@ class DemoCard extends LitElement {
|
|||||||
|
|
||||||
@state() private _size?: number;
|
@state() private _size?: number;
|
||||||
|
|
||||||
@query("#card") private _card!: HTMLElement;
|
@query("hui-card", false) private _card?: HuiCard;
|
||||||
|
|
||||||
|
private _config = memoizeOne((config: string) => {
|
||||||
|
const c = (load(config) as any)[0];
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
@@ -30,63 +37,32 @@ class DemoCard extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div id="card"></div>
|
<hui-card
|
||||||
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
|
.config=${this._config(this.config.config)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@card-updated=${this._cardUpdated}
|
||||||
|
></hui-card>
|
||||||
|
${this.showConfig
|
||||||
|
? html`<pre>${this.config.config.trim()}</pre>`
|
||||||
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProps: PropertyValues) {
|
private async _cardUpdated(ev) {
|
||||||
super.updated(changedProps);
|
ev.stopPropagation();
|
||||||
|
this._updateSize();
|
||||||
if (changedProps.has("config")) {
|
|
||||||
const card = this._card;
|
|
||||||
while (card.lastChild) {
|
|
||||||
card.removeChild(card.lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = this._createCardElement((load(this.config.config) as any)[0]);
|
|
||||||
card.appendChild(el);
|
|
||||||
this._getSize(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedProps.has("hass")) {
|
|
||||||
const card = this._card.lastChild;
|
|
||||||
if (card) {
|
|
||||||
(card as any).hass = this.hass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getSize(el) {
|
private async _updateSize() {
|
||||||
await customElements.whenDefined(el.localName);
|
this._size = await this._card?.getCardSize();
|
||||||
|
|
||||||
if (!("getCardSize" in el)) {
|
|
||||||
this._size = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._size = await el.getCardSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_createCardElement(cardConfig) {
|
protected update(
|
||||||
const element = createCardElement(cardConfig);
|
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||||
if (this.hass) {
|
): void {
|
||||||
element.hass = this.hass;
|
super.update(_changedProperties);
|
||||||
}
|
this._updateSize();
|
||||||
element.addEventListener(
|
|
||||||
"ll-rebuild",
|
|
||||||
(ev) => {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._rebuildCard(element, cardConfig);
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
_rebuildCard(cardElToReplace, config) {
|
|
||||||
const newCardEl = this._createCardElement(config);
|
|
||||||
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@@ -101,7 +77,7 @@ class DemoCard extends LitElement {
|
|||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
#card {
|
hui-card {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
@@ -532,15 +532,6 @@ export default {
|
|||||||
last_changed: "2018-07-19T10:44:46.200946+00:00",
|
last_changed: "2018-07-19T10:44:46.200946+00:00",
|
||||||
last_updated: "2018-07-19T10:44:46.200946+00:00",
|
last_updated: "2018-07-19T10:44:46.200946+00:00",
|
||||||
},
|
},
|
||||||
"mailbox.demomailbox": {
|
|
||||||
entity_id: "mailbox.demomailbox",
|
|
||||||
state: "10",
|
|
||||||
attributes: {
|
|
||||||
friendly_name: "DemoMailbox",
|
|
||||||
},
|
|
||||||
last_changed: "2018-07-19T10:45:16.555210+00:00",
|
|
||||||
last_updated: "2018-07-19T10:45:16.555210+00:00",
|
|
||||||
},
|
|
||||||
"input_select.living_room_preset": {
|
"input_select.living_room_preset": {
|
||||||
entity_id: "input_select.living_room_preset",
|
entity_id: "input_select.living_room_preset",
|
||||||
state: "Visitors",
|
state: "Visitors",
|
||||||
|
@@ -3,13 +3,16 @@ title: When to use remove, delete, add and create
|
|||||||
subtitle: The difference between remove/delete and add/create.
|
subtitle: The difference between remove/delete and add/create.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Remove vs Delete
|
# Removing or deleting content
|
||||||
|
|
||||||
Remove and Delete are quite similar, but can be frustrating if used inconsistently.
|
_Remove_ and _Delete_ are quite similar, but can be frustrating if used inconsistently.
|
||||||
|
|
||||||
|
- Remove refers to an action that can be restored or reapplied.
|
||||||
|
- Delete refers to a permanent, non-recoverable action.
|
||||||
|
|
||||||
## Remove
|
## Remove
|
||||||
|
|
||||||
Take away and set aside, but kept in existence.
|
The term _Remove_ should always be used when an item/setting or content is to be removed or disassociated, but the action can be reversed or reapplied.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@@ -22,7 +25,7 @@ For example:
|
|||||||
|
|
||||||
## Delete
|
## Delete
|
||||||
|
|
||||||
Erase, rendered nonexistent or nonrecoverable.
|
The term _Delete_ should always be used to refer to any action that will cause the permanent deletion of an item/setting or content.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import { getEntity } from "../../../../src/fake_data/entity";
|
|||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import "../../components/demo-black-white-row";
|
import "../../components/demo-black-white-row";
|
||||||
|
import { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
@@ -41,7 +42,7 @@ const ENTITIES = [
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEVICES = [
|
const DEVICES: DeviceRegistryEntry[] = [
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
@@ -53,6 +54,7 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "volume1"] as [string, string]],
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
model_id: null,
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Dishwasher",
|
name: "Dishwasher",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -60,6 +62,8 @@ const DEVICES = [
|
|||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@@ -72,6 +76,7 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
model_id: null,
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Lamp",
|
name: "Lamp",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -79,6 +84,8 @@ const DEVICES = [
|
|||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@@ -91,6 +98,7 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
model_id: null,
|
||||||
name_by_user: "User name",
|
name_by_user: "User name",
|
||||||
name: "Technical name",
|
name: "Technical name",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -98,6 +106,8 @@ const DEVICES = [
|
|||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -110,6 +120,8 @@ const AREAS: AreaRegistryEntry[] = [
|
|||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
@@ -119,6 +131,8 @@ const AREAS: AreaRegistryEntry[] = [
|
|||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "livingroom",
|
area_id: "livingroom",
|
||||||
@@ -128,6 +142,8 @@ const AREAS: AreaRegistryEntry[] = [
|
|||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@ import { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
|||||||
import { LabelRegistryEntry } from "../../../../src/data/label_registry";
|
import { LabelRegistryEntry } from "../../../../src/data/label_registry";
|
||||||
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
|
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
|
||||||
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
|
import { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
@@ -41,7 +42,7 @@ const ENTITIES = [
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEVICES = [
|
const DEVICES: DeviceRegistryEntry[] = [
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
@@ -53,6 +54,7 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "volume1"] as [string, string]],
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
model_id: null,
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Dishwasher",
|
name: "Dishwasher",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -60,6 +62,8 @@ const DEVICES = [
|
|||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@@ -72,6 +76,7 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
model_id: null,
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Lamp",
|
name: "Lamp",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -79,6 +84,8 @@ const DEVICES = [
|
|||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@@ -91,6 +98,7 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
model_id: null,
|
||||||
name_by_user: "User name",
|
name_by_user: "User name",
|
||||||
name: "Technical name",
|
name: "Technical name",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -98,6 +106,8 @@ const DEVICES = [
|
|||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -110,6 +120,8 @@ const AREAS: AreaRegistryEntry[] = [
|
|||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
@@ -119,6 +131,8 @@ const AREAS: AreaRegistryEntry[] = [
|
|||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "livingroom",
|
area_id: "livingroom",
|
||||||
@@ -128,6 +142,8 @@ const AREAS: AreaRegistryEntry[] = [
|
|||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -138,6 +154,8 @@ const FLOORS: FloorRegistryEntry[] = [
|
|||||||
level: 0,
|
level: 0,
|
||||||
icon: null,
|
icon: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
floor_id: "first",
|
floor_id: "first",
|
||||||
@@ -145,6 +163,8 @@ const FLOORS: FloorRegistryEntry[] = [
|
|||||||
level: 1,
|
level: 1,
|
||||||
icon: "mdi:numeric-1",
|
icon: "mdi:numeric-1",
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
floor_id: "second",
|
floor_id: "second",
|
||||||
@@ -152,6 +172,8 @@ const FLOORS: FloorRegistryEntry[] = [
|
|||||||
level: 2,
|
level: 2,
|
||||||
icon: "mdi:numeric-2",
|
icon: "mdi:numeric-2",
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -162,6 +184,8 @@ const LABELS: LabelRegistryEntry[] = [
|
|||||||
icon: null,
|
icon: null,
|
||||||
color: "yellow",
|
color: "yellow",
|
||||||
description: null,
|
description: null,
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label_id: "entertainment",
|
label_id: "entertainment",
|
||||||
@@ -169,6 +193,8 @@ const LABELS: LabelRegistryEntry[] = [
|
|||||||
icon: "mdi:popcorn",
|
icon: "mdi:popcorn",
|
||||||
color: "blue",
|
color: "blue",
|
||||||
description: null,
|
description: null,
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -287,11 +287,11 @@ const CONFIGS = [
|
|||||||
config: `
|
config: `
|
||||||
- type: entities
|
- type: entities
|
||||||
entities:
|
entities:
|
||||||
- type: call-service
|
- type: perform-action
|
||||||
icon: mdi:power
|
icon: mdi:power
|
||||||
name: Bed light
|
name: Bed light
|
||||||
action_name: Toggle light
|
action_name: Toggle light
|
||||||
service: light.toggle
|
action: light.toggle
|
||||||
data:
|
data:
|
||||||
entity_id: light.bed_light
|
entity_id: light.bed_light
|
||||||
- type: section
|
- type: section
|
||||||
|
3
gallery/src/pages/lovelace/picture-card.markdown
Normal file
3
gallery/src/pages/lovelace/picture-card.markdown
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Picture Card
|
||||||
|
---
|
61
gallery/src/pages/lovelace/picture-card.ts
Normal file
61
gallery/src/pages/lovelace/picture-card.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
|
import { customElement, query } from "lit/decorators";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
|
import "../../components/demo-cards";
|
||||||
|
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("person", "paulus", "home", {
|
||||||
|
friendly_name: "Paulus",
|
||||||
|
entity_picture: "/images/paulus.jpg",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONFIGS = [
|
||||||
|
{
|
||||||
|
heading: "Image URL",
|
||||||
|
config: `
|
||||||
|
- type: picture
|
||||||
|
image: /images/living_room.png
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Person entity",
|
||||||
|
config: `
|
||||||
|
- type: picture
|
||||||
|
image_entity: person.paulus
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Error: Image required",
|
||||||
|
config: `
|
||||||
|
- type: picture
|
||||||
|
entity: person.paulus
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-lovelace-picture-card")
|
||||||
|
class DemoPicture extends LitElement {
|
||||||
|
@query("#demos") private _demoRoot!: HTMLElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
const hass = provideHass(this._demoRoot);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("lovelace", "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
mockIcons(hass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-lovelace-picture-card": DemoPicture;
|
||||||
|
}
|
||||||
|
}
|
@@ -25,6 +25,15 @@ const ENTITIES = [
|
|||||||
friendly_name: "Movement Backyard",
|
friendly_name: "Movement Backyard",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
}),
|
}),
|
||||||
|
getEntity("person", "paulus", "home", {
|
||||||
|
friendly_name: "Paulus",
|
||||||
|
entity_picture: "/images/paulus.jpg",
|
||||||
|
}),
|
||||||
|
getEntity("sensor", "battery", 35, {
|
||||||
|
device_class: "battery",
|
||||||
|
friendly_name: "Battery",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -123,6 +132,19 @@ const CONFIGS = [
|
|||||||
left: 35%
|
left: 35%
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Person entity",
|
||||||
|
config: `
|
||||||
|
- type: picture-elements
|
||||||
|
image_entity: person.paulus
|
||||||
|
elements:
|
||||||
|
- type: state-icon
|
||||||
|
entity: sensor.battery
|
||||||
|
style:
|
||||||
|
top: 8%
|
||||||
|
left: 8%
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-lovelace-picture-elements-card")
|
@customElement("demo-lovelace-picture-elements-card")
|
||||||
|
@@ -12,6 +12,10 @@ const ENTITIES = [
|
|||||||
getEntity("light", "bed_light", "off", {
|
getEntity("light", "bed_light", "off", {
|
||||||
friendly_name: "Bed Light",
|
friendly_name: "Bed Light",
|
||||||
}),
|
}),
|
||||||
|
getEntity("person", "paulus", "home", {
|
||||||
|
friendly_name: "Paulus",
|
||||||
|
entity_picture: "/images/paulus.jpg",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -50,6 +54,13 @@ const CONFIGS = [
|
|||||||
entity: camera.demo_camera
|
entity: camera.demo_camera
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Person entity",
|
||||||
|
config: `
|
||||||
|
- type: picture-entity
|
||||||
|
entity: person.paulus
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Hidden name",
|
heading: "Hidden name",
|
||||||
config: `
|
config: `
|
||||||
|
@@ -20,6 +20,15 @@ const ENTITIES = [
|
|||||||
friendly_name: "Basement Floor Wet",
|
friendly_name: "Basement Floor Wet",
|
||||||
device_class: "moisture",
|
device_class: "moisture",
|
||||||
}),
|
}),
|
||||||
|
getEntity("person", "paulus", "home", {
|
||||||
|
friendly_name: "Paulus",
|
||||||
|
entity_picture: "/images/paulus.jpg",
|
||||||
|
}),
|
||||||
|
getEntity("sensor", "battery", 35, {
|
||||||
|
device_class: "battery",
|
||||||
|
friendly_name: "Battery",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -90,6 +99,15 @@ const CONFIGS = [
|
|||||||
- light.ceiling_lights
|
- light.ceiling_lights
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Person entity",
|
||||||
|
config: `
|
||||||
|
- type: picture-glance
|
||||||
|
image_entity: person.paulus
|
||||||
|
entities:
|
||||||
|
- sensor.battery
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Custom icon",
|
heading: "Custom icon",
|
||||||
config: `
|
config: `
|
||||||
|
@@ -8,6 +8,7 @@ import { getEntity } from "../../../../src/fake_data/entity";
|
|||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import "../../components/demo-cards";
|
import "../../components/demo-cards";
|
||||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||||
|
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("switch", "tv_outlet", "on", {
|
getEntity("switch", "tv_outlet", "on", {
|
||||||
@@ -60,6 +61,36 @@ const ENTITIES = [
|
|||||||
CoverEntityFeature.OPEN_TILT +
|
CoverEntityFeature.OPEN_TILT +
|
||||||
CoverEntityFeature.STOP_TILT,
|
CoverEntityFeature.STOP_TILT,
|
||||||
}),
|
}),
|
||||||
|
getEntity("input_number", "counter", "1.0", {
|
||||||
|
friendly_name: "Counter",
|
||||||
|
initial: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1,
|
||||||
|
mode: "slider",
|
||||||
|
}),
|
||||||
|
getEntity("climate", "dual_thermostat", "heat/cool", {
|
||||||
|
friendly_name: "Dual thermostat",
|
||||||
|
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
|
||||||
|
min_temp: 7,
|
||||||
|
max_temp: 35,
|
||||||
|
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
|
||||||
|
preset_modes: ["home", "eco", "away"],
|
||||||
|
swing_modes: ["auto", "1", "2", "3", "off"],
|
||||||
|
current_temperature: 23,
|
||||||
|
target_temp_high: 24,
|
||||||
|
target_temp_low: 21,
|
||||||
|
fan_mode: "auto_low",
|
||||||
|
preset_mode: "home",
|
||||||
|
swing_mode: "auto",
|
||||||
|
supported_features:
|
||||||
|
ClimateEntityFeature.TURN_ON +
|
||||||
|
ClimateEntityFeature.TURN_OFF +
|
||||||
|
ClimateEntityFeature.SWING_MODE +
|
||||||
|
ClimateEntityFeature.PRESET_MODE +
|
||||||
|
ClimateEntityFeature.FAN_MODE +
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -193,6 +224,25 @@ const CONFIGS = [
|
|||||||
- type: "cover-tilt"
|
- type: "cover-tilt"
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Number buttons feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: input_number.counter
|
||||||
|
features:
|
||||||
|
- type: numeric-input
|
||||||
|
style: buttons
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Dual thermostat feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: climate.dual_thermostat
|
||||||
|
features:
|
||||||
|
- type: target-temperature
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-lovelace-tile-card")
|
@customElement("demo-lovelace-tile-card")
|
||||||
|
@@ -140,6 +140,9 @@ const ENTITIES: HassEntity[] = [
|
|||||||
createEntity("climate.auto_preheating", "auto", undefined, {
|
createEntity("climate.auto_preheating", "auto", undefined, {
|
||||||
hvac_action: "preheating",
|
hvac_action: "preheating",
|
||||||
}),
|
}),
|
||||||
|
createEntity("climate.auto_defrosting", "auto", undefined, {
|
||||||
|
hvac_action: "defrosting",
|
||||||
|
}),
|
||||||
createEntity("climate.auto_heating", "auto", undefined, {
|
createEntity("climate.auto_heating", "auto", undefined, {
|
||||||
hvac_action: "heating",
|
hvac_action: "heating",
|
||||||
}),
|
}),
|
||||||
@@ -355,13 +358,11 @@ export class DemoEntityState extends LitElement {
|
|||||||
},
|
},
|
||||||
entity_id: {
|
entity_id: {
|
||||||
title: "Entity ID",
|
title: "Entity ID",
|
||||||
width: "30%",
|
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
title: "State",
|
title: "State",
|
||||||
width: "20%",
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
template: (entry) =>
|
template: (entry) =>
|
||||||
html`${computeStateDisplay(
|
html`${computeStateDisplay(
|
||||||
@@ -376,14 +377,12 @@ export class DemoEntityState extends LitElement {
|
|||||||
device_class: {
|
device_class: {
|
||||||
title: "Device class",
|
title: "Device class",
|
||||||
template: (entry) => html`${entry.device_class ?? "-"}`,
|
template: (entry) => html`${entry.device_class ?? "-"}`,
|
||||||
width: "20%",
|
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
domain: {
|
domain: {
|
||||||
title: "Domain",
|
title: "Domain",
|
||||||
template: (entry) => html`${computeDomain(entry.entity_id)}`,
|
template: (entry) => html`${computeDomain(entry.entity_id)}`,
|
||||||
width: "20%",
|
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
|
@@ -203,6 +203,8 @@ const createEntityRegistryEntries = (
|
|||||||
options: null,
|
options: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
categories: {},
|
categories: {},
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -215,6 +217,7 @@ const createDeviceRegistryEntries = (
|
|||||||
connections: [],
|
connections: [],
|
||||||
manufacturer: "ESPHome",
|
manufacturer: "ESPHome",
|
||||||
model: "Mock Device",
|
model: "Mock Device",
|
||||||
|
model_id: "ABC-001",
|
||||||
name: "Tag Reader",
|
name: "Tag Reader",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: "1.0.0",
|
hw_version: "1.0.0",
|
||||||
@@ -227,6 +230,8 @@ const createDeviceRegistryEntries = (
|
|||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -127,14 +127,13 @@ export class HassioBackups extends LitElement {
|
|||||||
main: true,
|
main: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
flex: 2,
|
||||||
template: (backup) =>
|
template: (backup) =>
|
||||||
html`${backup.name || backup.slug}
|
html`${backup.name || backup.slug}
|
||||||
<div class="secondary">${backup.secondary}</div>`,
|
<div class="secondary">${backup.secondary}</div>`,
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
title: this.supervisor.localize("backup.size"),
|
title: this.supervisor.localize("backup.size"),
|
||||||
width: "15%",
|
|
||||||
hidden: narrow,
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
@@ -142,7 +141,6 @@ export class HassioBackups extends LitElement {
|
|||||||
},
|
},
|
||||||
location: {
|
location: {
|
||||||
title: this.supervisor.localize("backup.location"),
|
title: this.supervisor.localize("backup.location"),
|
||||||
width: "15%",
|
|
||||||
hidden: narrow,
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
@@ -151,7 +149,6 @@ export class HassioBackups extends LitElement {
|
|||||||
},
|
},
|
||||||
date: {
|
date: {
|
||||||
title: this.supervisor.localize("backup.created"),
|
title: this.supervisor.localize("backup.created"),
|
||||||
width: "15%",
|
|
||||||
direction: "desc",
|
direction: "desc",
|
||||||
hidden: narrow,
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import Fuse from "fuse.js";
|
|
||||||
import type { IFuseOptions } from "fuse.js";
|
import type { IFuseOptions } from "fuse.js";
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
import { stripDiacritics } from "../../../src/common/string/strip-diacritics";
|
||||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||||
|
import { getStripDiacriticsFn } from "../../../src/util/fuse";
|
||||||
|
|
||||||
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||||
const options: IFuseOptions<StoreAddon> = {
|
const options: IFuseOptions<StoreAddon> = {
|
||||||
@@ -8,7 +10,8 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
|
getFn: getStripDiacriticsFn,
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(addons, options);
|
const fuse = new Fuse(addons, options);
|
||||||
return fuse.search(filter).map((result) => result.item);
|
return fuse.search(stripDiacritics(filter)).map((result) => result.item);
|
||||||
}
|
}
|
||||||
|
@@ -66,7 +66,8 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
repo.slug !== "core" && // The core add-ons repository
|
repo.slug !== "core" && // The core add-ons repository
|
||||||
repo.slug !== "local" && // Locally managed add-ons
|
repo.slug !== "local" && // Locally managed add-ons
|
||||||
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
|
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
|
||||||
repo.slug !== "5c53de3b" // The ESPHome repository
|
repo.slug !== "5c53de3b" && // The ESPHome repository
|
||||||
|
repo.slug !== "d5369777" // Music Assistant repository
|
||||||
)
|
)
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)
|
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)
|
||||||
|
@@ -4,11 +4,7 @@
|
|||||||
el.src = src;
|
el.src = src;
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
}
|
}
|
||||||
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
if (<%= modernRegex %>.test(navigator.userAgent)) {
|
||||||
<% for (const entry of es5EntryJS) { %>
|
|
||||||
loadES5("<%= entry %>");
|
|
||||||
<% } %>
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
<% for (const entry of latestEntryJS) { %>
|
<% for (const entry of latestEntryJS) { %>
|
||||||
new Function("import('<%= entry %>')")();
|
new Function("import('<%= entry %>')")();
|
||||||
@@ -17,6 +13,10 @@
|
|||||||
<% for (const entry of es5EntryJS) { %>
|
<% for (const entry of es5EntryJS) { %>
|
||||||
loadES5("<%= entry %>");
|
loadES5("<%= entry %>");
|
||||||
<% } %>
|
<% } %>
|
||||||
|
} else {
|
||||||
|
<% for (const entry of es5EntryJS) { %>
|
||||||
|
loadES5("<%= entry %>");
|
||||||
|
<% } %>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../../src/resources/compatibility";
|
import "../../src/resources/compatibility";
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
|
||||||
import "./hassio-main";
|
import "./hassio-main";
|
||||||
|
|
||||||
import("../../src/resources/ha-style");
|
import("../../src/resources/ha-style");
|
||||||
|
116
package.json
116
package.json
@@ -16,7 +16,7 @@
|
|||||||
"lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
|
"lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
|
||||||
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit",
|
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit",
|
||||||
"format": "yarn run format:eslint && yarn run format:prettier",
|
"format": "yarn run format:eslint && yarn run format:prettier",
|
||||||
"postinstall": "husky install",
|
"postinstall": "husky",
|
||||||
"prepack": "pinst --disable",
|
"prepack": "pinst --disable",
|
||||||
"postpack": "pinst --enable",
|
"postpack": "pinst --enable",
|
||||||
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
|
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
|
||||||
@@ -25,15 +25,15 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.24.6",
|
"@babel/runtime": "7.25.4",
|
||||||
"@braintree/sanitize-url": "7.0.2",
|
"@braintree/sanitize-url": "7.1.0",
|
||||||
"@codemirror/autocomplete": "6.16.2",
|
"@codemirror/autocomplete": "6.18.0",
|
||||||
"@codemirror/commands": "6.5.0",
|
"@codemirror/commands": "6.6.0",
|
||||||
"@codemirror/language": "6.10.1",
|
"@codemirror/language": "6.10.2",
|
||||||
"@codemirror/legacy-modes": "6.4.0",
|
"@codemirror/legacy-modes": "6.4.1",
|
||||||
"@codemirror/search": "6.5.6",
|
"@codemirror/search": "6.5.6",
|
||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.26.3",
|
"@codemirror/view": "6.32.0",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||||
"@formatjs/intl-displaynames": "6.6.8",
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
@@ -43,17 +43,17 @@
|
|||||||
"@formatjs/intl-numberformat": "8.10.3",
|
"@formatjs/intl-numberformat": "8.10.3",
|
||||||
"@formatjs/intl-pluralrules": "5.2.14",
|
"@formatjs/intl-pluralrules": "5.2.14",
|
||||||
"@formatjs/intl-relativetimeformat": "11.2.14",
|
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||||
"@fullcalendar/core": "6.1.13",
|
"@fullcalendar/core": "6.1.15",
|
||||||
"@fullcalendar/daygrid": "6.1.13",
|
"@fullcalendar/daygrid": "6.1.15",
|
||||||
"@fullcalendar/interaction": "6.1.13",
|
"@fullcalendar/interaction": "6.1.15",
|
||||||
"@fullcalendar/list": "6.1.13",
|
"@fullcalendar/list": "6.1.15",
|
||||||
"@fullcalendar/luxon3": "6.1.13",
|
"@fullcalendar/luxon3": "6.1.15",
|
||||||
"@fullcalendar/timegrid": "6.1.13",
|
"@fullcalendar/timegrid": "6.1.15",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.1",
|
||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.7",
|
"@lit-labs/motion": "1.0.7",
|
||||||
"@lit-labs/observers": "2.0.2",
|
"@lit-labs/observers": "2.0.2",
|
||||||
"@lit-labs/virtualizer": "2.0.12",
|
"@lit-labs/virtualizer": "2.0.14",
|
||||||
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "1.5.0",
|
"@material/web": "2.1.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
@@ -88,8 +88,8 @@
|
|||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.3.13",
|
"@vaadin/combo-box": "24.4.6",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.3.13",
|
"@vaadin/vaadin-themable-mixin": "24.4.6",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@@ -97,10 +97,10 @@
|
|||||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.3",
|
"chart.js": "4.4.4",
|
||||||
"color-name": "2.0.0",
|
"color-name": "2.0.0",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.37.1",
|
"core-js": "3.38.1",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"date-fns": "3.6.0",
|
"date-fns": "3.6.0",
|
||||||
"date-fns-tz": "3.1.3",
|
"date-fns-tz": "3.1.3",
|
||||||
@@ -110,27 +110,27 @@
|
|||||||
"fuse.js": "7.0.0",
|
"fuse.js": "7.0.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||||
"home-assistant-js-websocket": "9.3.0",
|
"home-assistant-js-websocket": "9.4.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.14",
|
"intl-messageformat": "10.5.14",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"luxon": "3.4.4",
|
"luxon": "3.5.0",
|
||||||
"marked": "12.0.2",
|
"marked": "14.0.0",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "0.3.2",
|
"proxy-polyfill": "0.3.2",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"qr-scanner": "1.4.2",
|
"qr-scanner": "1.4.2",
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "1.5.4",
|
||||||
"roboto-fontface": "0.10.0",
|
"roboto-fontface": "0.10.0",
|
||||||
"rrule": "2.8.1",
|
"rrule": "2.8.1",
|
||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "1.0.4",
|
"superstruct": "2.0.2",
|
||||||
"tinykeys": "2.1.0",
|
"tinykeys": "3.0.0",
|
||||||
"tsparticles-engine": "2.12.0",
|
"tsparticles-engine": "2.12.0",
|
||||||
"tsparticles-preset-links": "2.12.0",
|
"tsparticles-preset-links": "2.12.0",
|
||||||
"ua-parser-js": "1.0.38",
|
"ua-parser-js": "1.0.38",
|
||||||
@@ -149,26 +149,26 @@
|
|||||||
"xss": "1.0.15"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.24.6",
|
"@babel/core": "7.25.2",
|
||||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||||
"@babel/plugin-proposal-decorators": "7.24.6",
|
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "7.24.6",
|
"@babel/plugin-transform-runtime": "7.25.4",
|
||||||
"@babel/preset-env": "7.24.6",
|
"@babel/preset-env": "7.25.4",
|
||||||
"@babel/preset-typescript": "7.24.6",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
"@bundle-stats/plugin-webpack-filter": "4.14.2",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.5.0",
|
"@lokalise/node-api": "12.7.0",
|
||||||
"@octokit/auth-oauth-device": "7.1.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
"@octokit/plugin-retry": "7.1.1",
|
"@octokit/plugin-retry": "7.1.1",
|
||||||
"@octokit/rest": "20.1.1",
|
"@octokit/rest": "21.0.2",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.4",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
"@rollup/plugin-commonjs": "25.0.8",
|
"@rollup/plugin-commonjs": "26.0.1",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.5",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.14",
|
"@types/chromecast-caf-receiver": "6.0.17",
|
||||||
"@types/chromecast-caf-sender": "1.0.10",
|
"@types/chromecast-caf-sender": "1.0.10",
|
||||||
"@types/color-name": "1.1.4",
|
"@types/color-name": "1.1.4",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
@@ -178,19 +178,20 @@
|
|||||||
"@types/leaflet-draw": "1.0.11",
|
"@types/leaflet-draw": "1.0.11",
|
||||||
"@types/lodash.merge": "4.6.9",
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.4.2",
|
"@types/luxon": "3.4.2",
|
||||||
"@types/mocha": "10.0.6",
|
"@types/mocha": "10.0.7",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/serve-handler": "6.1.4",
|
"@types/serve-handler": "6.1.4",
|
||||||
"@types/sortablejs": "1.15.8",
|
"@types/sortablejs": "1.15.8",
|
||||||
"@types/tar": "6.1.13",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.11.0",
|
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||||
"@typescript-eslint/parser": "7.11.0",
|
"@typescript-eslint/parser": "7.18.0",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
|
"browserslist-useragent-regexp": "4.1.3",
|
||||||
"chai": "5.1.1",
|
"chai": "5.1.1",
|
||||||
"del": "7.1.0",
|
"del": "7.1.0",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
@@ -200,51 +201,51 @@
|
|||||||
"eslint-import-resolver-webpack": "0.13.8",
|
"eslint-import-resolver-webpack": "0.13.8",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.14.0",
|
"eslint-plugin-lit": "1.14.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.2",
|
"eslint-plugin-lit-a11y": "4.1.4",
|
||||||
"eslint-plugin-unused-imports": "4.0.0",
|
"eslint-plugin-unused-imports": "4.1.3",
|
||||||
"eslint-plugin-wc": "2.1.0",
|
"eslint-plugin-wc": "2.1.1",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"glob": "10.4.1",
|
"glob": "11.0.0",
|
||||||
"gulp": "5.0.0",
|
"gulp": "5.0.0",
|
||||||
|
"gulp-brotli": "3.0.0",
|
||||||
"gulp-json-transform": "0.5.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.1",
|
"gulp-zopfli-green": "6.0.2",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.0.11",
|
"husky": "9.1.5",
|
||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "15.2.5",
|
"lint-staged": "15.2.9",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
"lodash.merge": "4.6.2",
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.10",
|
"magic-string": "0.30.11",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.4.0",
|
"mocha": "10.5.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "10.1.0",
|
"open": "10.1.0",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.3.3",
|
||||||
"rollup": "2.79.1",
|
"rollup": "2.79.1",
|
||||||
"rollup-plugin-string": "3.0.0",
|
"rollup-plugin-string": "3.0.0",
|
||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.12.0",
|
"rollup-plugin-visualizer": "5.12.0",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "18.0.0",
|
"sinon": "18.0.0",
|
||||||
"source-map-url": "0.4.1",
|
|
||||||
"systemjs": "6.15.1",
|
"systemjs": "6.15.1",
|
||||||
"tar": "7.2.0",
|
"tar": "7.4.3",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.5.4",
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.94.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "6.0.1",
|
"webpackbar": "6.0.1",
|
||||||
"workbox-build": "7.1.1"
|
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||||
},
|
},
|
||||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
@@ -253,8 +254,9 @@
|
|||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"clean-css": "5.3.3",
|
"clean-css": "5.3.3",
|
||||||
"@lit/reactive-element": "1.6.3",
|
"@lit/reactive-element": "1.6.3",
|
||||||
|
"@fullcalendar/daygrid": "6.1.15",
|
||||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.2.2"
|
"packageManager": "yarn@4.4.0"
|
||||||
}
|
}
|
||||||
|
1
public/static/icons/ohf.svg
Normal file
1
public/static/icons/ohf.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
3
public/static/images/logo_x.svg
Normal file
3
public/static/images/logo_x.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 430 B |
66
public/static/images/ohf-badge.svg
Normal file
66
public/static/images/ohf-badge.svg
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="b" data-name="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760.69 138.69">
|
||||||
|
<g id="c" data-name="Layer_1">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M136.22,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM136.27,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||||
|
<path d="M184.16,80.53c0,3.47-1.06,6.27-3.18,8.41s-4.98,3.21-8.59,3.21h-7.45v12h-6.56v-35.18h14.06c3.64,0,6.5,1.04,8.59,3.11s3.13,4.89,3.13,8.45ZM177.25,80.39c0-1.64-.52-2.98-1.56-4.03s-2.52-1.57-4.44-1.57h-6.3v11.65h6.26c1.95,0,3.45-.55,4.49-1.65s1.56-2.57,1.56-4.39Z"/>
|
||||||
|
<path d="M210.82,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
|
||||||
|
<path d="M246.95,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||||
|
<path d="M266.45,68.98h6.56v14.44l14.7.05v-14.48h6.63v35.18h-6.63v-14.84l-14.7-.09v14.93h-6.56v-35.18Z"/>
|
||||||
|
<path d="M316.41,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM316.46,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||||
|
<path d="M373.66,68.98v35.18h-6.45v-20.55l-8.11,20.55h-6.23l-8.02-20.39v20.39h-6.28v-35.18h6.28l11.13,27.54,11.23-27.54h6.45Z"/>
|
||||||
|
<path d="M402.87,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
|
||||||
|
<path d="M427.83,75.12v8.93h13.01l-.05,5.91h-12.96v14.2h-6.52v-35.18h21.98l-.05,6.14h-15.42Z"/>
|
||||||
|
<path d="M463.16,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM463.21,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||||
|
<path d="M485,68.98h6.56v22.12c0,2.31.72,4.12,2.16,5.43s3.3,1.96,5.58,1.96,4.08-.67,5.58-2.02,2.25-3.13,2.25-5.37v-22.12h6.52v22.31c0,2.08-.38,3.98-1.14,5.7s-1.79,3.14-3.09,4.25-2.82,1.98-4.56,2.59-3.59.91-5.55.91c-2.59,0-4.96-.52-7.1-1.55s-3.88-2.58-5.2-4.65-1.99-4.49-1.99-7.25v-22.31Z"/>
|
||||||
|
<path d="M549.63,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||||
|
<path d="M586.9,86.58c.05,3.34-.71,6.37-2.27,9.08s-3.7,4.82-6.42,6.32-5.73,2.23-9.02,2.18h-12.42v-35.18h12.42c2.45-.03,4.78.39,6.98,1.28s4.1,2.1,5.68,3.66,2.84,3.43,3.75,5.64,1.35,4.55,1.3,7.03ZM579.99,86.58c0-3.39-1-6.16-3.01-8.3s-4.62-3.21-7.84-3.21h-5.81v23.04h5.81c3.27,0,5.89-1.06,7.88-3.19s2.98-4.91,2.98-8.34Z"/>
|
||||||
|
<path d="M609.16,96.19h-12.73l-2.79,7.97h-6.82l12.68-35.18h6.63l12.66,35.18h-6.96l-2.67-7.97ZM607.24,90.73l-4.43-12.87-4.45,12.87h8.88Z"/>
|
||||||
|
<path d="M642.87,75.17h-9.89v28.99h-6.56v-28.99h-9.94v-6.19h26.39v6.19Z"/>
|
||||||
|
<path d="M647.06,104.16v-35.18h6.56v35.18h-6.56Z"/>
|
||||||
|
<path d="M675.71,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM675.76,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||||
|
<path d="M726.96,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M94.34,79.34c0,2.75-2.25,5-5,5h-50c-2.75,0-5-2.25-5-5v-20c0-2.75,1.59-6.59,3.54-8.54l22.93-22.93c1.94-1.94,5.13-1.94,7.07,0l22.93,22.93c1.94,1.94,3.54,5.79,3.54,8.54v20Z"/>
|
||||||
|
<g>
|
||||||
|
<rect x="34.34" y="94.34" width="60" height="10" rx="2.5" ry="2.5"/>
|
||||||
|
<rect x="34.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
|
||||||
|
<rect x="84.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path d="M735.34,3c12.32,0,22.34,10.02,22.34,22.34v88c0,12.32-10.02,22.34-22.34,22.34H25.34c-12.32,0-22.34-10.02-22.34-22.34V25.34C3,13.02,13.02,3,25.34,3h710M735.34,0H25.34C11.37,0,0,11.37,0,25.34v88c0,13.98,11.37,25.34,25.34,25.34h710c13.97,0,25.34-11.37,25.34-25.34V25.34c0-13.98-11.37-25.34-25.34-25.34h0Z"/>
|
||||||
|
<g>
|
||||||
|
<path d="M120.98,36.79h2.95v7.26l7.66.02v-7.29h2.97v17.37h-2.97v-7.47l-7.66-.02v7.49h-2.95v-17.37Z"/>
|
||||||
|
<path d="M146.97,36.47c1.63,0,3.09.39,4.37,1.16s2.28,1.84,2.99,3.2,1.06,2.9,1.06,4.61c.02,1.7-.32,3.24-1.04,4.62s-1.72,2.47-3.02,3.25-2.75,1.16-4.36,1.14c-1.62.02-3.08-.36-4.37-1.14s-2.29-1.86-3-3.24-1.05-2.91-1.03-4.61c0-1.27.2-2.47.61-3.58s.99-2.08,1.72-2.88,1.63-1.42,2.68-1.88,2.18-.67,3.39-.66ZM146.99,51.57c1.6,0,2.89-.56,3.85-1.67s1.45-2.6,1.45-4.45-.48-3.32-1.45-4.43-2.25-1.66-3.85-1.66-2.89.55-3.86,1.66-1.45,2.58-1.45,4.43.48,3.34,1.44,4.46,2.25,1.67,3.88,1.67Z"/>
|
||||||
|
<path d="M176.51,36.79v17.37h-2.89v-10.78l-4.29,10.78h-2.81l-4.25-10.71v10.71h-2.84v-17.37h2.84l5.66,13.92,5.69-13.92h2.89Z"/>
|
||||||
|
<path d="M192.41,51.37v2.79h-10.78v-17.37h10.78v2.81h-7.83v4.5h7v2.61h-7v4.66h7.83Z"/>
|
||||||
|
<path d="M213.93,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM213.07,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
|
||||||
|
<path d="M226.96,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||||
|
<path d="M242.38,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||||
|
<path d="M252.68,54.16v-17.37h2.95v17.37h-2.95Z"/>
|
||||||
|
<path d="M265.82,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||||
|
<path d="M287.47,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
|
||||||
|
<path d="M298.87,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM298.01,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
|
||||||
|
<path d="M320.89,36.79v17.37h-2.93l-8.25-12.67v12.67h-2.93v-17.37h2.93l8.25,12.65v-12.65h2.93Z"/>
|
||||||
|
<path d="M337.31,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
|
||||||
|
<path d="M348.75,54.16v-17.14h2.05v17.14h-2.05Z"/>
|
||||||
|
<path d="M360.95,36.72c1.55,0,2.82.38,3.81,1.14,1,.74,1.61,1.72,1.82,2.95l-1.95.52c-.16-.87-.56-1.54-1.23-2.02s-1.5-.71-2.5-.71c-1.08,0-1.95.27-2.6.8s-.97,1.24-.97,2.13c0,1.36.84,2.26,2.52,2.71l2.9.73c1.37.34,2.41.9,3.11,1.68s1.05,1.74,1.05,2.9c0,1.46-.53,2.64-1.6,3.54s-2.49,1.36-4.28,1.36c-1.61,0-2.95-.37-4.03-1.12s-1.72-1.76-1.95-3.06l1.98-.55c.13.88.55,1.56,1.25,2.06s1.63.75,2.77.75,2.12-.26,2.79-.77,1.02-1.22,1.02-2.14c0-1.44-.84-2.36-2.52-2.77l-2.88-.71c-1.38-.34-2.42-.9-3.12-1.69s-1.05-1.75-1.05-2.9c0-1.44.52-2.61,1.56-3.51s2.4-1.35,4.09-1.35Z"/>
|
||||||
|
<path d="M388.35,49.75h-7.54l-1.59,4.4h-2.07l6.25-17.14h2.36l6.31,17.14h-2.15l-1.57-4.4ZM387.73,48.05l-3.09-8.71-3.2,8.71h6.29Z"/>
|
||||||
|
<path d="M415.46,42.47c0,1.6-.5,2.91-1.5,3.95s-2.32,1.56-3.97,1.56h-4.53v6.18h-2.05v-17.14h6.6c1.67,0,3,.49,3.98,1.47s1.47,2.31,1.47,3.98ZM413.31,42.42c0-1.07-.32-1.92-.95-2.56s-1.51-.96-2.64-.96h-4.26v7.24h4.17c1.15,0,2.06-.34,2.71-1.02s.98-1.58.98-2.7Z"/>
|
||||||
|
<path d="M428.37,46.9l3.43,7.26h-2.31l-3.18-6.95h-4.76v6.95h-2.05v-17.14h6.54c1.81,0,3.22.45,4.24,1.35s1.53,2.14,1.53,3.72c0,1.22-.3,2.26-.9,3.1s-1.44,1.41-2.53,1.7ZM429.64,42.12c0-1.01-.32-1.81-.95-2.38s-1.52-.86-2.66-.86h-4.5v6.47h4.53c1.15,0,2.03-.28,2.64-.85s.92-1.36.92-2.38Z"/>
|
||||||
|
<path d="M443.34,36.74c1.18-.02,2.28.2,3.31.65s1.9,1.07,2.62,1.85,1.28,1.73,1.69,2.83.6,2.27.59,3.52c.02,1.67-.33,3.19-1.03,4.54s-1.68,2.42-2.95,3.19-2.68,1.14-4.25,1.12c-1.59,0-3-.38-4.25-1.13s-2.21-1.81-2.89-3.15-1.03-2.87-1.03-4.57c-.02-1.67.32-3.18,1.02-4.53s1.67-2.42,2.93-3.2,2.68-1.15,4.24-1.13ZM443.34,52.45c1.8,0,3.26-.64,4.38-1.91s1.68-2.92,1.68-4.95-.56-3.71-1.68-4.98-2.58-1.9-4.38-1.9-3.28.63-4.4,1.9-1.68,2.93-1.68,4.98.56,3.69,1.68,4.96,2.59,1.9,4.39,1.9Z"/>
|
||||||
|
<path d="M464.3,37.02v12.42c0,1.49-.47,2.71-1.41,3.64s-2.17,1.39-3.71,1.39c-1.56,0-2.76-.46-3.61-1.37s-1.27-2.13-1.27-3.69v-.55h1.98v.55c0,1.18.29,1.99.86,2.45.59.45,1.26.67,2.02.67.93,0,1.68-.26,2.24-.78.57-.52.85-1.32.85-2.39v-12.35h2.05Z"/>
|
||||||
|
<path d="M479.86,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
|
||||||
|
<path d="M496.97,42.42c-.36-1.15-1.01-2.06-1.93-2.71s-2.02-.98-3.3-.98c-1.79,0-3.24.63-4.35,1.89s-1.66,2.92-1.66,4.96.55,3.72,1.66,4.96,2.55,1.86,4.33,1.86c1.27,0,2.39-.31,3.35-.94.95-.63,1.61-1.44,1.98-2.45l1.93.83c-.55,1.41-1.48,2.53-2.78,3.36-1.31.81-2.81,1.22-4.51,1.22-2.4,0-4.35-.81-5.84-2.43s-2.24-3.75-2.24-6.4c0-1.74.34-3.28,1.02-4.62s1.64-2.39,2.88-3.13,2.65-1.11,4.25-1.11c1.8,0,3.33.46,4.59,1.38,1.26.91,2.12,2.1,2.57,3.59l-1.93.71Z"/>
|
||||||
|
<path d="M512.92,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
|
||||||
|
<path d="M536.4,49.66c0,1.39-.49,2.48-1.46,3.29s-2.27,1.21-3.9,1.21h-6.72v-17.12h6.58c1.65,0,2.95.41,3.89,1.24s1.41,1.93,1.41,3.32c0,.92-.21,1.72-.64,2.4s-1.02,1.2-1.79,1.57c.81.37,1.45.91,1.92,1.62s.7,1.53.7,2.47ZM526.3,38.81v5.97h4.54c1.03,0,1.85-.27,2.46-.81s.92-1.24.92-2.09c0-.91-.31-1.65-.93-2.22s-1.45-.85-2.5-.85h-4.5ZM534.45,49.39c0-.91-.32-1.64-.95-2.17s-1.44-.8-2.46-.8h-4.74v5.95h4.74c1.04,0,1.86-.27,2.48-.81s.92-1.26.92-2.16Z"/>
|
||||||
|
<path d="M541.07,37.02l4.54,8.1,4.54-8.1h2.24l-5.76,10.05v7.09h-2.05v-7.09l-5.83-10.05h2.31Z"/>
|
||||||
|
<path d="M574.27,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
|
||||||
|
<path d="M577.95,37.02h2.05v7.55l8.74.02v-7.58h2.05v17.14h-2.05v-7.76l-8.74-.02v7.79h-2.05v-17.14Z"/>
|
||||||
|
<path d="M606.55,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 12 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240604.0"
|
version = "20240809.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@@ -37,8 +37,7 @@
|
|||||||
{
|
{
|
||||||
"description": "Group tsparticles engine and presets",
|
"description": "Group tsparticles engine and presets",
|
||||||
"groupName": "tsparticles",
|
"groupName": "tsparticles",
|
||||||
"matchPackageNames": ["tsparticles-engine"],
|
"matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
|
||||||
"matchPackagePrefixes": ["tsparticles-preset-"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Group date-fns with dependent timezone package",
|
"description": "Group date-fns with dependent timezone package",
|
||||||
@@ -48,8 +47,8 @@
|
|||||||
{
|
{
|
||||||
"description": "Group and temporarily disable WDS packages",
|
"description": "Group and temporarily disable WDS packages",
|
||||||
"groupName": "Web Dev Server",
|
"groupName": "Web Dev Server",
|
||||||
"matchPackagePrefixes": ["@web/dev-server"],
|
"enabled": false,
|
||||||
"enabled": false
|
"matchPackageNames": ["@web/dev-server{/,}**"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ import {
|
|||||||
mdiFormatListBulleted,
|
mdiFormatListBulleted,
|
||||||
mdiFormatListCheckbox,
|
mdiFormatListCheckbox,
|
||||||
mdiFormTextbox,
|
mdiFormTextbox,
|
||||||
|
mdiForumOutline,
|
||||||
mdiGauge,
|
mdiGauge,
|
||||||
mdiGoogleAssistant,
|
mdiGoogleAssistant,
|
||||||
mdiGoogleCirclesCommunities,
|
mdiGoogleCirclesCommunities,
|
||||||
@@ -39,7 +40,6 @@ import {
|
|||||||
mdiImageFilterFrames,
|
mdiImageFilterFrames,
|
||||||
mdiLightbulb,
|
mdiLightbulb,
|
||||||
mdiLightningBolt,
|
mdiLightningBolt,
|
||||||
mdiMailbox,
|
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMeterGas,
|
mdiMeterGas,
|
||||||
mdiMicrophoneMessage,
|
mdiMicrophoneMessage,
|
||||||
@@ -98,7 +98,7 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
calendar: mdiCalendar,
|
calendar: mdiCalendar,
|
||||||
climate: mdiThermostat,
|
climate: mdiThermostat,
|
||||||
configurator: mdiCog,
|
configurator: mdiCog,
|
||||||
conversation: mdiMicrophoneMessage,
|
conversation: mdiForumOutline,
|
||||||
counter: mdiCounter,
|
counter: mdiCounter,
|
||||||
date: mdiCalendar,
|
date: mdiCalendar,
|
||||||
datetime: mdiCalendarClock,
|
datetime: mdiCalendarClock,
|
||||||
@@ -118,7 +118,6 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
input_text: mdiFormTextbox,
|
input_text: mdiFormTextbox,
|
||||||
lawn_mower: mdiRobotMower,
|
lawn_mower: mdiRobotMower,
|
||||||
light: mdiLightbulb,
|
light: mdiLightbulb,
|
||||||
mailbox: mdiMailbox,
|
|
||||||
notify: mdiCommentAlert,
|
notify: mdiCommentAlert,
|
||||||
number: mdiRayVertex,
|
number: mdiRayVertex,
|
||||||
persistent_notification: mdiBell,
|
persistent_notification: mdiBell,
|
||||||
@@ -235,6 +234,8 @@ export const SENSOR_ENTITIES = [
|
|||||||
"weather",
|
"weather",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
|
||||||
|
|
||||||
/** Domains that render an input element instead of a text value when displayed in a row.
|
/** Domains that render an input element instead of a text value when displayed in a row.
|
||||||
* Those rows should then not show a cursor pointer when hovered (which would normally
|
* Those rows should then not show a cursor pointer when hovered (which would normally
|
||||||
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
|
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
|
||||||
|
1
src/common/dom/prevent_default.ts
Normal file
1
src/common/dom/prevent_default.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const preventDefault = (ev) => ev.preventDefault();
|
@@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = {
|
|||||||
humidifier: ["on", "off"],
|
humidifier: ["on", "off"],
|
||||||
input_boolean: ["on", "off"],
|
input_boolean: ["on", "off"],
|
||||||
input_button: [],
|
input_button: [],
|
||||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
|
||||||
light: ["on", "off"],
|
light: ["on", "off"],
|
||||||
lock: [
|
lock: [
|
||||||
"jammed",
|
"jammed",
|
||||||
@@ -125,6 +125,7 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
|||||||
"off",
|
"off",
|
||||||
"idle",
|
"idle",
|
||||||
"preheating",
|
"preheating",
|
||||||
|
"defrosting",
|
||||||
"heating",
|
"heating",
|
||||||
"cooling",
|
"cooling",
|
||||||
"drying",
|
"drying",
|
||||||
|
@@ -25,7 +25,9 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
if (__DEMO__) {
|
if (__DEMO__) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
mainWindow.history.state?.root
|
||||||
|
? { root: true }
|
||||||
|
: (options?.data ?? null),
|
||||||
"",
|
"",
|
||||||
`${mainWindow.location.pathname}#${path}`
|
`${mainWindow.location.pathname}#${path}`
|
||||||
);
|
);
|
||||||
@@ -34,7 +36,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
}
|
}
|
||||||
} else if (replace) {
|
} else if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
mainWindow.history.state?.root ? { root: true } : (options?.data ?? null),
|
||||||
"",
|
"",
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { stripDiacritics } from "../strip-diacritics";
|
||||||
import { fuzzyScore } from "./filter";
|
import { fuzzyScore } from "./filter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,10 +20,10 @@ export const fuzzySequentialMatch = (
|
|||||||
for (const word of item.strings) {
|
for (const word of item.strings) {
|
||||||
const scores = fuzzyScore(
|
const scores = fuzzyScore(
|
||||||
filter,
|
filter,
|
||||||
filter.toLowerCase(),
|
stripDiacritics(filter.toLowerCase()),
|
||||||
0,
|
0,
|
||||||
word,
|
word,
|
||||||
word.toLowerCase(),
|
stripDiacritics(word.toLowerCase()),
|
||||||
0,
|
0,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
2
src/common/string/strip-diacritics.ts
Normal file
2
src/common/string/strip-diacritics.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const stripDiacritics = (str: string) =>
|
||||||
|
str.normalize("NFD").replace(/[\u0300-\u036F]/g, "");
|
@@ -1,5 +1,6 @@
|
|||||||
import { LitElement, TemplateResult, html } from "lit";
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { HassServiceTarget } from "home-assistant-js-websocket";
|
||||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import "./ha-progress-button";
|
import "./ha-progress-button";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
@@ -17,7 +18,9 @@ class HaCallServiceButton extends LitElement {
|
|||||||
|
|
||||||
@property() public service!: string;
|
@property() public service!: string;
|
||||||
|
|
||||||
@property({ type: Object }) public serviceData = {};
|
@property({ type: Object }) public target!: HassServiceTarget;
|
||||||
|
|
||||||
|
@property({ type: Object }) public data = {};
|
||||||
|
|
||||||
@property() public confirmation?;
|
@property() public confirmation?;
|
||||||
|
|
||||||
@@ -39,7 +42,8 @@ class HaCallServiceButton extends LitElement {
|
|||||||
const eventData = {
|
const eventData = {
|
||||||
domain: this.domain,
|
domain: this.domain,
|
||||||
service: this.service,
|
service: this.service,
|
||||||
serviceData: this.serviceData,
|
data: this.data,
|
||||||
|
target: this.target,
|
||||||
success: false,
|
success: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,7 +51,12 @@ class HaCallServiceButton extends LitElement {
|
|||||||
this.shadowRoot!.querySelector("ha-progress-button")!;
|
this.shadowRoot!.querySelector("ha-progress-button")!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.hass.callService(this.domain, this.service, this.serviceData);
|
await this.hass.callService(
|
||||||
|
this.domain,
|
||||||
|
this.service,
|
||||||
|
this.data,
|
||||||
|
this.target
|
||||||
|
);
|
||||||
this.progress = false;
|
this.progress = false;
|
||||||
progressElement.actionSuccess();
|
progressElement.actionSuccess();
|
||||||
eventData.success = true;
|
eventData.success = true;
|
||||||
@@ -85,7 +94,8 @@ declare global {
|
|||||||
"hass-service-called": {
|
"hass-service-called": {
|
||||||
domain: string;
|
domain: string;
|
||||||
service: string;
|
service: string;
|
||||||
serviceData: object;
|
target: HassServiceTarget;
|
||||||
|
data: object;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -159,10 +159,10 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
},
|
},
|
||||||
afterUpdate: (y) => {
|
afterUpdate: (y) => {
|
||||||
const yWidth = this.showNames
|
const yWidth = this.showNames
|
||||||
? y.width ?? 0
|
? (y.width ?? 0)
|
||||||
: computeRTL(this.hass)
|
: computeRTL(this.hass)
|
||||||
? 0
|
? 0
|
||||||
: y.left ?? 0;
|
: (y.left ?? 0);
|
||||||
if (
|
if (
|
||||||
this._yWidth !== Math.floor(yWidth) &&
|
this._yWidth !== Math.floor(yWidth) &&
|
||||||
y.ticks.length === this.data.length
|
y.ticks.length === this.data.length
|
||||||
|
319
src/components/data-table/dialog-data-table-settings.ts
Normal file
319
src/components/data-table/dialog-data-table-settings.ts
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
import "@material/mwc-list";
|
||||||
|
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { createCloseHeading } from "../ha-dialog";
|
||||||
|
import "../ha-list-item";
|
||||||
|
import "../ha-sortable";
|
||||||
|
import "../ha-button";
|
||||||
|
import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table";
|
||||||
|
import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
@customElement("dialog-data-table-settings")
|
||||||
|
export class DialogDataTableSettings extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: DataTableSettingsDialogParams;
|
||||||
|
|
||||||
|
@state() private _columnOrder?: string[];
|
||||||
|
|
||||||
|
@state() private _hiddenColumns?: string[];
|
||||||
|
|
||||||
|
public showDialog(params: DataTableSettingsDialogParams) {
|
||||||
|
this._params = params;
|
||||||
|
this._columnOrder = params.columnOrder;
|
||||||
|
this._hiddenColumns = params.hiddenColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._params = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sortedColumns = memoizeOne(
|
||||||
|
(
|
||||||
|
columns: DataTableColumnContainer,
|
||||||
|
columnOrder: string[] | undefined,
|
||||||
|
hiddenColumns: string[] | undefined
|
||||||
|
) =>
|
||||||
|
Object.keys(columns)
|
||||||
|
.filter((col) => !columns[col].hidden)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const orderA = columnOrder?.indexOf(a) ?? -1;
|
||||||
|
const orderB = columnOrder?.indexOf(b) ?? -1;
|
||||||
|
const hiddenA =
|
||||||
|
hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden);
|
||||||
|
const hiddenB =
|
||||||
|
hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden);
|
||||||
|
if (hiddenA !== hiddenB) {
|
||||||
|
return hiddenA ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (orderA !== orderB) {
|
||||||
|
if (orderA === -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (orderB === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderA - orderB;
|
||||||
|
})
|
||||||
|
.reduce(
|
||||||
|
(arr, key) => {
|
||||||
|
arr.push({ key, ...columns[key] });
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
[] as (DataTableColumnData & { key: string })[]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._params) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localize = this._params.localizeFunc || this.hass.localize;
|
||||||
|
|
||||||
|
const columns = this._sortedColumns(
|
||||||
|
this._params.columns,
|
||||||
|
this._columnOrder,
|
||||||
|
this._hiddenColumns
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
localize("ui.components.data-table.settings.header")
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-sortable
|
||||||
|
@item-moved=${this._columnMoved}
|
||||||
|
draggable-selector=".draggable"
|
||||||
|
handle-selector=".handle"
|
||||||
|
>
|
||||||
|
<mwc-list>
|
||||||
|
${repeat(
|
||||||
|
columns,
|
||||||
|
(col) => col.key,
|
||||||
|
(col, _idx) => {
|
||||||
|
const canMove = !col.main && col.moveable !== false;
|
||||||
|
const canHide = !col.main && col.hideable !== false;
|
||||||
|
const isVisible = !(this._columnOrder &&
|
||||||
|
this._columnOrder.includes(col.key)
|
||||||
|
? (this._hiddenColumns?.includes(col.key) ??
|
||||||
|
col.defaultHidden)
|
||||||
|
: col.defaultHidden);
|
||||||
|
|
||||||
|
return html`<ha-list-item
|
||||||
|
hasMeta
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !isVisible,
|
||||||
|
draggable: canMove && isVisible,
|
||||||
|
})}
|
||||||
|
graphic="icon"
|
||||||
|
noninteractive
|
||||||
|
>${col.title || col.label || col.key}
|
||||||
|
${canMove && isVisible
|
||||||
|
? html`<ha-svg-icon
|
||||||
|
class="handle"
|
||||||
|
.path=${mdiDrag}
|
||||||
|
slot="graphic"
|
||||||
|
></ha-svg-icon>`
|
||||||
|
: nothing}
|
||||||
|
<ha-icon-button
|
||||||
|
tabindex="0"
|
||||||
|
class="action"
|
||||||
|
.disabled=${!canHide}
|
||||||
|
.hidden=${!isVisible}
|
||||||
|
.path=${isVisible ? mdiEye : mdiEyeOff}
|
||||||
|
slot="meta"
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`ui.components.data-table.settings.${isVisible ? "hide" : "show"}`,
|
||||||
|
{ title: typeof col.title === "string" ? col.title : "" }
|
||||||
|
)}
|
||||||
|
.column=${col.key}
|
||||||
|
@click=${this._toggle}
|
||||||
|
></ha-icon-button>
|
||||||
|
</ha-list-item>`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</mwc-list>
|
||||||
|
</ha-sortable>
|
||||||
|
<ha-button slot="secondaryAction" @click=${this._reset}
|
||||||
|
>${localize("ui.components.data-table.settings.restore")}</ha-button
|
||||||
|
>
|
||||||
|
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
||||||
|
${localize("ui.components.data-table.settings.done")}
|
||||||
|
</ha-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _columnMoved(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
|
||||||
|
const columns = this._sortedColumns(
|
||||||
|
this._params.columns,
|
||||||
|
this._columnOrder,
|
||||||
|
this._hiddenColumns
|
||||||
|
);
|
||||||
|
|
||||||
|
const columnOrder = columns.map((column) => column.key);
|
||||||
|
|
||||||
|
const option = columnOrder.splice(oldIndex, 1)[0];
|
||||||
|
columnOrder.splice(newIndex, 0, option);
|
||||||
|
|
||||||
|
this._columnOrder = columnOrder;
|
||||||
|
|
||||||
|
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle(ev) {
|
||||||
|
if (!this._params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const column = ev.target.column;
|
||||||
|
const wasHidden = ev.target.hidden;
|
||||||
|
|
||||||
|
const hidden = [
|
||||||
|
...(this._hiddenColumns ??
|
||||||
|
Object.entries(this._params.columns)
|
||||||
|
.filter(([_key, col]) => col.defaultHidden)
|
||||||
|
.map(([key]) => key)),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (wasHidden && hidden.includes(column)) {
|
||||||
|
hidden.splice(hidden.indexOf(column), 1);
|
||||||
|
} else if (!wasHidden) {
|
||||||
|
hidden.push(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = this._sortedColumns(
|
||||||
|
this._params.columns,
|
||||||
|
this._columnOrder,
|
||||||
|
hidden
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this._columnOrder) {
|
||||||
|
this._columnOrder = columns.map((col) => col.key);
|
||||||
|
} else {
|
||||||
|
const newOrder = this._columnOrder.filter((col) => col !== column);
|
||||||
|
|
||||||
|
// Array.findLastIndex when supported or core-js polyfill
|
||||||
|
const findLastIndex = (
|
||||||
|
arr: Array<any>,
|
||||||
|
fn: (item: any, index: number, arr: Array<any>) => boolean
|
||||||
|
) => {
|
||||||
|
for (let i = arr.length - 1; i >= 0; i--) {
|
||||||
|
if (fn(arr[i], i, arr)) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastMoveable = findLastIndex(
|
||||||
|
newOrder,
|
||||||
|
(col) =>
|
||||||
|
col !== column &&
|
||||||
|
!hidden.includes(col) &&
|
||||||
|
!this._params!.columns[col].main &&
|
||||||
|
this._params!.columns[col].moveable !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastMoveable === -1) {
|
||||||
|
lastMoveable = newOrder.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.forEach((col) => {
|
||||||
|
if (!newOrder.includes(col.key)) {
|
||||||
|
if (col.moveable === false) {
|
||||||
|
newOrder.unshift(col.key);
|
||||||
|
} else {
|
||||||
|
newOrder.splice(lastMoveable + 1, 0, col.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
col.key !== column &&
|
||||||
|
col.defaultHidden &&
|
||||||
|
!hidden.includes(col.key)
|
||||||
|
) {
|
||||||
|
hidden.push(col.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._columnOrder = newOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._hiddenColumns = hidden;
|
||||||
|
|
||||||
|
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
_reset() {
|
||||||
|
this._columnOrder = undefined;
|
||||||
|
this._hiddenColumns = undefined;
|
||||||
|
|
||||||
|
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 500px;
|
||||||
|
--dialog-z-index: 10;
|
||||||
|
--dialog-content-padding: 0 8px;
|
||||||
|
}
|
||||||
|
@media all and (max-width: 451px) {
|
||||||
|
ha-dialog {
|
||||||
|
--vertical-align-dialog: flex-start;
|
||||||
|
--dialog-surface-margin-top: 250px;
|
||||||
|
--ha-dialog-border-radius: 28px 28px 0 0;
|
||||||
|
--mdc-dialog-min-height: calc(100% - 250px);
|
||||||
|
--mdc-dialog-max-height: calc(100% - 250px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ha-list-item {
|
||||||
|
--mdc-list-side-padding: 12px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
|
.handle {
|
||||||
|
cursor: move; /* fallback if grab cursor is unsupported */
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
display: block;
|
||||||
|
margin: -12px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-data-table-settings": DialogDataTableSettings;
|
||||||
|
}
|
||||||
|
}
|
@@ -34,6 +34,7 @@ import type { HaCheckbox } from "../ha-checkbox";
|
|||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "../search-input";
|
import "../search-input";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
|
import { LocalizeFunc } from "../../common/translations/localize";
|
||||||
|
|
||||||
export interface RowClickedEvent {
|
export interface RowClickedEvent {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -65,6 +66,10 @@ export interface DataTableSortColumnData {
|
|||||||
valueColumn?: string;
|
valueColumn?: string;
|
||||||
direction?: SortingDirection;
|
direction?: SortingDirection;
|
||||||
groupable?: boolean;
|
groupable?: boolean;
|
||||||
|
moveable?: boolean;
|
||||||
|
hideable?: boolean;
|
||||||
|
defaultHidden?: boolean;
|
||||||
|
showNarrow?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||||
@@ -79,9 +84,10 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
|||||||
| "overflow-menu"
|
| "overflow-menu"
|
||||||
| "flex";
|
| "flex";
|
||||||
template?: (row: T) => TemplateResult | string | typeof nothing;
|
template?: (row: T) => TemplateResult | string | typeof nothing;
|
||||||
width?: string;
|
extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
|
||||||
|
minWidth?: string;
|
||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
grows?: boolean;
|
flex?: number;
|
||||||
forceLTR?: boolean;
|
forceLTR?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
@@ -105,6 +111,10 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
|
|||||||
export class HaDataTable extends LitElement {
|
export class HaDataTable extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public localizeFunc?: LocalizeFunc;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||||
|
|
||||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||||
@@ -145,6 +155,10 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public hiddenColumns?: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public columnOrder?: string[];
|
||||||
|
|
||||||
@state() private _filterable = false;
|
@state() private _filterable = false;
|
||||||
|
|
||||||
@state() private _filter = "";
|
@state() private _filter = "";
|
||||||
@@ -202,6 +216,18 @@ export class HaDataTable extends LitElement {
|
|||||||
this.updateComplete.then(() => this._calcTableHeight());
|
this.updateComplete.then(() => this._calcTableHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated() {
|
||||||
|
const header = this.renderRoot.querySelector(".mdc-data-table__header-row");
|
||||||
|
if (!header) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (header.scrollWidth > header.clientWidth) {
|
||||||
|
this.style.setProperty("--table-row-width", `${header.scrollWidth}px`);
|
||||||
|
} else {
|
||||||
|
this.style.removeProperty("--table-row-width");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public willUpdate(properties: PropertyValues) {
|
public willUpdate(properties: PropertyValues) {
|
||||||
super.willUpdate(properties);
|
super.willUpdate(properties);
|
||||||
|
|
||||||
@@ -235,6 +261,7 @@ export class HaDataTable extends LitElement {
|
|||||||
(column: ClonedDataTableColumnData) => {
|
(column: ClonedDataTableColumnData) => {
|
||||||
delete column.title;
|
delete column.title;
|
||||||
delete column.template;
|
delete column.template;
|
||||||
|
delete column.extraTemplate;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -272,12 +299,46 @@ export class HaDataTable extends LitElement {
|
|||||||
this._sortFilterData();
|
this._sortFilterData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.has("selectable")) {
|
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
||||||
this._items = [...this._items];
|
this._items = [...this._items];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _sortedColumns = memoizeOne(
|
||||||
|
(columns: DataTableColumnContainer, columnOrder?: string[]) => {
|
||||||
|
if (!columnOrder || !columnOrder.length) {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(columns)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const orderA = columnOrder!.indexOf(a);
|
||||||
|
const orderB = columnOrder!.indexOf(b);
|
||||||
|
if (orderA !== orderB) {
|
||||||
|
if (orderA === -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (orderB === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderA - orderB;
|
||||||
|
})
|
||||||
|
.reduce((obj, key) => {
|
||||||
|
obj[key] = columns[key];
|
||||||
|
return obj;
|
||||||
|
}, {}) as DataTableColumnContainer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
const localize = this.localizeFunc || this.hass.localize;
|
||||||
|
|
||||||
|
const columns = this._sortedColumns(this.columns, this.columnOrder);
|
||||||
|
|
||||||
|
const renderRow = (row: DataTableRowData, index: number) =>
|
||||||
|
this._renderRow(columns, this.narrow, row, index);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="mdc-data-table">
|
<div class="mdc-data-table">
|
||||||
<slot name="header" @slotchange=${this._calcTableHeight}>
|
<slot name="header" @slotchange=${this._calcTableHeight}>
|
||||||
@@ -306,7 +367,12 @@ export class HaDataTable extends LitElement {
|
|||||||
: `calc(100% - ${this._headerHeight}px)`,
|
: `calc(100% - ${this._headerHeight}px)`,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div class="mdc-data-table__header-row" role="row" aria-rowindex="1">
|
<div
|
||||||
|
class="mdc-data-table__header-row"
|
||||||
|
role="row"
|
||||||
|
aria-rowindex="1"
|
||||||
|
@scroll=${this._scrollContent}
|
||||||
|
>
|
||||||
<slot name="header-row">
|
<slot name="header-row">
|
||||||
${this.selectable
|
${this.selectable
|
||||||
? html`
|
? html`
|
||||||
@@ -326,9 +392,15 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${Object.entries(this.columns).map(([key, column]) => {
|
${Object.entries(columns).map(([key, column]) => {
|
||||||
if (column.hidden) {
|
if (
|
||||||
return "";
|
column.hidden ||
|
||||||
|
(this.columnOrder && this.columnOrder.includes(key)
|
||||||
|
? (this.hiddenColumns?.includes(key) ??
|
||||||
|
column.defaultHidden)
|
||||||
|
: column.defaultHidden)
|
||||||
|
) {
|
||||||
|
return nothing;
|
||||||
}
|
}
|
||||||
const sorted = key === this.sortColumn;
|
const sorted = key === this.sortColumn;
|
||||||
const classes = {
|
const classes = {
|
||||||
@@ -343,18 +415,16 @@ export class HaDataTable extends LitElement {
|
|||||||
column.type === "overflow",
|
column.type === "overflow",
|
||||||
sortable: Boolean(column.sortable),
|
sortable: Boolean(column.sortable),
|
||||||
"not-sorted": Boolean(column.sortable && !sorted),
|
"not-sorted": Boolean(column.sortable && !sorted),
|
||||||
grows: Boolean(column.grows),
|
|
||||||
};
|
};
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
aria-label=${ifDefined(column.label)}
|
aria-label=${ifDefined(column.label)}
|
||||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||||
style=${column.width
|
style=${styleMap({
|
||||||
? styleMap({
|
minWidth: column.minWidth,
|
||||||
[column.grows ? "minWidth" : "width"]: column.width,
|
maxWidth: column.maxWidth,
|
||||||
maxWidth: column.maxWidth || "",
|
flex: column.flex || 1,
|
||||||
})
|
})}
|
||||||
: ""}
|
|
||||||
role="columnheader"
|
role="columnheader"
|
||||||
aria-sort=${ifDefined(
|
aria-sort=${ifDefined(
|
||||||
sorted
|
sorted
|
||||||
@@ -387,7 +457,7 @@ export class HaDataTable extends LitElement {
|
|||||||
<div class="mdc-data-table__row" role="row">
|
<div class="mdc-data-table__row" role="row">
|
||||||
<div class="mdc-data-table__cell grows center" role="cell">
|
<div class="mdc-data-table__cell grows center" role="cell">
|
||||||
${this.noDataText ||
|
${this.noDataText ||
|
||||||
this.hass.localize("ui.components.data-table.no-data")}
|
localize("ui.components.data-table.no-data")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -399,7 +469,7 @@ export class HaDataTable extends LitElement {
|
|||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
.items=${this._items}
|
.items=${this._items}
|
||||||
.keyFunction=${this._keyFunction}
|
.keyFunction=${this._keyFunction}
|
||||||
.renderItem=${this._renderRow}
|
.renderItem=${renderRow}
|
||||||
></lit-virtualizer>
|
></lit-virtualizer>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@@ -409,7 +479,12 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
||||||
|
|
||||||
private _renderRow = (row: DataTableRowData, index: number) => {
|
private _renderRow = (
|
||||||
|
columns: DataTableColumnContainer,
|
||||||
|
narrow: boolean,
|
||||||
|
row: DataTableRowData,
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
// not sure how this happens...
|
// not sure how this happens...
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -454,8 +529,14 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${Object.entries(this.columns).map(([key, column]) => {
|
${Object.entries(columns).map(([key, column]) => {
|
||||||
if (column.hidden) {
|
if (
|
||||||
|
(narrow && !column.main && !column.showNarrow) ||
|
||||||
|
column.hidden ||
|
||||||
|
(this.columnOrder && this.columnOrder.includes(key)
|
||||||
|
? (this.hiddenColumns?.includes(key) ?? column.defaultHidden)
|
||||||
|
: column.defaultHidden)
|
||||||
|
) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
@@ -472,17 +553,46 @@ export class HaDataTable extends LitElement {
|
|||||||
"mdc-data-table__cell--overflow-menu":
|
"mdc-data-table__cell--overflow-menu":
|
||||||
column.type === "overflow-menu",
|
column.type === "overflow-menu",
|
||||||
"mdc-data-table__cell--overflow": column.type === "overflow",
|
"mdc-data-table__cell--overflow": column.type === "overflow",
|
||||||
grows: Boolean(column.grows),
|
|
||||||
forceLTR: Boolean(column.forceLTR),
|
forceLTR: Boolean(column.forceLTR),
|
||||||
})}"
|
})}"
|
||||||
style=${column.width
|
style=${styleMap({
|
||||||
? styleMap({
|
minWidth: column.minWidth,
|
||||||
[column.grows ? "minWidth" : "width"]: column.width,
|
maxWidth: column.maxWidth,
|
||||||
maxWidth: column.maxWidth ? column.maxWidth : "",
|
flex: column.flex || 1,
|
||||||
})
|
})}
|
||||||
: ""}
|
|
||||||
>
|
>
|
||||||
${column.template ? column.template(row) : row[key]}
|
${column.template
|
||||||
|
? column.template(row)
|
||||||
|
: narrow && column.main
|
||||||
|
? html`<div class="primary">${row[key]}</div>
|
||||||
|
<div class="secondary">
|
||||||
|
${Object.entries(columns)
|
||||||
|
.filter(
|
||||||
|
([key2, column2]) =>
|
||||||
|
!column2.hidden &&
|
||||||
|
!column2.main &&
|
||||||
|
!column2.showNarrow &&
|
||||||
|
!(this.columnOrder &&
|
||||||
|
this.columnOrder.includes(key2)
|
||||||
|
? (this.hiddenColumns?.includes(key2) ??
|
||||||
|
column2.defaultHidden)
|
||||||
|
: column2.defaultHidden)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
([key2, column2], i) =>
|
||||||
|
html`${i !== 0
|
||||||
|
? " ⸱ "
|
||||||
|
: nothing}${column2.template
|
||||||
|
? column2.template(row)
|
||||||
|
: row[key2]}`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
${column.extraTemplate
|
||||||
|
? column.extraTemplate(row)
|
||||||
|
: nothing}`
|
||||||
|
: html`${row[key]}${column.extraTemplate
|
||||||
|
? column.extraTemplate(row)
|
||||||
|
: nothing}`}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
@@ -500,7 +610,7 @@ export class HaDataTable extends LitElement {
|
|||||||
filteredData = await this._memFilterData(
|
filteredData = await this._memFilterData(
|
||||||
this.data,
|
this.data,
|
||||||
this._sortColumns,
|
this._sortColumns,
|
||||||
this._filter
|
this._filter.trim()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +638,8 @@ export class HaDataTable extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localize = this.localizeFunc || this.hass.localize;
|
||||||
|
|
||||||
if (this.appendRow || this.hasFab || this.groupColumn) {
|
if (this.appendRow || this.hasFab || this.groupColumn) {
|
||||||
let items = [...data];
|
let items = [...data];
|
||||||
|
|
||||||
@@ -581,7 +693,7 @@ export class HaDataTable extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
${groupName === UNDEFINED_GROUP_KEY
|
${groupName === UNDEFINED_GROUP_KEY
|
||||||
? this.hass.localize("ui.components.data-table.ungrouped")
|
? localize("ui.components.data-table.ungrouped")
|
||||||
: groupName || ""}
|
: groupName || ""}
|
||||||
</div>`,
|
</div>`,
|
||||||
});
|
});
|
||||||
@@ -716,6 +828,17 @@ export class HaDataTable extends LitElement {
|
|||||||
@eventOptions({ passive: true })
|
@eventOptions({ passive: true })
|
||||||
private _saveScrollPos(e: Event) {
|
private _saveScrollPos(e: Event) {
|
||||||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||||
|
|
||||||
|
this.renderRoot.querySelector(".mdc-data-table__header-row")!.scrollLeft = (
|
||||||
|
e.target as HTMLDivElement
|
||||||
|
).scrollLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
@eventOptions({ passive: true })
|
||||||
|
private _scrollContent(e: Event) {
|
||||||
|
this.renderRoot.querySelector("lit-virtualizer")!.scrollLeft = (
|
||||||
|
e.target as HTMLDivElement
|
||||||
|
).scrollLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _collapseGroup = (ev: Event) => {
|
private _collapseGroup = (ev: Event) => {
|
||||||
@@ -790,8 +913,8 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
.mdc-data-table__row {
|
.mdc-data-table__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
|
||||||
height: var(--data-table-row-height, 52px);
|
height: var(--data-table-row-height, 52px);
|
||||||
|
width: var(--table-row-width, 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__row ~ .mdc-data-table__row {
|
.mdc-data-table__row ~ .mdc-data-table__row {
|
||||||
@@ -815,18 +938,26 @@ export class HaDataTable extends LitElement {
|
|||||||
.mdc-data-table__header-row {
|
.mdc-data-table__header-row {
|
||||||
height: 56px;
|
height: 56px;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
.mdc-data-table__header-row::-webkit-scrollbar {
|
.mdc-data-table__header-row::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.mdc-data-table__header-row {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell,
|
.mdc-data-table__cell,
|
||||||
.mdc-data-table__header-cell {
|
.mdc-data-table__header-cell {
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
min-width: 150px;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -861,6 +992,7 @@ export class HaDataTable extends LitElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell {
|
.mdc-data-table__cell {
|
||||||
@@ -873,6 +1005,8 @@ export class HaDataTable extends LitElement {
|
|||||||
letter-spacing: 0.0178571429em;
|
letter-spacing: 0.0178571429em;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
text-transform: inherit;
|
text-transform: inherit;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell a {
|
.mdc-data-table__cell a {
|
||||||
@@ -891,7 +1025,8 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
.mdc-data-table__header-cell--icon,
|
.mdc-data-table__header-cell--icon,
|
||||||
.mdc-data-table__cell--icon {
|
.mdc-data-table__cell--icon {
|
||||||
width: 54px;
|
min-width: 64px;
|
||||||
|
flex: 0 0 64px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell--icon img {
|
.mdc-data-table__cell--icon img {
|
||||||
@@ -931,11 +1066,14 @@ export class HaDataTable extends LitElement {
|
|||||||
.mdc-data-table__header-cell--overflow-menu,
|
.mdc-data-table__header-cell--overflow-menu,
|
||||||
.mdc-data-table__header-cell--icon-button,
|
.mdc-data-table__header-cell--icon-button,
|
||||||
.mdc-data-table__cell--icon-button {
|
.mdc-data-table__cell--icon-button {
|
||||||
|
min-width: 64px;
|
||||||
|
flex: 0 0 64px !important;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__header-cell--icon-button,
|
.mdc-data-table__header-cell--icon-button,
|
||||||
.mdc-data-table__cell--icon-button {
|
.mdc-data-table__cell--icon-button {
|
||||||
|
min-width: 56px;
|
||||||
width: 56px;
|
width: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/components/data-table/show-dialog-data-table-settings.ts
Normal file
28
src/components/data-table/show-dialog-data-table-settings.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { LocalizeFunc } from "../../common/translations/localize";
|
||||||
|
import { DataTableColumnContainer } from "./ha-data-table";
|
||||||
|
|
||||||
|
export interface DataTableSettingsDialogParams {
|
||||||
|
columns: DataTableColumnContainer;
|
||||||
|
onUpdate: (
|
||||||
|
columnOrder: string[] | undefined,
|
||||||
|
hiddenColumns: string[] | undefined
|
||||||
|
) => void;
|
||||||
|
hiddenColumns?: string[];
|
||||||
|
columnOrder?: string[];
|
||||||
|
localizeFunc?: LocalizeFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadDataTableSettingsDialog = () =>
|
||||||
|
import("./dialog-data-table-settings");
|
||||||
|
|
||||||
|
export const showDataTableSettingsDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: DataTableSettingsDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-data-table-settings",
|
||||||
|
dialogImport: loadDataTableSettingsDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@@ -1,5 +1,6 @@
|
|||||||
import { expose } from "comlink";
|
import { expose } from "comlink";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
|
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
||||||
import type {
|
import type {
|
||||||
ClonedDataTableColumnData,
|
ClonedDataTableColumnData,
|
||||||
DataTableRowData,
|
DataTableRowData,
|
||||||
@@ -12,20 +13,18 @@ const filterData = (
|
|||||||
columns: SortableColumnContainer,
|
columns: SortableColumnContainer,
|
||||||
filter: string
|
filter: string
|
||||||
) => {
|
) => {
|
||||||
filter = filter.toUpperCase();
|
filter = stripDiacritics(filter.toLowerCase());
|
||||||
return data.filter((row) =>
|
return data.filter((row) =>
|
||||||
Object.entries(columns).some((columnEntry) => {
|
Object.entries(columns).some((columnEntry) => {
|
||||||
const [key, column] = columnEntry;
|
const [key, column] = columnEntry;
|
||||||
if (column.filterable) {
|
if (column.filterable) {
|
||||||
if (
|
const value = String(
|
||||||
String(
|
column.filterKey
|
||||||
column.filterKey
|
? row[column.valueColumn || key][column.filterKey]
|
||||||
? row[column.valueColumn || key][column.filterKey]
|
: row[column.valueColumn || key]
|
||||||
: row[column.valueColumn || key]
|
);
|
||||||
)
|
|
||||||
.toUpperCase()
|
if (stripDiacritics(value).toLowerCase().includes(filter)) {
|
||||||
.includes(filter)
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
330
src/components/entity/ha-entity-state-content-picker.ts
Normal file
330
src/components/entity/ha-entity-state-content-picker.ts
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import { mdiDrag } from "@mdi/js";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
|
import {
|
||||||
|
STATE_DISPLAY_SPECIAL_CONTENT,
|
||||||
|
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS,
|
||||||
|
} from "../../state-display/state-display";
|
||||||
|
import { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||||
|
import "../ha-combo-box";
|
||||||
|
import type { HaComboBox } from "../ha-combo-box";
|
||||||
|
|
||||||
|
const HIDDEN_ATTRIBUTES = [
|
||||||
|
"access_token",
|
||||||
|
"available_modes",
|
||||||
|
"battery_icon",
|
||||||
|
"battery_level",
|
||||||
|
"code_arm_required",
|
||||||
|
"code_format",
|
||||||
|
"color_modes",
|
||||||
|
"device_class",
|
||||||
|
"editable",
|
||||||
|
"effect_list",
|
||||||
|
"entity_id",
|
||||||
|
"entity_picture",
|
||||||
|
"event_types",
|
||||||
|
"fan_modes",
|
||||||
|
"fan_speed_list",
|
||||||
|
"friendly_name",
|
||||||
|
"frontend_stream_type",
|
||||||
|
"has_date",
|
||||||
|
"has_time",
|
||||||
|
"hvac_modes",
|
||||||
|
"icon",
|
||||||
|
"id",
|
||||||
|
"max_color_temp_kelvin",
|
||||||
|
"max_mireds",
|
||||||
|
"max_temp",
|
||||||
|
"max",
|
||||||
|
"min_color_temp_kelvin",
|
||||||
|
"min_mireds",
|
||||||
|
"min_temp",
|
||||||
|
"min",
|
||||||
|
"mode",
|
||||||
|
"operation_list",
|
||||||
|
"options",
|
||||||
|
"percentage_step",
|
||||||
|
"precipitation_unit",
|
||||||
|
"preset_modes",
|
||||||
|
"pressure_unit",
|
||||||
|
"remaining",
|
||||||
|
"sound_mode_list",
|
||||||
|
"source_list",
|
||||||
|
"state_class",
|
||||||
|
"step",
|
||||||
|
"supported_color_modes",
|
||||||
|
"supported_features",
|
||||||
|
"swing_modes",
|
||||||
|
"target_temp_step",
|
||||||
|
"temperature_unit",
|
||||||
|
"token",
|
||||||
|
"unit_of_measurement",
|
||||||
|
"visibility_unit",
|
||||||
|
"wind_speed_unit",
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("ha-entity-state-content-picker")
|
||||||
|
class HaEntityStatePicker extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public entityId?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "allow-name" }) public allowName =
|
||||||
|
false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public value?: string[] | string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@state() private _opened = false;
|
||||||
|
|
||||||
|
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||||
|
|
||||||
|
protected shouldUpdate(changedProps: PropertyValues) {
|
||||||
|
return !(!changedProps.has("_opened") && this._opened);
|
||||||
|
}
|
||||||
|
|
||||||
|
private options = memoizeOne(
|
||||||
|
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||||
|
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||||
|
value: "state",
|
||||||
|
},
|
||||||
|
...(allowName
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: this.hass.localize(
|
||||||
|
"ui.components.state-content-picker.name"
|
||||||
|
),
|
||||||
|
value: "name",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
label: this.hass.localize(
|
||||||
|
"ui.components.state-content-picker.last_changed"
|
||||||
|
),
|
||||||
|
value: "last_changed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.hass.localize(
|
||||||
|
"ui.components.state-content-picker.last_updated"
|
||||||
|
),
|
||||||
|
value: "last_updated",
|
||||||
|
},
|
||||||
|
...(domain
|
||||||
|
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||||
|
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||||
|
).map((content) => ({
|
||||||
|
label: this.hass.localize(
|
||||||
|
`ui.components.state-content-picker.${content}`
|
||||||
|
),
|
||||||
|
value: content,
|
||||||
|
}))
|
||||||
|
: []),
|
||||||
|
...Object.keys(stateObj?.attributes ?? {})
|
||||||
|
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||||
|
.map((attribute) => ({
|
||||||
|
value: attribute,
|
||||||
|
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _filter = "";
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = this._value;
|
||||||
|
|
||||||
|
const stateObj = this.entityId
|
||||||
|
? this.hass.states[this.entityId]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const options = this.options(this.entityId, stateObj, this.allowName);
|
||||||
|
const optionItems = options.filter(
|
||||||
|
(option) => !this._value.includes(option.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${value?.length
|
||||||
|
? html`
|
||||||
|
<ha-sortable
|
||||||
|
no-style
|
||||||
|
@item-moved=${this._moveItem}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
<ha-chip-set>
|
||||||
|
${repeat(
|
||||||
|
this._value,
|
||||||
|
(item) => item,
|
||||||
|
(item, idx) => {
|
||||||
|
const label =
|
||||||
|
options.find((option) => option.value === item)?.label ||
|
||||||
|
item;
|
||||||
|
return html`
|
||||||
|
<ha-input-chip
|
||||||
|
.idx=${idx}
|
||||||
|
@remove=${this._removeItem}
|
||||||
|
.label=${label}
|
||||||
|
selected
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${mdiDrag}
|
||||||
|
data-handle
|
||||||
|
></ha-svg-icon>
|
||||||
|
|
||||||
|
${label}
|
||||||
|
</ha-input-chip>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</ha-chip-set>
|
||||||
|
</ha-sortable>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<ha-combo-box
|
||||||
|
item-value-path="value"
|
||||||
|
item-label-path="label"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required && !value.length}
|
||||||
|
.value=${""}
|
||||||
|
.items=${optionItems}
|
||||||
|
allow-custom-value
|
||||||
|
@filter-changed=${this._filterChanged}
|
||||||
|
@value-changed=${this._comboBoxValueChanged}
|
||||||
|
@opened-changed=${this._openedChanged}
|
||||||
|
></ha-combo-box>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _value() {
|
||||||
|
return !this.value ? [] : ensureArray(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||||
|
this._opened = ev.detail.value;
|
||||||
|
this._comboBox.filteredItems = this._comboBox.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterChanged(ev?: CustomEvent): void {
|
||||||
|
this._filter = ev?.detail.value || "";
|
||||||
|
|
||||||
|
const filteredItems = this._comboBox.items?.filter((item) => {
|
||||||
|
const label = item.label || item.value;
|
||||||
|
return label.toLowerCase().includes(this._filter?.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._filter) {
|
||||||
|
filteredItems?.unshift({ label: this._filter, value: this._filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
this._comboBox.filteredItems = filteredItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _moveItem(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const { oldIndex, newIndex } = ev.detail;
|
||||||
|
const value = this._value;
|
||||||
|
const newValue = value.concat();
|
||||||
|
const element = newValue.splice(oldIndex, 1)[0];
|
||||||
|
newValue.splice(newIndex, 0, element);
|
||||||
|
this._setValue(newValue);
|
||||||
|
await this.updateComplete;
|
||||||
|
this._filterChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeItem(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const value: string[] = [...this._value];
|
||||||
|
value.splice(ev.target.idx, 1);
|
||||||
|
this._setValue(value);
|
||||||
|
await this.updateComplete;
|
||||||
|
this._filterChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _comboBoxValueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const newValue = ev.detail.value;
|
||||||
|
|
||||||
|
if (this.disabled || newValue === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentValue = this._value;
|
||||||
|
|
||||||
|
if (currentValue.includes(newValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this._filterChanged();
|
||||||
|
this._comboBox.setInputValue("");
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
this._setValue([...currentValue, newValue]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setValue(value: string[]) {
|
||||||
|
const newValue =
|
||||||
|
value.length === 0 ? undefined : value.length === 1 ? value[0] : value;
|
||||||
|
this.value = newValue;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-chip-set {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-fallback {
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-ghost {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-drag {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-entity-state-content-picker": HaEntityStatePicker;
|
||||||
|
}
|
||||||
|
}
|
@@ -134,7 +134,7 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
this._timerTimeRemaining
|
this._timerTimeRemaining
|
||||||
)}
|
)}
|
||||||
.description=${this.showName
|
.description=${this.showName
|
||||||
? this.name ?? computeStateName(entityState)
|
? (this.name ?? computeStateName(entityState))
|
||||||
: undefined}
|
: undefined}
|
||||||
>
|
>
|
||||||
${!image && showIcon
|
${!image && showIcon
|
||||||
|
@@ -90,7 +90,8 @@ class HaAnsiToHtml extends LitElement {
|
|||||||
|
|
||||||
private _parseTextToColoredPre(text) {
|
private _parseTextToColoredPre(text) {
|
||||||
const pre = document.createElement("pre");
|
const pre = document.createElement("pre");
|
||||||
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
const state: State = {
|
const state: State = {
|
||||||
|
@@ -279,6 +279,8 @@ export class HaAreaPicker extends LitElement {
|
|||||||
icon: null,
|
icon: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -295,6 +297,8 @@ export class HaAreaPicker extends LitElement {
|
|||||||
icon: "mdi:plus",
|
icon: "mdi:plus",
|
||||||
aliases: [],
|
aliases: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -377,6 +381,8 @@ export class HaAreaPicker extends LitElement {
|
|||||||
picture: null,
|
picture: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
] as AreaRegistryEntry[];
|
] as AreaRegistryEntry[];
|
||||||
} else {
|
} else {
|
||||||
@@ -393,6 +399,8 @@ export class HaAreaPicker extends LitElement {
|
|||||||
picture: null,
|
picture: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
] as AreaRegistryEntry[];
|
] as AreaRegistryEntry[];
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import "./ha-select";
|
import "./ha-select";
|
||||||
|
import "./ha-icon-button";
|
||||||
import { HaTextField } from "./ha-textfield";
|
import { HaTextField } from "./ha-textfield";
|
||||||
import "./ha-input-helper-text";
|
import "./ha-input-helper-text";
|
||||||
|
|
||||||
@@ -124,116 +126,128 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
*/
|
*/
|
||||||
@property() amPm: "AM" | "PM" = "AM";
|
@property() amPm: "AM" | "PM" = "AM";
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.label
|
${this.label
|
||||||
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="time-input-wrap">
|
<div class="time-input-wrap-wrap">
|
||||||
${this.enableDay
|
<div class="time-input-wrap">
|
||||||
? html`
|
${this.enableDay
|
||||||
<ha-textfield
|
? html`
|
||||||
id="day"
|
<ha-textfield
|
||||||
|
id="day"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this.days.toFixed()}
|
||||||
|
.label=${this.dayLabel}
|
||||||
|
name="days"
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
@focusin=${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"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this.hours.toFixed()}
|
||||||
|
.label=${this.hourLabel}
|
||||||
|
name="hours"
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
@focusin=${this._onFocus}
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max=${ifDefined(this._hourMax)}
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
suffix=":"
|
||||||
|
class="hasSuffix"
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
id="min"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.minutes)}
|
||||||
|
.label=${this.minLabel}
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
@focusin=${this._onFocus}
|
||||||
|
name="minutes"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableSecond ? ":" : ""}
|
||||||
|
class=${this.enableSecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
${this.enableSecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="sec"
|
||||||
type="number"
|
type="number"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
.value=${this.days.toFixed()}
|
.value=${this._formatValue(this.seconds)}
|
||||||
.label=${this.dayLabel}
|
.label=${this.secLabel}
|
||||||
name="days"
|
|
||||||
@change=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
@focusin=${this._onFocus}
|
@focusin=${this._onFocus}
|
||||||
|
name="seconds"
|
||||||
no-spinner
|
no-spinner
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.autoValidate=${this.autoValidate}
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
min="0"
|
min="0"
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
suffix=":"
|
.suffix=${this.enableMillisecond ? ":" : ""}
|
||||||
class="hasSuffix"
|
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||||
>
|
>
|
||||||
</ha-textfield>
|
</ha-textfield>`
|
||||||
`
|
: ""}
|
||||||
: ""}
|
${this.enableMillisecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="millisec"
|
||||||
|
type="number"
|
||||||
|
.value=${this._formatValue(this.milliseconds, 3)}
|
||||||
|
.label=${this.millisecLabel}
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
@focusin=${this._onFocus}
|
||||||
|
name="milliseconds"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="3"
|
||||||
|
max="999"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.clearable && !this.required && !this.disabled
|
||||||
|
? html`<ha-icon-button
|
||||||
|
label="clear"
|
||||||
|
@click=${this._clearValue}
|
||||||
|
.path=${mdiClose}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
|
||||||
<ha-textfield
|
|
||||||
id="hour"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
.value=${this.hours.toFixed()}
|
|
||||||
.label=${this.hourLabel}
|
|
||||||
name="hours"
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
@focusin=${this._onFocus}
|
|
||||||
no-spinner
|
|
||||||
.required=${this.required}
|
|
||||||
.autoValidate=${this.autoValidate}
|
|
||||||
maxlength="2"
|
|
||||||
max=${ifDefined(this._hourMax)}
|
|
||||||
min="0"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
suffix=":"
|
|
||||||
class="hasSuffix"
|
|
||||||
>
|
|
||||||
</ha-textfield>
|
|
||||||
<ha-textfield
|
|
||||||
id="min"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
.value=${this._formatValue(this.minutes)}
|
|
||||||
.label=${this.minLabel}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
@focusin=${this._onFocus}
|
|
||||||
name="minutes"
|
|
||||||
no-spinner
|
|
||||||
.required=${this.required}
|
|
||||||
.autoValidate=${this.autoValidate}
|
|
||||||
maxlength="2"
|
|
||||||
max="59"
|
|
||||||
min="0"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.suffix=${this.enableSecond ? ":" : ""}
|
|
||||||
class=${this.enableSecond ? "has-suffix" : ""}
|
|
||||||
>
|
|
||||||
</ha-textfield>
|
|
||||||
${this.enableSecond
|
|
||||||
? html`<ha-textfield
|
|
||||||
id="sec"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
.value=${this._formatValue(this.seconds)}
|
|
||||||
.label=${this.secLabel}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
@focusin=${this._onFocus}
|
|
||||||
name="seconds"
|
|
||||||
no-spinner
|
|
||||||
.required=${this.required}
|
|
||||||
.autoValidate=${this.autoValidate}
|
|
||||||
maxlength="2"
|
|
||||||
max="59"
|
|
||||||
min="0"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.suffix=${this.enableMillisecond ? ":" : ""}
|
|
||||||
class=${this.enableMillisecond ? "has-suffix" : ""}
|
|
||||||
>
|
|
||||||
</ha-textfield>`
|
|
||||||
: ""}
|
|
||||||
${this.enableMillisecond
|
|
||||||
? html`<ha-textfield
|
|
||||||
id="millisec"
|
|
||||||
type="number"
|
|
||||||
.value=${this._formatValue(this.milliseconds, 3)}
|
|
||||||
.label=${this.millisecLabel}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
@focusin=${this._onFocus}
|
|
||||||
name="milliseconds"
|
|
||||||
no-spinner
|
|
||||||
.required=${this.required}
|
|
||||||
.autoValidate=${this.autoValidate}
|
|
||||||
maxlength="3"
|
|
||||||
max="999"
|
|
||||||
min="0"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
>
|
|
||||||
</ha-textfield>`
|
|
||||||
: ""}
|
|
||||||
${this.format === 24
|
${this.format === 24
|
||||||
? ""
|
? ""
|
||||||
: html`<ha-select
|
: html`<ha-select
|
||||||
@@ -249,13 +263,17 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
<mwc-list-item value="AM">AM</mwc-list-item>
|
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
</ha-select>`}
|
</ha-select>`}
|
||||||
|
${this.helper
|
||||||
|
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _clearValue(): void {
|
||||||
|
fireEvent(this, "value-changed");
|
||||||
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: InputEvent) {
|
private _valueChanged(ev: InputEvent) {
|
||||||
const textField = ev.currentTarget as HaTextField;
|
const textField = ev.currentTarget as HaTextField;
|
||||||
this[textField.name] =
|
this[textField.name] =
|
||||||
@@ -302,18 +320,25 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
:host([clearable]) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.time-input-wrap-wrap {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.time-input-wrap {
|
.time-input-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
|
padding-right: 3px;
|
||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
width: 40px;
|
width: 55px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
--mdc-shape-small: 0;
|
--mdc-shape-small: 0;
|
||||||
--text-field-appearance: none;
|
--text-field-appearance: none;
|
||||||
@@ -335,6 +360,21 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
--mdc-shape-small: 0;
|
--mdc-shape-small: 0;
|
||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
|
:host([clearable]) .mdc-select__anchor {
|
||||||
|
padding-inline-end: var(--select-selected-text-padding-end, 12px);
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
position: relative
|
||||||
|
--mdc-icon-button-size: 36px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
direction: var(--direction);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color:var(--mdc-text-field-fill-color, whitesmoke);
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
label {
|
label {
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
@@ -47,6 +47,8 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public readOnly = false;
|
@property({ type: Boolean }) public readOnly = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public linewrap = false;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "autocomplete-entities" })
|
@property({ type: Boolean, attribute: "autocomplete-entities" })
|
||||||
public autocompleteEntities = false;
|
public autocompleteEntities = false;
|
||||||
|
|
||||||
@@ -134,6 +136,13 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (changedProps.has("linewrap")) {
|
||||||
|
transactions.push({
|
||||||
|
effects: this._loadedCodeMirror!.linewrapCompartment!.reconfigure(
|
||||||
|
this.linewrap ? this._loadedCodeMirror!.EditorView.lineWrapping : []
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
if (changedProps.has("_value") && this._value !== this.value) {
|
if (changedProps.has("_value") && this._value !== this.value) {
|
||||||
transactions.push({
|
transactions.push({
|
||||||
changes: {
|
changes: {
|
||||||
@@ -181,6 +190,9 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
this._loadedCodeMirror.readonlyCompartment.of(
|
this._loadedCodeMirror.readonlyCompartment.of(
|
||||||
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
|
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
|
||||||
),
|
),
|
||||||
|
this._loadedCodeMirror.linewrapCompartment.of(
|
||||||
|
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
|
||||||
|
),
|
||||||
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
|
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -196,8 +196,8 @@ export class HaControlNumberButton extends LitElement {
|
|||||||
--control-number-buttons-background-opacity: 0.2;
|
--control-number-buttons-background-opacity: 0.2;
|
||||||
--control-number-buttons-border-radius: 10px;
|
--control-number-buttons-border-radius: 10px;
|
||||||
--mdc-icon-size: 16px;
|
--mdc-icon-size: 16px;
|
||||||
height: 40px;
|
height: var(--feature-height);
|
||||||
width: 200px;
|
width: 100%;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@@ -89,13 +89,18 @@ export class HaFilterDomains extends LitElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(domains.values())
|
return Array.from(domains.values())
|
||||||
|
.map((domain) => ({
|
||||||
|
domain,
|
||||||
|
name: domainToName(this.hass.localize, domain),
|
||||||
|
}))
|
||||||
.filter(
|
.filter(
|
||||||
(entry) =>
|
(entry) =>
|
||||||
!filter ||
|
!filter ||
|
||||||
entry.toLowerCase().includes(filter) ||
|
entry.domain.toLowerCase().includes(filter) ||
|
||||||
domainToName(this.hass.localize, entry).toLowerCase().includes(filter)
|
entry.name.toLowerCase().includes(filter)
|
||||||
)
|
)
|
||||||
.sort((a, b) => stringCompare(a, b, this.hass.locale.language));
|
.sort((a, b) => stringCompare(a.name, b.name, this.hass.locale.language))
|
||||||
|
.map((entry) => entry.domain);
|
||||||
});
|
});
|
||||||
|
|
||||||
protected updated(changed) {
|
protected updated(changed) {
|
||||||
|
@@ -295,6 +295,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
|||||||
icon: null,
|
icon: null,
|
||||||
level: null,
|
level: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -309,6 +311,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
|||||||
icon: "mdi:plus",
|
icon: "mdi:plus",
|
||||||
level: null,
|
level: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -391,6 +395,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
|||||||
icon: null,
|
icon: null,
|
||||||
level: null,
|
level: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
] as FloorRegistryEntry[];
|
] as FloorRegistryEntry[];
|
||||||
} else {
|
} else {
|
||||||
@@ -405,6 +411,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
|||||||
icon: "mdi:plus",
|
icon: "mdi:plus",
|
||||||
level: null,
|
level: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
] as FloorRegistryEntry[];
|
] as FloorRegistryEntry[];
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import type { Selector } from "../../data/selector";
|
|||||||
import type { HaFormSchema } from "./types";
|
import type { HaFormSchema } from "./types";
|
||||||
|
|
||||||
export const computeInitialHaFormData = (
|
export const computeInitialHaFormData = (
|
||||||
schema: HaFormSchema[]
|
schema: HaFormSchema[] | readonly HaFormSchema[]
|
||||||
): Record<string, any> => {
|
): Record<string, any> => {
|
||||||
const data = {};
|
const data = {};
|
||||||
schema.forEach((field) => {
|
schema.forEach((field) => {
|
||||||
@@ -36,6 +36,8 @@ export const computeInitialHaFormData = (
|
|||||||
minutes: 0,
|
minutes: 0,
|
||||||
seconds: 0,
|
seconds: 0,
|
||||||
};
|
};
|
||||||
|
} else if (field.type === "expandable") {
|
||||||
|
data[field.name] = computeInitialHaFormData(field.schema);
|
||||||
} else if ("selector" in field) {
|
} else if ("selector" in field) {
|
||||||
const selector: Selector = field.selector;
|
const selector: Selector = field.selector;
|
||||||
|
|
||||||
@@ -92,6 +94,8 @@ export const computeInitialHaFormData = (
|
|||||||
data[field.name] = selector.color_temp?.min_mireds ?? 153;
|
data[field.name] = selector.color_temp?.min_mireds ?? 153;
|
||||||
} else if (
|
} else if (
|
||||||
"action" in selector ||
|
"action" in selector ||
|
||||||
|
"trigger" in selector ||
|
||||||
|
"condition" in selector ||
|
||||||
"media" in selector ||
|
"media" in selector ||
|
||||||
"target" in selector
|
"target" in selector
|
||||||
) {
|
) {
|
||||||
|
246
src/components/ha-grid-size-picker.ts
Normal file
246
src/components/ha-grid-size-picker.ts
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
|
||||||
|
|
||||||
|
import { mdiRestore } from "@mdi/js";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import { conditionalClamp } from "../common/number/clamp";
|
||||||
|
|
||||||
|
type GridSizeValue = {
|
||||||
|
rows?: number | "auto";
|
||||||
|
columns?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-grid-size-picker")
|
||||||
|
export class HaGridSizeEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public value?: GridSizeValue;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public rows = 8;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public columns = 4;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public rowMin?: number;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public rowMax?: number;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public columnMin?: number;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public columnMax?: number;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public isDefault?: boolean;
|
||||||
|
|
||||||
|
@state() public _localValue?: GridSizeValue = undefined;
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties) {
|
||||||
|
if (changedProperties.has("value")) {
|
||||||
|
this._localValue = this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const disabledColumns =
|
||||||
|
this.columnMin !== undefined && this.columnMin === this.columnMax;
|
||||||
|
const disabledRows =
|
||||||
|
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
||||||
|
|
||||||
|
const autoHeight = this._localValue?.rows === "auto";
|
||||||
|
|
||||||
|
const rowMin = this.rowMin ?? 1;
|
||||||
|
const rowMax = this.rowMax ?? this.rows;
|
||||||
|
const columnMin = this.columnMin ?? 1;
|
||||||
|
const columnMax = this.columnMax ?? this.columns;
|
||||||
|
const rowValue = autoHeight ? rowMin : this._localValue?.rows;
|
||||||
|
const columnValue = this._localValue?.columns;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="grid">
|
||||||
|
<ha-grid-layout-slider
|
||||||
|
aria-label=${this.hass.localize(
|
||||||
|
"ui.components.grid-size-picker.columns"
|
||||||
|
)}
|
||||||
|
id="columns"
|
||||||
|
.min=${columnMin}
|
||||||
|
.max=${columnMax}
|
||||||
|
.range=${this.columns}
|
||||||
|
.value=${columnValue}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
@slider-moved=${this._sliderMoved}
|
||||||
|
.disabled=${disabledColumns}
|
||||||
|
></ha-grid-layout-slider>
|
||||||
|
|
||||||
|
<ha-grid-layout-slider
|
||||||
|
aria-label=${this.hass.localize(
|
||||||
|
"ui.components.grid-size-picker.rows"
|
||||||
|
)}
|
||||||
|
id="rows"
|
||||||
|
.min=${rowMin}
|
||||||
|
.max=${rowMax}
|
||||||
|
.range=${this.rows}
|
||||||
|
vertical
|
||||||
|
.value=${rowValue}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
@slider-moved=${this._sliderMoved}
|
||||||
|
.disabled=${disabledRows}
|
||||||
|
></ha-grid-layout-slider>
|
||||||
|
${!this.isDefault
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
@click=${this._reset}
|
||||||
|
class="reset"
|
||||||
|
.path=${mdiRestore}
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.components.grid-size-picker.reset_default"
|
||||||
|
)}
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.components.grid-size-picker.reset_default"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<div
|
||||||
|
class="preview"
|
||||||
|
style=${styleMap({
|
||||||
|
"--total-rows": this.rows,
|
||||||
|
"--total-columns": this.columns,
|
||||||
|
"--rows": rowValue,
|
||||||
|
"--columns": columnValue,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
${Array(this.rows * this.columns)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => {
|
||||||
|
const row = Math.floor(index / this.columns) + 1;
|
||||||
|
const column = (index % this.columns) + 1;
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="cell"
|
||||||
|
data-row=${row}
|
||||||
|
data-column=${column}
|
||||||
|
@click=${this._cellClick}
|
||||||
|
></div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div class="selected">
|
||||||
|
<div class="cell"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cellClick(ev) {
|
||||||
|
const cell = ev.currentTarget as HTMLElement;
|
||||||
|
const rows = Number(cell.getAttribute("data-row"));
|
||||||
|
const columns = Number(cell.getAttribute("data-column"));
|
||||||
|
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
|
||||||
|
const clampedColumn = conditionalClamp(
|
||||||
|
columns,
|
||||||
|
this.columnMin,
|
||||||
|
this.columnMax
|
||||||
|
);
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { rows: clampedRow, columns: clampedColumn },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const key = ev.currentTarget.id;
|
||||||
|
const newValue = {
|
||||||
|
...this.value,
|
||||||
|
[key]: ev.detail.value,
|
||||||
|
};
|
||||||
|
fireEvent(this, "value-changed", { value: newValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _reset(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
rows: undefined,
|
||||||
|
columns: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sliderMoved(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const key = ev.currentTarget.id;
|
||||||
|
const value = ev.detail.value;
|
||||||
|
if (value === undefined) return;
|
||||||
|
this._localValue = {
|
||||||
|
...this.value,
|
||||||
|
[key]: ev.detail.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = [
|
||||||
|
css`
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"reset column-slider"
|
||||||
|
"row-slider preview";
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
#columns {
|
||||||
|
grid-area: column-slider;
|
||||||
|
}
|
||||||
|
#rows {
|
||||||
|
grid-area: row-slider;
|
||||||
|
}
|
||||||
|
.reset {
|
||||||
|
grid-area: reset;
|
||||||
|
}
|
||||||
|
.preview {
|
||||||
|
position: relative;
|
||||||
|
grid-area: preview;
|
||||||
|
aspect-ratio: 1 / 1.2;
|
||||||
|
}
|
||||||
|
.preview > div {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--total-columns), 1fr);
|
||||||
|
grid-template-rows: repeat(var(--total-rows), 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.preview .cell {
|
||||||
|
background-color: var(--disabled-color);
|
||||||
|
grid-column: span 1;
|
||||||
|
grid-row: span 1;
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.selected {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.selected .cell {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
grid-column: 1 / span var(--columns, 0);
|
||||||
|
grid-row: 1 / span var(--rows, 0);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-grid-size-picker": HaGridSizeEditor;
|
||||||
|
}
|
||||||
|
}
|
@@ -303,6 +303,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
|||||||
icon: null,
|
icon: null,
|
||||||
color: null,
|
color: null,
|
||||||
description: null,
|
description: null,
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -317,6 +319,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
|||||||
icon: "mdi:plus",
|
icon: "mdi:plus",
|
||||||
color: null,
|
color: null,
|
||||||
description: null,
|
description: null,
|
||||||
|
created_at: 0,
|
||||||
|
modified_at: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,11 @@ const LAWN_MOWER_ACTIONS: Partial<
|
|||||||
service: "start_mowing",
|
service: "start_mowing",
|
||||||
feature: LawnMowerEntityFeature.START_MOWING,
|
feature: LawnMowerEntityFeature.START_MOWING,
|
||||||
},
|
},
|
||||||
|
returning: {
|
||||||
|
action: "pause",
|
||||||
|
service: "pause",
|
||||||
|
feature: LawnMowerEntityFeature.PAUSE,
|
||||||
|
},
|
||||||
paused: {
|
paused: {
|
||||||
action: "resume_mowing",
|
action: "resume_mowing",
|
||||||
service: "start_mowing",
|
service: "start_mowing",
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { MdMenuItem } from "@material/web/menu/menu-item";
|
import { MdMenuItem } from "@material/web/menu/menu-item";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-menu-item")
|
@customElement("ha-menu-item")
|
||||||
export class HaMenuItem extends MdMenuItem {
|
export class HaMenuItem extends MdMenuItem {
|
||||||
|
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
|
@@ -1,9 +1,30 @@
|
|||||||
import { MdMenu } from "@material/web/menu/menu";
|
import { MdMenu } from "@material/web/menu/menu";
|
||||||
|
import type { CloseMenuEvent } from "@material/web/menu/menu";
|
||||||
|
import {
|
||||||
|
CloseReason,
|
||||||
|
KeydownCloseKey,
|
||||||
|
} from "@material/web/menu/internal/controllers/shared";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
import type { HaMenuItem } from "./ha-menu-item";
|
||||||
|
|
||||||
@customElement("ha-menu")
|
@customElement("ha-menu")
|
||||||
export class HaMenu extends MdMenu {
|
export class HaMenu extends MdMenu {
|
||||||
|
connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.addEventListener("close-menu", this._handleCloseMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleCloseMenu(ev: CloseMenuEvent) {
|
||||||
|
if (
|
||||||
|
ev.detail.reason.kind === CloseReason.KEYDOWN &&
|
||||||
|
ev.detail.reason.key === KeydownCloseKey.ESCAPE
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(ev.detail.initiator as HaMenuItem).clickAction?.(ev.detail.initiator);
|
||||||
|
}
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
@@ -18,4 +39,8 @@ declare global {
|
|||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-menu": HaMenu;
|
"ha-menu": HaMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HTMLElementEventMap {
|
||||||
|
"close-menu": CloseMenuEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,72 +1,92 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import { mdiCamera } from "@mdi/js";
|
import { mdiCamera } from "@mdi/js";
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, nothing, PropertyValues } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import type QrScanner from "qr-scanner";
|
import type QrScanner from "qr-scanner";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
|
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
import "./ha-button-menu";
|
import "./ha-button-menu";
|
||||||
|
import "./ha-list-item";
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
import type { HaTextField } from "./ha-textfield";
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
@customElement("ha-qr-scanner")
|
||||||
class HaQrScanner extends LitElement {
|
class HaQrScanner extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||||
|
|
||||||
|
@property() public description?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "alternative_option_label" })
|
||||||
|
public alternativeOptionLabel?: string;
|
||||||
|
|
||||||
|
@property() public error?: string;
|
||||||
|
|
||||||
@state() private _cameras?: QrScanner.Camera[];
|
@state() private _cameras?: QrScanner.Camera[];
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _manual = false;
|
||||||
|
|
||||||
private _qrScanner?: QrScanner;
|
private _qrScanner?: QrScanner;
|
||||||
|
|
||||||
private _qrNotFoundCount = 0;
|
private _qrNotFoundCount = 0;
|
||||||
|
|
||||||
@query("video", true) private _video!: HTMLVideoElement;
|
private _removeListener?: UnsubscribeFunc;
|
||||||
|
|
||||||
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
@query("video", true) private _video?: HTMLVideoElement;
|
||||||
|
|
||||||
|
@query("#canvas-container", true) private _canvasContainer?: HTMLDivElement;
|
||||||
|
|
||||||
@query("ha-textfield") private _manualInput?: HaTextField;
|
@query("ha-textfield") private _manualInput?: HaTextField;
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
|
if (this._nativeBarcodeScanner) {
|
||||||
|
this._closeExternalScanner();
|
||||||
|
}
|
||||||
if (this._qrScanner) {
|
if (this._qrScanner) {
|
||||||
this._qrScanner.stop();
|
this._qrScanner.stop();
|
||||||
this._qrScanner.destroy();
|
this._qrScanner.destroy();
|
||||||
this._qrScanner = undefined;
|
this._qrScanner = undefined;
|
||||||
}
|
}
|
||||||
while (this._canvasContainer.lastChild) {
|
while (this._canvasContainer?.lastChild) {
|
||||||
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this.hasUpdated && navigator.mediaDevices) {
|
if (this.hasUpdated) {
|
||||||
this._loadQrScanner();
|
this._loadQrScanner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
if (navigator.mediaDevices) {
|
this._loadQrScanner();
|
||||||
this._loadQrScanner();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
if (changedProps.has("_error") && this._error) {
|
if (changedProps.has("error") && this.error) {
|
||||||
fireEvent(this, "qr-code-error", { message: this._error });
|
alert(`error: ${this.error}`);
|
||||||
|
this._notifyExternalScanner(this.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render() {
|
||||||
return html`${this._error
|
if (this._nativeBarcodeScanner && !this._manual) {
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`${this.error
|
||||||
|
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${navigator.mediaDevices
|
${navigator.mediaDevices && !this._manual
|
||||||
? html`<video></video>
|
? html`<video></video>
|
||||||
<div id="canvas-container">
|
<div id="canvas-container">
|
||||||
${this._cameras && this._cameras.length > 1
|
${this._cameras && this._cameras.length > 1
|
||||||
@@ -80,21 +100,26 @@ class HaQrScanner extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
${this._cameras!.map(
|
${this._cameras!.map(
|
||||||
(camera) => html`
|
(camera) => html`
|
||||||
<mwc-list-item
|
<ha-list-item
|
||||||
.value=${camera.id}
|
.value=${camera.id}
|
||||||
@click=${this._cameraChanged}
|
@click=${this._cameraChanged}
|
||||||
>${camera.label}</mwc-list-item
|
|
||||||
>
|
>
|
||||||
|
${camera.label}
|
||||||
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ha-button-menu>`
|
</ha-button-menu>`
|
||||||
: ""}
|
: nothing}
|
||||||
</div>`
|
</div>`
|
||||||
: html`<ha-alert alert-type="warning">
|
: html`${this._manual
|
||||||
${!window.isSecureContext
|
? nothing
|
||||||
? this.localize("ui.components.qr-scanner.only_https_supported")
|
: html`<ha-alert alert-type="warning">
|
||||||
: this.localize("ui.components.qr-scanner.not_supported")}
|
${!window.isSecureContext
|
||||||
</ha-alert>
|
? this.localize(
|
||||||
|
"ui.components.qr-scanner.only_https_supported"
|
||||||
|
)
|
||||||
|
: this.localize("ui.components.qr-scanner.not_supported")}
|
||||||
|
</ha-alert>`}
|
||||||
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
@@ -102,33 +127,44 @@ class HaQrScanner extends LitElement {
|
|||||||
@keyup=${this._manualKeyup}
|
@keyup=${this._manualKeyup}
|
||||||
@paste=${this._manualPaste}
|
@paste=${this._manualPaste}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
<mwc-button @click=${this._manualSubmit}
|
<mwc-button @click=${this._manualSubmit}>
|
||||||
>${this.localize("ui.common.submit")}</mwc-button
|
${this.localize("ui.common.submit")}
|
||||||
>
|
</mwc-button>
|
||||||
</div>`}`;
|
</div>`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get _nativeBarcodeScanner(): boolean {
|
||||||
|
return Boolean(this.hass.auth.external?.config.hasBarCodeScanner);
|
||||||
|
}
|
||||||
|
|
||||||
private async _loadQrScanner() {
|
private async _loadQrScanner() {
|
||||||
|
if (this._nativeBarcodeScanner) {
|
||||||
|
this._openExternalScanner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!navigator.mediaDevices) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const QrScanner = (await import("qr-scanner")).default;
|
const QrScanner = (await import("qr-scanner")).default;
|
||||||
if (!(await QrScanner.hasCamera())) {
|
if (!(await QrScanner.hasCamera())) {
|
||||||
this._error = "No camera found";
|
this._reportError("No camera found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
||||||
this._listCameras(QrScanner);
|
this._listCameras(QrScanner);
|
||||||
this._qrScanner = new QrScanner(
|
this._qrScanner = new QrScanner(
|
||||||
this._video,
|
this._video!,
|
||||||
this._qrCodeScanned,
|
this._qrCodeScanned,
|
||||||
this._qrCodeError
|
this._qrCodeError
|
||||||
);
|
);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const canvas = this._qrScanner.$canvas;
|
const canvas = this._qrScanner.$canvas;
|
||||||
this._canvasContainer.appendChild(canvas);
|
this._canvasContainer!.appendChild(canvas);
|
||||||
canvas.style.display = "block";
|
canvas.style.display = "block";
|
||||||
try {
|
try {
|
||||||
await this._qrScanner.start();
|
await this._qrScanner.start();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = err;
|
this._reportError(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,16 +176,16 @@ class HaQrScanner extends LitElement {
|
|||||||
if (err === "No QR code found") {
|
if (err === "No QR code found") {
|
||||||
this._qrNotFoundCount++;
|
this._qrNotFoundCount++;
|
||||||
if (this._qrNotFoundCount === 250) {
|
if (this._qrNotFoundCount === 250) {
|
||||||
this._error = err;
|
this._reportError(err);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._error = err.message || err;
|
this._reportError(err.message || err);
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(err);
|
console.log(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => {
|
private _qrCodeScanned = (qrCodeString: string): void => {
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
||||||
};
|
};
|
||||||
@@ -175,6 +211,62 @@ class HaQrScanner extends LitElement {
|
|||||||
this._qrScanner?.setCamera((ev.target as any).value);
|
this._qrScanner?.setCamera((ev.target as any).value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _openExternalScanner() {
|
||||||
|
this._removeListener = addExternalBarCodeListener((msg) => {
|
||||||
|
if (msg.command === "bar_code/scan_result") {
|
||||||
|
if (msg.payload.format !== "qr_code") {
|
||||||
|
this._notifyExternalScanner(
|
||||||
|
`Wrong barcode scanned! ${msg.payload.format}: ${msg.payload.rawValue}, we need a QR code.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._qrCodeScanned(msg.payload.rawValue);
|
||||||
|
}
|
||||||
|
} else if (msg.command === "bar_code/aborted") {
|
||||||
|
this._closeExternalScanner();
|
||||||
|
if (msg.payload.reason === "canceled") {
|
||||||
|
fireEvent(this, "qr-code-closed");
|
||||||
|
} else {
|
||||||
|
this._manual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
this.hass.auth.external!.fireMessage({
|
||||||
|
type: "bar_code/scan",
|
||||||
|
payload: {
|
||||||
|
title: this.title || "Scan QR code",
|
||||||
|
description: this.description || "Scan a barcode.",
|
||||||
|
alternative_option_label:
|
||||||
|
this.alternativeOptionLabel || "Click to manually enter the barcode",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _closeExternalScanner() {
|
||||||
|
this._removeListener?.();
|
||||||
|
this._removeListener = undefined;
|
||||||
|
this.hass.auth.external!.fireMessage({
|
||||||
|
type: "bar_code/close",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notifyExternalScanner(message: string) {
|
||||||
|
if (!this.hass.auth.external) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hass.auth.external.fireMessage({
|
||||||
|
type: "bar_code/notify",
|
||||||
|
payload: {
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.error = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _reportError(message: string) {
|
||||||
|
fireEvent(this, "qr-code-error", { message });
|
||||||
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
canvas {
|
canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -210,6 +302,7 @@ declare global {
|
|||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"qr-code-scanned": { value: string };
|
"qr-code-scanned": { value: string };
|
||||||
"qr-code-error": { message: string };
|
"qr-code-error": { message: string };
|
||||||
|
"qr-code-closed": undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user