Compare commits
376 Commits
20250306.0
...
dev
Author | SHA1 | Date |
---|---|---|
|
fcab356639 | |
|
a70a0d4b4a | |
|
5a34560381 | |
|
f71245893a | |
|
1e7bfd59f2 | |
|
1c15116052 | |
|
3647722824 | |
|
713dd68089 | |
|
53dd0cbaa8 | |
|
6bf8faa96a | |
|
09a17131ab | |
|
7f20b2d6d2 | |
|
fa05cd0c90 | |
|
0b7fc330b3 | |
|
6aa78794a7 | |
|
3f17548582 | |
|
0cee3c2882 | |
|
5753b3e166 | |
|
7b78d821f9 | |
|
9a4469588c | |
|
f9eadf08fd | |
|
c630176fcf | |
|
0389fbba52 | |
|
d56c7c41e2 | |
|
e74cac697e | |
|
77216e8e76 | |
|
02a8924f63 | |
|
9fc28e5abb | |
|
933fb1327a | |
|
c73a9fccb8 | |
|
38c11e738e | |
|
93c5632ee0 | |
|
5459eaff30 | |
|
b02f1037fb | |
|
3d130b790c | |
|
e23d2392d8 | |
|
d5a6e16bf8 | |
|
91a5497c60 | |
|
65dae09a49 | |
|
7e0f293d1f | |
|
2682011ae6 | |
|
1bba103a3d | |
|
e425375d55 | |
|
a2689eee63 | |
|
74741c5d69 | |
|
53426d647a | |
|
f6e4f4c0d6 | |
|
2f086f4d00 | |
|
cd91e8c07c | |
|
b3a5ea2893 | |
|
98ae0295b4 | |
|
43bb9d3401 | |
|
8ad4385d67 | |
|
8fb7c1594a | |
|
4fca09f9ae | |
|
6793753755 | |
|
f4e3fdb98e | |
|
63f4cc456c | |
|
33735abfb0 | |
|
22b59b247e | |
|
6d7a40368c | |
|
fbeb457c25 | |
|
4a6834f0d9 | |
|
add417a166 | |
|
ae4f43496e | |
|
4ce792e5bf | |
|
b9433b96dc | |
|
1dfd937c94 | |
|
14e0666c3a | |
|
929a0b9cd4 | |
|
0541270695 | |
|
20d357fb13 | |
|
6658c10b94 | |
|
c2ce02652b | |
|
634db1944f | |
|
21b3177f95 | |
|
7383e3247b | |
|
b33e4bf305 | |
|
9d9522cade | |
|
430e47c0fc | |
|
a6c9702ab2 | |
|
e3122e8e4d | |
|
c8e46bd239 | |
|
4fd87a1d7c | |
|
80151ff759 | |
|
5f187c1bb3 | |
|
ddc04dd48a | |
|
228acf1fae | |
|
74acd7ec38 | |
|
9bc867d0dc | |
|
590df8dd1a | |
|
ccee57f4a5 | |
|
828bf977b2 | |
|
a2b3ea2ac6 | |
|
9c3f77532c | |
|
4a1cf250c4 | |
|
9df5141aac | |
|
13aeb02b53 | |
|
f0f60bae78 | |
|
d1465a79ae | |
|
6fe8af7c75 | |
|
21180d066e | |
|
dec968af54 | |
|
2ccc5355c4 | |
|
316c3f4e1f | |
|
f88d0ca613 | |
|
edd4a3c31f | |
|
a7ee98e7de | |
|
1b6ed8cdc3 | |
|
671049beb2 | |
|
daf4158fa0 | |
|
848713858f | |
|
f0ef7e0c53 | |
|
e10b0fad95 | |
|
8d50bb1d2b | |
|
a15f0c7814 | |
|
e37f7219c2 | |
|
570076c539 | |
|
cfeb0336d1 | |
|
b18cc4dcfb | |
|
e271989cee | |
|
ca223f9d73 | |
|
8fb1cf35ad | |
|
9f59be492e | |
|
8429d114a8 | |
|
4fbc155f8b | |
|
cd39e2d0f2 | |
|
a23f57256c | |
|
c279efaa99 | |
|
c4389ec119 | |
|
50d632f8d4 | |
|
dba2fba828 | |
|
3890afddb9 | |
|
76f187ee2c | |
|
488b54cf19 | |
|
29d2c29af3 | |
|
a2f9101a9f | |
|
7893eba7a7 | |
|
94ced8af32 | |
|
c4b5882b2d | |
|
6e8bac2e58 | |
|
8a2ab2eab4 | |
|
c7e5be185d | |
|
e98721aa76 | |
|
4c8d661c63 | |
|
b7c60ffc74 | |
|
db6c728cd6 | |
|
34f8335a9d | |
|
ecf5068bd0 | |
|
0a2a2b8a70 | |
|
52f4fe6bc0 | |
|
a781bca94b | |
|
63b44c25f8 | |
|
b96319703a | |
|
9e686190f6 | |
|
5ca7b1d508 | |
|
7c1d74c6c3 | |
|
d257f667c1 | |
|
842a064682 | |
|
3d8e146582 | |
|
78e8bd4305 | |
|
0152a79bd5 | |
|
f5bb72f067 | |
|
9ca6a886f5 | |
|
f39011f8f4 | |
|
8b190867e3 | |
|
321b15a270 | |
|
6ba235d540 | |
|
e34fd8161c | |
|
084cda8218 | |
|
f06a0fa34c | |
|
750c59399b | |
|
a6a17cd70c | |
|
de1c6a5178 | |
|
04c3cd7d68 | |
|
17ef74d680 | |
|
098c6a2567 | |
|
899288ae43 | |
|
a9823f30e3 | |
|
97966805fa | |
|
615b228827 | |
|
1819c04c27 | |
|
05e303d771 | |
|
53bb8251fa | |
|
f6467a35db | |
|
7cc6397324 | |
|
f6e3e312bb | |
|
be1e1ff9fc | |
|
2717e1e6cb | |
|
2e9f72867f | |
|
ff6b318fc9 | |
|
386b8ba747 | |
|
e27b97abc0 | |
|
772a2658cb | |
|
1a076061da | |
|
1cb71ed379 | |
|
fb11c21518 | |
|
9cfcd21a93 | |
|
e3f5e921d6 | |
|
df4e81be75 | |
|
1519e1b90c | |
|
4c8b7a30f4 | |
|
1e513281f4 | |
|
3c28764264 | |
|
82be98dad6 | |
|
3320cf1880 | |
|
f7cb83482a | |
|
a9ddaf1bd7 | |
|
eb7923fa49 | |
|
2ae70e9b54 | |
|
3857c53b7f | |
|
620fb6375e | |
|
e18f853f7e | |
|
bbe549fa86 | |
|
586a137037 | |
|
60010c82bd | |
|
d77f962087 | |
|
4c952c191a | |
|
e0fbd3cd1f | |
|
9f05f4df50 | |
|
6fbc7b2efe | |
|
8dab7c598e | |
|
b24f185d62 | |
|
dc5bb899d2 | |
|
420477e416 | |
|
cd9faf7d67 | |
|
852207a5f5 | |
|
1f705c07b2 | |
|
39ee84b54e | |
|
de402e7c1a | |
|
9b74cdebc2 | |
|
ebe8e54046 | |
|
2bac7455cc | |
|
3cbeef070a | |
|
bae0c232be | |
|
e65b5ae91e | |
|
4a166b6c23 | |
|
fe17bb89eb | |
|
888b2472df | |
|
ef7f499364 | |
|
5d9a53dcd5 | |
|
7ce166e40f | |
|
2c0c48106d | |
|
0e8be25a60 | |
|
27379c98df | |
|
4076e5655a | |
|
858b8b90d8 | |
|
d61c771e35 | |
|
ddbf57d541 | |
|
a5b7bb8391 | |
|
5803ab68c2 | |
|
64b9104199 | |
|
7aaea37db7 | |
|
3c11323ea4 | |
|
4f7d5053ec | |
|
7009482057 | |
|
d1090e8ad3 | |
|
06b969f6b6 | |
|
d8b6de2afd | |
|
dfd5e80436 | |
|
9d4df46d5f | |
|
5f4cb9e3c1 | |
|
96e6169b8d | |
|
0906d7aa5a | |
|
02fce1f40a | |
|
05aa55bfb9 | |
|
d4717f1293 | |
|
f6a7e40d4a | |
|
d73e677bea | |
|
356b74607a | |
|
8cb248223d | |
|
24eed2e5fa | |
|
c8a21a7a2f | |
|
54cc096b1a | |
|
49b1198cb7 | |
|
91e9836423 | |
|
6aa2a576b3 | |
|
9712f04662 | |
|
731a9a2e07 | |
|
d42bd36a3e | |
|
28c355812c | |
|
e09dbb474b | |
|
ee10f9080d | |
|
4f9ec622bf | |
|
dba269f2a3 | |
|
46c9af75fd | |
|
df934cfed9 | |
|
e871dc8151 | |
|
69026cbecf | |
|
dda7de3301 | |
|
1e000d2740 | |
|
1d747c0901 | |
|
8ef769559f | |
|
e141b4dbee | |
|
d0545fe827 | |
|
4ef3a25479 | |
|
616c1fda81 | |
|
e8805be561 | |
|
1b6d2ac08e | |
|
de4a8a0a72 | |
|
58dd778b3d | |
|
a4cdb294b1 | |
|
07c4296771 | |
|
95a99c7857 | |
|
fee215fe96 | |
|
5e9341bf4e | |
|
58b7c76b90 | |
|
6ed26407be | |
|
1ac0092d4e | |
|
a4e8ea366a | |
|
fe70355dde | |
|
01f07f6476 | |
|
8fe55b9bc0 | |
|
913fdfd0eb | |
|
c97916bea4 | |
|
cdfe4b53bf | |
|
75edc5132b | |
|
c79164992b | |
|
c581d6d028 | |
|
d899711a48 | |
|
b7be74e722 | |
|
e53961d395 | |
|
03b08fefb7 | |
|
79374f6052 | |
|
ba19849182 | |
|
48338e0886 | |
|
3b87fc84a9 | |
|
61effc3f70 | |
|
e5b460c259 | |
|
7a56731f56 | |
|
bfe20d3760 | |
|
5a37087231 | |
|
dbe5bffe22 | |
|
62da09d045 | |
|
75d7676b36 | |
|
0bea89db91 | |
|
7ad759dd95 | |
|
5377c9e75d | |
|
bf206aa12b | |
|
73669e27f4 | |
|
1b5f4d3432 | |
|
49379b49d0 | |
|
2827421c9f | |
|
1a5a183410 | |
|
88a1de9aaf | |
|
4ad64ce2c8 | |
|
3f1ca32d13 | |
|
a6f7ee6b28 | |
|
0294198fba | |
|
ed6659ad8f | |
|
f1d04e5178 | |
|
807e87fce0 | |
|
cafab61727 | |
|
88e6906b6b | |
|
ebc1259e39 | |
|
c1f2e6d82b | |
|
ef964fd23e | |
|
13105c2d6f | |
|
5b9262487d | |
|
4ec6e324f8 | |
|
20f385e053 | |
|
dd441f882b | |
|
3a1d371b0b | |
|
8848911b34 | |
|
51193cf441 | |
|
a5b7f2466e | |
|
3d9bde548d | |
|
dfa98a4ba8 | |
|
20fe5b1b71 | |
|
9250ecb16f | |
|
e21d1399ea | |
|
5dded38ccc | |
|
54cc8f025c | |
|
0d215e65cd | |
|
f9824a3b3b | |
|
197c9219bd |
|
@ -26,7 +26,7 @@ jobs:
|
|||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -41,9 +41,8 @@ jobs:
|
|||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=cast/dist --alias dev
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=cast/dist --alias dev
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||
|
@ -62,7 +61,7 @@ jobs:
|
|||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -77,9 +76,8 @@ jobs:
|
|||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=cast/dist --prod
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=cast/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||
|
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||
- name: Setup lint cache
|
||||
uses: actions/cache@v4.2.1
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: |
|
||||
node_modules/.cache/prettier
|
||||
|
@ -60,7 +60,7 @@ jobs:
|
|||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -78,7 +78,7 @@ jobs:
|
|||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -89,7 +89,7 @@ jobs:
|
|||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
|
@ -102,7 +102,7 @@ jobs:
|
|||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -113,7 +113,7 @@ jobs:
|
|||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: supervisor-bundle-stats
|
||||
path: build/stats/*.json
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -42,9 +42,8 @@ jobs:
|
|||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=demo/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||
|
@ -63,7 +62,7 @@ jobs:
|
|||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -78,9 +77,8 @@ jobs:
|
|||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=demo/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}
|
||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -34,9 +34,8 @@ jobs:
|
|||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=gallery/dist --prod
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=gallery/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -39,13 +39,14 @@ jobs:
|
|||
|
||||
- name: Deploy preview to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}"
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \
|
||||
--json > deploy_output.json
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
||||
|
||||
- name: Generate summary
|
||||
run: |
|
||||
echo "${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}" >> "$GITHUB_STEP_SUMMARY"
|
||||
NETLIFY_LIVE_URL=$(jq -r '.deploy_url' deploy_output.json)
|
||||
echo "$NETLIFY_LIVE_URL" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -57,14 +57,14 @@ jobs:
|
|||
run: tar -czvf translations.tar.gz translations
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@v2.1.14
|
||||
uses: relative-ci/agent-action@v2.2.0
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -55,7 +55,7 @@ jobs:
|
|||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.2.2
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
|
@ -74,7 +74,7 @@ jobs:
|
|||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
uses: home-assistant/wheels@2025.03.0
|
||||
with:
|
||||
abi: cp313
|
||||
tag: musllinux_1_2
|
||||
|
@ -92,7 +92,7 @@ jobs:
|
|||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -107,7 +107,7 @@ jobs:
|
|||
- name: Tar folder
|
||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.2.2
|
||||
with:
|
||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
|
@ -121,7 +121,7 @@ jobs:
|
|||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
@ -136,6 +136,6 @@ jobs:
|
|||
- name: Tar folder
|
||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.2.2
|
||||
with:
|
||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
|
||||
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
|
||||
--- a/lib/legacy/class.js
|
||||
+++ b/lib/legacy/class.js
|
||||
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
|
||||
// only proceed if the generated class' prototype has not been registered.
|
||||
const generatedProto = PolymerGenerated.prototype;
|
||||
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
|
||||
- generatedProto.__hasRegisterFinished = true;
|
||||
+ // make sure legacy lifecycle is called on the *element*'s prototype
|
||||
+ // and not the generated class prototype; if the element has been
|
||||
+ // extended, these are *not* the same.
|
||||
+ const proto = Object.getPrototypeOf(this);
|
||||
+ // Only set flag when generated prototype itself is registered,
|
||||
+ // as this element may be extended from, and needs to run `registered`
|
||||
+ // on all behaviors on the subclass as well.
|
||||
+ if (proto === generatedProto) {
|
||||
+ generatedProto.__hasRegisterFinished = true;
|
||||
+ }
|
||||
// ensure superclass is registered first.
|
||||
super._registered();
|
||||
// copy properties onto the generated class lazily if we're optimizing,
|
||||
- if (legacyOptimizations) {
|
||||
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
|
||||
+ generatedProto.__hasCopiedProperties = true;
|
||||
copyPropertiesToProto(generatedProto);
|
||||
}
|
||||
- // make sure legacy lifecycle is called on the *element*'s prototype
|
||||
- // and not the generated class prototype; if the element has been
|
||||
- // extended, these are *not* the same.
|
||||
- const proto = Object.getPrototypeOf(this);
|
||||
let list = lifecycle.beforeRegister;
|
||||
if (list) {
|
||||
for (let i=0; i < list.length; i++) {
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.6.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
|
|
|
@ -2,7 +2,7 @@ import defineProvider from "@babel/helper-define-polyfill-provider";
|
|||
import { join } from "node:path";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
|
||||
const POLYFILL_DIR = join(paths.root_dir, "src/resources/polyfills");
|
||||
|
||||
// List of polyfill keys with supported browser targets for the functionality
|
||||
const polyfillSupport = {
|
||||
|
|
|
@ -18,30 +18,18 @@ module.exports.sourceMapURL = () => {
|
|||
module.exports.ignorePackages = () => [];
|
||||
|
||||
// Files from NPM packages that we should replace with empty file
|
||||
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
module.exports.emptyPackages = ({ isHassioBuild }) =>
|
||||
[
|
||||
// Contains all color definitions for all material color sets.
|
||||
// We don't use it
|
||||
require.resolve("@polymer/paper-styles/color.js"),
|
||||
require.resolve("@polymer/paper-styles/default-theme.js"),
|
||||
// Loads stuff from a CDN
|
||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
||||
),
|
||||
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
||||
path.resolve(paths.root_dir, "src/components/ha-icon.ts")
|
||||
),
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
|
||||
path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
|
@ -55,8 +43,9 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
|||
__STATIC_PATH__: "/static/",
|
||||
__HASS_URL__: `\`${
|
||||
"HASS_URL" in process.env
|
||||
? process.env["HASS_URL"]
|
||||
: "${location.protocol}//${location.host}"
|
||||
? process.env.HASS_URL
|
||||
: // eslint-disable-next-line no-template-curly-in-string
|
||||
"${location.protocol}//${location.host}"
|
||||
}\``,
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
|
@ -170,7 +159,7 @@ module.exports.babelOptions = ({
|
|||
],
|
||||
],
|
||||
exclude: [
|
||||
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
||||
path.join(paths.root_dir, "src/resources/polyfills"),
|
||||
...[
|
||||
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
|
||||
"@lit-labs/virtualizer/polyfills",
|
||||
|
@ -188,6 +177,7 @@ module.exports.babelOptions = ({
|
|||
include: /\/node_modules\//,
|
||||
exclude: [
|
||||
"element-internals-polyfill",
|
||||
"@shoelace-style",
|
||||
"@?lit(?:-labs|-element|-html)?",
|
||||
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = {
|
|||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
||||
.readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
|
||||
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
|
|
|
@ -56,6 +56,7 @@ const getCommonTemplateVars = () => {
|
|||
);
|
||||
return {
|
||||
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||
hassUrl: process.env.HASS_URL || "",
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -168,14 +169,14 @@ const APP_PAGE_ENTRIES = {
|
|||
|
||||
gulp.task(
|
||||
"gen-pages-app-dev",
|
||||
genPagesDevTask(APP_PAGE_ENTRIES, paths.polymer_dir, paths.app_output_root)
|
||||
genPagesDevTask(APP_PAGE_ENTRIES, paths.root_dir, paths.app_output_root)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"gen-pages-app-prod",
|
||||
genPagesProdTask(
|
||||
APP_PAGE_ENTRIES,
|
||||
paths.polymer_dir,
|
||||
paths.root_dir,
|
||||
paths.app_output_root,
|
||||
paths.app_output_latest,
|
||||
paths.app_output_es5
|
||||
|
|
|
@ -6,8 +6,8 @@ import path from "path";
|
|||
import paths from "../paths.cjs";
|
||||
|
||||
const npmPath = (...parts) =>
|
||||
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
||||
const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
|
||||
path.resolve(paths.root_dir, "node_modules", ...parts);
|
||||
const polyPath = (...parts) => path.resolve(paths.root_dir, ...parts);
|
||||
|
||||
const copyFileDir = (fromFile, toDir) =>
|
||||
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
|
||||
|
@ -59,6 +59,11 @@ function copyPolyfills(staticDir) {
|
|||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
// Lit polyfill support
|
||||
fs.copySync(
|
||||
npmPath("lit/polyfill-support.js"),
|
||||
path.join(staticPath("polyfills/"), "lit-polyfill-support.js")
|
||||
);
|
||||
|
||||
// dialog-polyfill css
|
||||
copyFileDir(
|
||||
|
|
|
@ -4,7 +4,7 @@ import gulp from "gulp";
|
|||
import { join, resolve } from "node:path";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const formatjsDir = join(paths.polymer_dir, "node_modules", "@formatjs");
|
||||
const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs");
|
||||
const outDir = join(paths.build_dir, "locale-data");
|
||||
|
||||
const INTL_POLYFILLS = {
|
||||
|
|
|
@ -40,20 +40,17 @@ class CustomJSON extends Transform {
|
|||
this._reviver = reviver;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
async _transform(file, _, callback) {
|
||||
try {
|
||||
let obj = JSON.parse(file.contents.toString(), this._reviver);
|
||||
if (this._func) obj = this._func(obj, file.path);
|
||||
for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
|
||||
const outFile = file.clone({ contents: false });
|
||||
outFile.contents = Buffer.from(JSON.stringify(outObj));
|
||||
outFile.dirname += `/${dir}`;
|
||||
this.push(outFile);
|
||||
}
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
let obj = JSON.parse(file.contents.toString(), this._reviver);
|
||||
if (this._func) obj = this._func(obj, file.path);
|
||||
for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
|
||||
const outFile = file.clone({ contents: false });
|
||||
outFile.contents = Buffer.from(JSON.stringify(outObj));
|
||||
outFile.dirname += `/${dir}`;
|
||||
this.push(outFile);
|
||||
}
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,25 +65,19 @@ class MergeJSON extends Transform {
|
|||
this._reviver = reviver;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
async _transform(file, _, callback) {
|
||||
try {
|
||||
this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
|
||||
if (!this._outFile) this._outFile = file.clone({ contents: false });
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
|
||||
if (!this._outFile) this._outFile = file.clone({ contents: false });
|
||||
callback(null);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
async _flush(callback) {
|
||||
try {
|
||||
const mergedObj = merge(this._startObj, ...this._objects);
|
||||
this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
|
||||
this._outFile.stem = this._stem;
|
||||
callback(null, this._outFile);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const mergedObj = merge(this._startObj, ...this._objects);
|
||||
this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
|
||||
this._outFile.stem = this._stem;
|
||||
callback(null, this._outFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
polymer_dir: path.resolve(__dirname, ".."),
|
||||
root_dir: path.resolve(__dirname, ".."),
|
||||
|
||||
build_dir: path.resolve(__dirname, "../build"),
|
||||
app_output_root: path.resolve(__dirname, "../hass_frontend"),
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
const { existsSync } = require("fs");
|
||||
const path = require("path");
|
||||
const rspack = require("@rspack/core");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { RsdoctorRspackPlugin } = require("@rsdoctor/rspack-plugin");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
||||
const filterStats = require("@bundle-stats/plugin-webpack-filter").default;
|
||||
const filterStats = require("@bundle-stats/plugin-webpack-filter");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { WebpackManifestPlugin } = require("rspack-manifest-plugin");
|
||||
const log = require("fancy-log");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const WebpackBar = require("webpackbar/rspack");
|
||||
const paths = require("./paths.cjs");
|
||||
const bundle = require("./bundle.cjs");
|
||||
|
@ -155,10 +160,8 @@ const createRspackConfig = ({
|
|||
},
|
||||
}),
|
||||
new rspack.NormalModuleReplacementPlugin(
|
||||
new RegExp(
|
||||
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
||||
),
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")),
|
||||
path.resolve(paths.root_dir, "src/util/empty.js")
|
||||
),
|
||||
!isProdBuild && new LogStartCompilePlugin(),
|
||||
isProdBuild &&
|
||||
|
@ -192,6 +195,7 @@ const createRspackConfig = ({
|
|||
"lit/directives/if-defined$": "lit/directives/if-defined.js",
|
||||
"lit/directives/guard$": "lit/directives/guard.js",
|
||||
"lit/directives/cache$": "lit/directives/cache.js",
|
||||
"lit/directives/join$": "lit/directives/join.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/directives/live$": "lit/directives/live.js",
|
||||
"lit/directives/keyed$": "lit/directives/keyed.js",
|
||||
|
|
|
@ -309,7 +309,7 @@ export class HcMain extends HassElement {
|
|||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
rawConfig.strategy,
|
||||
rawConfig,
|
||||
this.hass!
|
||||
);
|
||||
this._handleNewLovelaceConfig(config);
|
||||
|
@ -351,10 +351,7 @@ export class HcMain extends HassElement {
|
|||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(
|
||||
DEFAULT_CONFIG.strategy,
|
||||
this.hass!
|
||||
)
|
||||
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ export const demoThemeJimpower = () => ({
|
|||
"paper-item-icon-color": "var(--primary-text-color)",
|
||||
"primary-color": "#5294E2",
|
||||
"label-badge-red": "var(--accent-color)",
|
||||
"paper-tabs-selection-bar-color": "green",
|
||||
"light-primary-color": "var(--accent-color)",
|
||||
"primary-background-color": "#383C45",
|
||||
"primary-text-color": "#FFFFFF",
|
||||
|
|
|
@ -4,7 +4,6 @@ export const demoThemeKernehed = () => ({
|
|||
"paper-item-icon-color": "var(--primary-text-color)",
|
||||
"primary-color": "#2980b9",
|
||||
"label-badge-red": "var(--accent-color)",
|
||||
"paper-tabs-selection-bar-color": "green",
|
||||
"primary-text-color": "#FFFFFF",
|
||||
"light-primary-color": "var(--accent-color)",
|
||||
"primary-background-color": "#222222",
|
||||
|
|
|
@ -5,7 +5,7 @@ import { until } from "lit/directives/until";
|
|||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-button";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import "../../../src/components/ha-spinner";
|
||||
import type { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import type {
|
||||
|
@ -44,9 +44,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`
|
||||
? html`<ha-spinner></ha-spinner>`
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import type { validateConfig } from "../../../src/data/config";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("validate_config", () => ({
|
||||
actions: { valid: true },
|
||||
conditions: { valid: true },
|
||||
triggers: { valid: true },
|
||||
hass.mockWS<typeof validateConfig>("validate_config", () => ({
|
||||
actions: { valid: true, error: null },
|
||||
conditions: { valid: true, error: null },
|
||||
triggers: { valid: true, error: null },
|
||||
}));
|
||||
};
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
import type { getConfigEntries } from "../../../src/data/config_entries";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfigEntries = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("config_entries/get", () => ({
|
||||
entry_id: "co2signal",
|
||||
domain: "co2signal",
|
||||
title: "Electricity Maps",
|
||||
source: "user",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
supports_reconfigure: true,
|
||||
supported_subentry_types: {},
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
reason: null,
|
||||
}));
|
||||
hass.mockWS<typeof getConfigEntries>("config_entries/get", () => [
|
||||
{
|
||||
entry_id: "mock-entry-co2signal",
|
||||
domain: "co2signal",
|
||||
title: "Electricity Maps",
|
||||
source: "user",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
supports_reconfigure: true,
|
||||
supported_subentry_types: {},
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
reason: null,
|
||||
num_subentries: 0,
|
||||
error_reason_translation_key: null,
|
||||
error_reason_translation_placeholders: null,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -42,7 +42,6 @@ export default tseslint.config(
|
|||
__VERSION__: false,
|
||||
__STATIC_PATH__: false,
|
||||
__SUPERVISOR__: false,
|
||||
Polymer: true,
|
||||
},
|
||||
|
||||
parser: tseslint.parser,
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
import type { TemplateResult } from "lit";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-bar";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "@material/web/progress/circular-progress";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("demo-components-ha-circular-progress")
|
||||
export class DemoHaCircularProgress extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card header="Basic circular progress">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Different circular progress sizes">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="tiny"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="medium"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="large"
|
||||
></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Circular progress with an aria-label">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
aria-label="Doing something..."
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
.ariaLabel=${"Doing something..."}
|
||||
></ha-circular-progress></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-circular-progress": DemoHaCircularProgress;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { mdiPacMan } from "@mdi/js";
|
||||
import { mdiLightbulbOn, mdiPacMan } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
@ -125,6 +125,23 @@ const SAMPLES: {
|
|||
`;
|
||||
},
|
||||
},
|
||||
{
|
||||
template(slot, leftChevron) {
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
slot=${slot}
|
||||
.leftChevron=${leftChevron}
|
||||
header="Attr Header with actions"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="leading-icon"
|
||||
.path=${mdiLightbulbOn}
|
||||
></ha-svg-icon>
|
||||
${SHORT_TEXT}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-expansion-panel")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
---
|
||||
title: Circular Progress
|
||||
title: Spinner
|
||||
subtitle: Can be used to indicate an ongoing task.
|
||||
---
|
|
@ -0,0 +1,44 @@
|
|||
import type { TemplateResult } from "lit";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-bar";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("demo-components-ha-spinner")
|
||||
export class DemoHaSpinner extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card header="Basic spinner">
|
||||
<div class="card-content">
|
||||
<ha-spinner></ha-spinner></div
|
||||
></ha-card>
|
||||
<ha-card header="Different spinner sizes">
|
||||
<div class="card-content">
|
||||
<ha-spinner size="tiny"></ha-spinner>
|
||||
<ha-spinner size="small"></ha-spinner>
|
||||
<ha-spinner size="medium"></ha-spinner>
|
||||
<ha-spinner size="large"></ha-spinner></div
|
||||
></ha-card>
|
||||
<ha-card header="Spinner with an aria-label">
|
||||
<div class="card-content">
|
||||
<ha-spinner aria-label="Doing something..."></ha-spinner>
|
||||
<ha-spinner .ariaLabel=${"Doing something..."}></ha-spinner></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-spinner": DemoHaSpinner;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
@ -21,7 +21,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
|||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
return html`<ha-spinner></ha-spinner>`;
|
||||
}
|
||||
const hasConfiguration =
|
||||
(this.addon.options && Object.keys(this.addon.options).length) ||
|
||||
|
|
|
@ -113,8 +113,9 @@ class HassioAddonConfig extends LitElement {
|
|||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
type: entry.format
|
||||
? entry.format
|
||||
: MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@ import "../../../../src/components/ha-card";
|
|||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
|
@ -33,7 +33,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
|||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
return html`<ha-spinner></ha-spinner>`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
|
|
@ -11,7 +11,6 @@ import memoizeOne from "memoize-one";
|
|||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import type { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
import {
|
||||
fetchAddonInfo,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
@ -23,7 +23,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
|||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
return html`<ha-spinner></ha-spinner>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
|
|
@ -1331,6 +1331,12 @@ class HassioAddonInfo extends LitElement {
|
|||
ha-alert mwc-button {
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host > ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
@ -28,9 +28,7 @@ class HassioAddonLogDashboard extends LitElement {
|
|||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`;
|
||||
return html` <ha-spinner></ha-spinner> `;
|
||||
}
|
||||
return html`
|
||||
<div class="search">
|
||||
|
|
|
@ -3,7 +3,6 @@ import type { TemplateResult } from "lit";
|
|||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import "../../../src/components/ha-file-upload";
|
||||
import type { HassioBackup } from "../../../src/data/hassio/backup";
|
||||
import { uploadBackup } from "../../../src/data/hassio/backup";
|
||||
|
|
|
@ -12,6 +12,7 @@ import "../../../../src/components/ha-md-dialog";
|
|||
import "../../../../src/components/ha-dialog-header";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
|
@ -138,7 +139,7 @@ class HassioBackupDialog
|
|||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: this._restoringBackup
|
||||
? html`<div class="loading">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>`
|
||||
: html`
|
||||
<supervisor-backup-content
|
||||
|
@ -310,10 +311,6 @@ class HassioBackupDialog
|
|||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
|
|
|
@ -5,6 +5,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
|||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import {
|
||||
createHassioFullBackup,
|
||||
|
@ -58,7 +59,7 @@ class HassioCreateBackupDialog extends LitElement {
|
|||
)}
|
||||
>
|
||||
${this._creatingBackup
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
? html`<ha-spinner></ha-spinner>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
|
@ -142,10 +143,6 @@ class HassioCreateBackupDialog extends LitElement {
|
|||
:host {
|
||||
direction: var(--direction);
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { css, html, LitElement, nothing } from "lit";
|
|||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-select";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import {
|
||||
|
@ -69,12 +69,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||
?hideActions=${this.moving}
|
||||
>
|
||||
${this.moving
|
||||
? html` <ha-circular-progress
|
||||
aria-label="Moving"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
? html`<ha-spinner aria-label="Moving" size="large"></ha-spinner>
|
||||
<p class="progress-text">
|
||||
${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.moving_desc"
|
||||
|
@ -166,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
ha-circular-progress {
|
||||
ha-spinner {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
|
|
|
@ -16,23 +16,14 @@ import type { HomeAssistant } from "../../../../src/types";
|
|||
import type { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
|
||||
|
||||
const _filterDevices = memoizeOne(
|
||||
(
|
||||
showAdvanced: boolean,
|
||||
hardware: HassioHardwareInfo,
|
||||
filter: string,
|
||||
language: string
|
||||
) =>
|
||||
(hardware: HassioHardwareInfo, filter: string, language: string) =>
|
||||
hardware.devices
|
||||
.filter(
|
||||
(device) =>
|
||||
(showAdvanced ||
|
||||
["tty", "gpio", "input"].includes(device.subsystem)) &&
|
||||
(device.by_id?.toLowerCase().includes(filter) ||
|
||||
device.name.toLowerCase().includes(filter) ||
|
||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
||||
JSON.stringify(device.attributes)
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter))
|
||||
device.by_id?.toLowerCase().includes(filter) ||
|
||||
device.name.toLowerCase().includes(filter) ||
|
||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
||||
JSON.stringify(device.attributes).toLocaleLowerCase().includes(filter)
|
||||
)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language))
|
||||
);
|
||||
|
@ -60,7 +51,6 @@ class HassioHardwareDialog extends LitElement {
|
|||
}
|
||||
|
||||
const devices = _filterDevices(
|
||||
this.hass.userData?.showAdvanced || false,
|
||||
this._dialogParams.hardware,
|
||||
(this._filter || "").toLowerCase(),
|
||||
this.hass.locale.language
|
||||
|
|
|
@ -10,7 +10,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||
import { cache } from "lit/directives/cache";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
|
@ -161,12 +161,8 @@ export class DialogHassioNetwork
|
|||
.disabled=${this._scanning}
|
||||
>
|
||||
${this._scanning
|
||||
? html`<ha-circular-progress
|
||||
aria-label="Scanning"
|
||||
indeterminate
|
||||
size="small"
|
||||
>
|
||||
</ha-circular-progress>`
|
||||
? html`<ha-spinner aria-label="Scanning" size="small">
|
||||
</ha-spinner>`
|
||||
: this.supervisor.localize("dialog.network.scan_ap")}
|
||||
</mwc-button>
|
||||
${this._accessPoints &&
|
||||
|
@ -282,8 +278,7 @@ export class DialogHassioNetwork
|
|||
</mwc-button>
|
||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
? html`<ha-spinner size="small"> </ha-spinner>`
|
||||
: this.supervisor.localize("common.save")}
|
||||
</mwc-button>
|
||||
</div>`;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
|||
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-tooltip";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import type {
|
||||
|
@ -161,10 +161,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
></ha-textfield>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
? html`<ha-spinner size="small"></ha-spinner>`
|
||||
: this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
|
@ -202,7 +199,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
ha-circular-progress {
|
||||
ha-spinner {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import "./hassio-main";
|
||||
|
||||
import("../../src/resources/ha-style");
|
||||
import("@polymer/polymer/lib/utils/settings").then(
|
||||
({ setCancelSyntheticClickEvents }) => setCancelSyntheticClickEvents(false)
|
||||
);
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.textContent = `
|
||||
|
|
|
@ -15,6 +15,7 @@ import "../../../src/components/buttons/ha-progress-button";
|
|||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-spinner";
|
||||
import "../../../src/components/ha-checkbox";
|
||||
import "../../../src/components/ha-faded";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
|
@ -192,12 +193,10 @@ class UpdateAvailableCard extends LitElement {
|
|||
`
|
||||
: nothing}
|
||||
`
|
||||
: html`<ha-circular-progress
|
||||
: html`<ha-spinner
|
||||
aria-label="Updating"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
></ha-spinner>
|
||||
<p class="progress-text">
|
||||
${this.supervisor.localize("update_available.updating", {
|
||||
name: this._name,
|
||||
|
@ -465,7 +464,7 @@ class UpdateAvailableCard extends LitElement {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
ha-spinner {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
type NetworkInfo,
|
||||
} from "./data/supervisor";
|
||||
|
||||
export const ASSUME_CORE_START_SECONDS = 30;
|
||||
export const ASSUME_CORE_START_SECONDS = 60;
|
||||
const SCHEDULE_CORE_CHECK_SECONDS = 1;
|
||||
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
|
||||
|
||||
|
|
136
package.json
136
package.json
|
@ -26,31 +26,31 @@
|
|||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.26.9",
|
||||
"@babel/runtime": "7.27.0",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@codemirror/autocomplete": "6.18.6",
|
||||
"@codemirror/commands": "6.8.0",
|
||||
"@codemirror/language": "6.10.8",
|
||||
"@codemirror/legacy-modes": "6.4.3",
|
||||
"@codemirror/search": "6.5.9",
|
||||
"@codemirror/commands": "6.8.1",
|
||||
"@codemirror/language": "6.11.0",
|
||||
"@codemirror/legacy-modes": "6.5.0",
|
||||
"@codemirror/search": "6.5.10",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.36.3",
|
||||
"@codemirror/view": "6.36.5",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.17.3",
|
||||
"@formatjs/intl-displaynames": "6.8.10",
|
||||
"@formatjs/intl-durationformat": "0.7.3",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.4",
|
||||
"@formatjs/intl-listformat": "7.7.10",
|
||||
"@formatjs/intl-locale": "4.2.10",
|
||||
"@formatjs/intl-numberformat": "8.15.3",
|
||||
"@formatjs/intl-pluralrules": "5.4.3",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.10",
|
||||
"@fullcalendar/core": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/interaction": "6.1.15",
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/luxon3": "6.1.15",
|
||||
"@fullcalendar/timegrid": "6.1.15",
|
||||
"@formatjs/intl-datetimeformat": "6.18.0",
|
||||
"@formatjs/intl-displaynames": "6.8.11",
|
||||
"@formatjs/intl-durationformat": "0.7.4",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.5",
|
||||
"@formatjs/intl-listformat": "7.7.11",
|
||||
"@formatjs/intl-locale": "4.2.11",
|
||||
"@formatjs/intl-numberformat": "8.15.4",
|
||||
"@formatjs/intl-pluralrules": "5.4.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.11",
|
||||
"@fullcalendar/core": "6.1.17",
|
||||
"@fullcalendar/daygrid": "6.1.17",
|
||||
"@fullcalendar/interaction": "6.1.17",
|
||||
"@fullcalendar/list": "6.1.17",
|
||||
"@fullcalendar/luxon3": "6.1.17",
|
||||
"@fullcalendar/timegrid": "6.1.17",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.8",
|
||||
|
@ -81,27 +81,25 @@
|
|||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "2.2.0",
|
||||
"@material/web": "2.3.0",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
"@polymer/paper-listbox": "3.0.1",
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.2",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@shoelace-style/shoelace": "2.20.0",
|
||||
"@shoelace-style/shoelace": "2.20.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.6.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.6.5",
|
||||
"@tsparticles/engine": "3.8.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.7.3",
|
||||
"@vaadin/vaadin-themable-mixin": "24.7.3",
|
||||
"@vibrant/color": "4.0.0",
|
||||
"@vue/web-component-wrapper": "1.3.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"barcode-detector": "3.0.0",
|
||||
"barcode-detector": "3.0.1",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.40.0",
|
||||
"core-js": "3.41.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "4.1.0",
|
||||
"date-fns-tz": "3.2.0",
|
||||
|
@ -109,22 +107,22 @@
|
|||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "5.6.0",
|
||||
"element-internals-polyfill": "1.3.13",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.1.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||
"home-assistant-js-websocket": "9.4.0",
|
||||
"home-assistant-js-websocket": "9.5.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.7.15",
|
||||
"intl-messageformat": "10.7.16",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
"lit": "2.8.0",
|
||||
"lit-html": "2.8.0",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "15.0.7",
|
||||
"luxon": "3.6.1",
|
||||
"marked": "15.0.8",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.3",
|
||||
"object-hash": "3.0.0",
|
||||
|
@ -137,9 +135,7 @@
|
|||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "3.0.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "2.0.2",
|
||||
"ua-parser-js": "2.0.3",
|
||||
"vis-data": "7.1.9",
|
||||
"vis-network": "9.1.9",
|
||||
"vue": "2.7.16",
|
||||
|
@ -154,20 +150,20 @@
|
|||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.26.9",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.3",
|
||||
"@babel/core": "7.26.10",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.4",
|
||||
"@babel/plugin-proposal-decorators": "7.25.9",
|
||||
"@babel/plugin-transform-runtime": "7.26.9",
|
||||
"@babel/plugin-transform-runtime": "7.26.10",
|
||||
"@babel/preset-env": "7.26.9",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.18.2",
|
||||
"@lokalise/node-api": "13.2.1",
|
||||
"@octokit/auth-oauth-device": "7.1.3",
|
||||
"@octokit/plugin-retry": "7.1.4",
|
||||
"@babel/preset-typescript": "7.27.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.19.1",
|
||||
"@lokalise/node-api": "14.4.0",
|
||||
"@octokit/auth-oauth-device": "7.1.5",
|
||||
"@octokit/plugin-retry": "7.2.1",
|
||||
"@octokit/rest": "21.1.1",
|
||||
"@rsdoctor/rspack-plugin": "0.4.13",
|
||||
"@rspack/cli": "1.2.5",
|
||||
"@rspack/core": "1.2.5",
|
||||
"@rsdoctor/rspack-plugin": "1.0.2",
|
||||
"@rspack/cli": "1.3.5",
|
||||
"@rspack/core": "1.3.5",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.21",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
|
@ -175,31 +171,31 @@
|
|||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/leaflet": "1.9.16",
|
||||
"@types/leaflet": "1.9.17",
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/leaflet.markercluster": "1.5.5",
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.4.2",
|
||||
"@types/luxon": "3.6.2",
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "3.0.6",
|
||||
"babel-loader": "9.2.1",
|
||||
"@vitest/coverage-v8": "3.1.1",
|
||||
"babel-loader": "10.0.0",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"del": "8.0.0",
|
||||
"eslint": "9.21.0",
|
||||
"eslint": "9.25.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint-import-resolver-webpack": "0.13.10",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-lit": "1.15.0",
|
||||
"eslint-plugin-lit": "2.1.1",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.1.4",
|
||||
"eslint-plugin-wc": "2.2.1",
|
||||
"eslint-plugin-wc": "3.0.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"glob": "11.0.1",
|
||||
|
@ -209,40 +205,38 @@
|
|||
"gulp-rename": "2.0.0",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "26.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.4.3",
|
||||
"lint-staged": "15.5.1",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
"map-stream": "0.0.7",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.5.2",
|
||||
"prettier": "3.5.3",
|
||||
"rspack-manifest-plugin": "5.0.3",
|
||||
"serve": "14.2.4",
|
||||
"sinon": "19.0.2",
|
||||
"sinon": "20.0.0",
|
||||
"tar": "7.4.3",
|
||||
"terser-webpack-plugin": "5.3.11",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.24.1",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.30.1",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.0.6",
|
||||
"vitest": "3.1.1",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"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",
|
||||
"resolutions": {
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.2#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "2.8.0",
|
||||
"lit-html": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.17",
|
||||
"globals": "16.0.0",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
"packageManager": "yarn@4.9.1"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M30.8239 22.3365L38.8239 38.8365L30.3239 50.3365" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1110_23734" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23734)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#212121"/>
|
||||
</g>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,15 @@
|
|||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.824 22.3365L38.824 38.8365L30.324 50.3365" stroke="white" stroke-opacity="0.24" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1180_4955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4955)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="white" fill-opacity="0.48"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="white" fill-opacity="0.48"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="white" stroke-opacity="0.24" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,19 @@
|
|||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="white"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1110_23775" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23775)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#212121"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,19 @@
|
|||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="#1C1C1C"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1180_4965" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4965)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,11 +1,12 @@
|
|||
[build-system]
|
||||
requires = ["setuptools~=75.1"]
|
||||
requires = ["setuptools~=77.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250306.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
version = "20250326.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
@ -17,8 +18,6 @@ requires-python = ">=3.13.0"
|
|||
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||
|
||||
[tool.setuptools]
|
||||
platforms = ["any"]
|
||||
zip-safe = false
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
{
|
||||
"description": "Group tsparticles engine and presets",
|
||||
"groupName": "tsparticles",
|
||||
"matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
|
||||
"matchPackageNames": ["@tsparticles/engine", "@tsparticles/preset-{/,}**"]
|
||||
},
|
||||
{
|
||||
"description": "Group date-fns with dependent timezone package",
|
||||
|
|
|
@ -265,7 +265,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||
);
|
||||
}
|
||||
|
||||
if (window.innerWidth > 450) {
|
||||
if (
|
||||
window.innerWidth > 450 &&
|
||||
!matchMedia("(prefers-reduced-motion)").matches
|
||||
) {
|
||||
import("../resources/particles");
|
||||
}
|
||||
|
||||
|
|
|
@ -132,6 +132,15 @@ export const hs2rgb = (hs: [number, number]): [number, number, number] =>
|
|||
|
||||
export function theme2hex(themeColor: string): string {
|
||||
if (themeColor.startsWith("#")) {
|
||||
if (themeColor.length === 4 || themeColor.length === 5) {
|
||||
const c = themeColor;
|
||||
// Convert short-form hex (#abc) to 6 digit (#aabbcc). Ignore alpha channel.
|
||||
return `#${c[1]}${c[1]}${c[2]}${c[2]}${c[3]}${c[3]}`;
|
||||
}
|
||||
if (themeColor.length === 9) {
|
||||
// Ignore alpha channel.
|
||||
return themeColor.substring(0, 7);
|
||||
}
|
||||
return themeColor;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ import {
|
|||
differenceInMilliseconds,
|
||||
differenceInMonths,
|
||||
endOfMonth,
|
||||
startOfDay,
|
||||
endOfDay,
|
||||
differenceInDays,
|
||||
addDays,
|
||||
} from "date-fns";
|
||||
import { toZonedTime, fromZonedTime } from "date-fns-tz";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
|
@ -100,6 +104,32 @@ export const shiftDateRange = (
|
|||
locale,
|
||||
config
|
||||
);
|
||||
} else if (
|
||||
calcDateProperty(
|
||||
startDate,
|
||||
(date) => startOfDay(date).getMilliseconds() === date.getMilliseconds(),
|
||||
locale,
|
||||
config
|
||||
) &&
|
||||
calcDateProperty(
|
||||
endDate,
|
||||
(date) => endOfDay(date).getMilliseconds() === date.getMilliseconds(),
|
||||
locale,
|
||||
config
|
||||
)
|
||||
) {
|
||||
const difference =
|
||||
((calcDateDifferenceProperty(
|
||||
endDate,
|
||||
startDate,
|
||||
differenceInDays,
|
||||
locale,
|
||||
config
|
||||
) as number) +
|
||||
1) *
|
||||
(forward ? 1 : -1);
|
||||
start = calcDate(startDate, addDays, locale, config, difference);
|
||||
end = calcDate(endDate, addDays, locale, config, difference);
|
||||
} else {
|
||||
const difference =
|
||||
((calcDateDifferenceProperty(
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import {
|
||||
addDays,
|
||||
subHours,
|
||||
endOfDay,
|
||||
endOfMonth,
|
||||
endOfWeek,
|
||||
endOfYear,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfWeek,
|
||||
startOfYear,
|
||||
startOfQuarter,
|
||||
endOfQuarter,
|
||||
subDays,
|
||||
subMonths,
|
||||
} from "date-fns";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { calcDate } from "./calc_date";
|
||||
import { firstWeekdayIndex } from "./first_weekday";
|
||||
|
||||
export type DateRange =
|
||||
| "today"
|
||||
| "yesterday"
|
||||
| "this_week"
|
||||
| "this_month"
|
||||
| "this_quarter"
|
||||
| "this_year"
|
||||
| "now-7d"
|
||||
| "now-30d"
|
||||
| "now-12m"
|
||||
| "now-1h"
|
||||
| "now-12h"
|
||||
| "now-24h";
|
||||
|
||||
export const calcDateRange = (
|
||||
hass: HomeAssistant,
|
||||
range: DateRange
|
||||
): [Date, Date] => {
|
||||
const today = new Date();
|
||||
const weekStartsOn = firstWeekdayIndex(hass.locale);
|
||||
switch (range) {
|
||||
case "today":
|
||||
return [
|
||||
calcDate(today, startOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
calcDate(today, endOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
];
|
||||
case "yesterday":
|
||||
return [
|
||||
calcDate(addDays(today, -1), startOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
calcDate(addDays(today, -1), endOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
];
|
||||
case "this_week":
|
||||
return [
|
||||
calcDate(today, startOfWeek, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
calcDate(today, endOfWeek, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
];
|
||||
case "this_month":
|
||||
return [
|
||||
calcDate(today, startOfMonth, hass.locale, hass.config),
|
||||
calcDate(today, endOfMonth, hass.locale, hass.config),
|
||||
];
|
||||
case "this_quarter":
|
||||
return [
|
||||
calcDate(today, startOfQuarter, hass.locale, hass.config),
|
||||
calcDate(today, endOfQuarter, hass.locale, hass.config),
|
||||
];
|
||||
case "this_year":
|
||||
return [
|
||||
calcDate(today, startOfYear, hass.locale, hass.config),
|
||||
calcDate(today, endOfYear, hass.locale, hass.config),
|
||||
];
|
||||
case "now-7d":
|
||||
return [
|
||||
calcDate(today, subDays, hass.locale, hass.config, 7),
|
||||
calcDate(today, subDays, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-30d":
|
||||
return [
|
||||
calcDate(today, subDays, hass.locale, hass.config, 30),
|
||||
calcDate(today, subDays, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-12m":
|
||||
return [
|
||||
calcDate(subMonths(today, 12), startOfMonth, hass.locale, hass.config),
|
||||
calcDate(subMonths(today, 1), endOfMonth, hass.locale, hass.config),
|
||||
];
|
||||
case "now-1h":
|
||||
return [
|
||||
calcDate(today, subHours, hass.locale, hass.config, 1),
|
||||
calcDate(today, subHours, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-12h":
|
||||
return [
|
||||
calcDate(today, subHours, hass.locale, hass.config, 12),
|
||||
calcDate(today, subHours, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-24h":
|
||||
return [
|
||||
calcDate(today, subHours, hass.locale, hass.config, 24),
|
||||
calcDate(today, subHours, hass.locale, hass.config, 0),
|
||||
];
|
||||
}
|
||||
return [today, today];
|
||||
};
|
|
@ -134,10 +134,7 @@ export const applyThemesOnElement = (
|
|||
element.__themes = { cacheKey, keys: newTheme?.keys };
|
||||
|
||||
// Set and/or reset styles
|
||||
if (element.updateStyles) {
|
||||
// Use updateStyles() method of Polymer elements
|
||||
element.updateStyles(styles);
|
||||
} else if (window.ShadyCSS) {
|
||||
if (window.ShadyCSS) {
|
||||
// Use ShadyCSS if available
|
||||
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
|
||||
export const computeAreaName = (area: AreaRegistryEntry): string | undefined =>
|
||||
area.name?.trim();
|
|
@ -34,7 +34,7 @@ export const computeAttributeValueDisplay = (
|
|||
value !== undefined ? value : stateObj.attributes[attribute];
|
||||
|
||||
// Null value, the state is unknown
|
||||
if (attributeValue === null) {
|
||||
if (attributeValue === null || attributeValue === undefined) {
|
||||
return localize("state.default.unknown");
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import memoizeOne from "memoize-one";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeStateName } from "./compute_state_name";
|
||||
import { getDuplicates } from "../string/get_duplicates";
|
||||
|
||||
export const computeDeviceName = (
|
||||
device: DeviceRegistryEntry
|
||||
): string | undefined => (device.name_by_user || device.name)?.trim();
|
||||
|
||||
export const computeDeviceNameDisplay = (
|
||||
device: DeviceRegistryEntry,
|
||||
hass: HomeAssistant,
|
||||
entities?: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||
) =>
|
||||
computeDeviceName(device) ||
|
||||
(entities && fallbackDeviceName(hass, entities)) ||
|
||||
hass.localize("ui.panel.config.devices.unnamed_device", {
|
||||
type: hass.localize(
|
||||
`ui.panel.config.devices.type.${device.entry_type || "device"}`
|
||||
),
|
||||
});
|
||||
|
||||
export const fallbackDeviceName = (
|
||||
hass: HomeAssistant,
|
||||
entities: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||
) => {
|
||||
for (const entity of entities || []) {
|
||||
const entityId = typeof entity === "string" ? entity : entity.entity_id;
|
||||
const stateObj = hass.states[entityId];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getDuplicatedDeviceNames = memoizeOne(
|
||||
(devices: HomeAssistant["devices"]): Set<string> => {
|
||||
const names = Object.values(devices)
|
||||
.map((device) => computeDeviceName(device))
|
||||
.filter((name): name is string => name !== undefined);
|
||||
|
||||
return getDuplicates(names);
|
||||
}
|
||||
);
|
|
@ -0,0 +1,59 @@
|
|||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeDeviceName } from "./compute_device_name";
|
||||
import { computeStateName } from "./compute_state_name";
|
||||
import { stripPrefixFromEntityName } from "./strip_prefix_from_entity_name";
|
||||
|
||||
export const computeEntityName = (
|
||||
stateObj: HassEntity,
|
||||
hass: HomeAssistant
|
||||
): string | undefined => {
|
||||
const entry = hass.entities[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
if (!entry) {
|
||||
// Fall back to state name if not in the entity registry (friendly name)
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
return computeEntityEntryName(entry, hass);
|
||||
};
|
||||
|
||||
export const computeEntityEntryName = (
|
||||
entry: EntityRegistryDisplayEntry | EntityRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): string | undefined => {
|
||||
const name =
|
||||
entry.name || ("original_name" in entry ? entry.original_name : undefined);
|
||||
|
||||
const device = entry.device_id ? hass.devices[entry.device_id] : undefined;
|
||||
|
||||
if (!device) {
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const deviceName = computeDeviceName(device);
|
||||
|
||||
// If the device name is the same as the entity name, consider empty entity name
|
||||
if (deviceName === name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Remove the device name from the entity name if it starts with it
|
||||
if (deviceName && name) {
|
||||
return stripPrefixFromEntityName(name, deviceName) || name;
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
|
||||
export const computeFloorName = (floor: FloorRegistryEntry): string =>
|
||||
floor.name?.trim();
|
|
@ -0,0 +1,30 @@
|
|||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
interface AreaContext {
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getAreaContext = (
|
||||
areaId: string,
|
||||
hass: HomeAssistant
|
||||
): AreaContext => {
|
||||
const area = (hass.areas[areaId] as AreaRegistryEntry | undefined) || null;
|
||||
|
||||
if (!area) {
|
||||
return {
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
interface EntityContext {
|
||||
entity: EntityRegistryDisplayEntry | null;
|
||||
device: DeviceRegistryEntry | null;
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getEntityContext = (
|
||||
entityId: string,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const entity =
|
||||
(hass.entities[entityId] as EntityRegistryDisplayEntry | undefined) || null;
|
||||
|
||||
if (!entity) {
|
||||
return {
|
||||
entity: null,
|
||||
device: null,
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const deviceId = entity?.device_id;
|
||||
const device = deviceId ? hass.devices[deviceId] : null;
|
||||
const areaId = entity?.area_id || device?.area_id;
|
||||
const area = areaId ? hass.areas[areaId] : null;
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
entity: entity,
|
||||
device: device,
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
import { computeDomain } from "./compute_domain";
|
||||
|
||||
export type EntityDomainFilterFunc = (entityId: string) => boolean;
|
||||
|
||||
export interface EntityDomainFilter {
|
||||
include_domains: string[];
|
||||
include_entities: string[];
|
||||
exclude_domains: string[];
|
||||
exclude_entities: string[];
|
||||
}
|
||||
|
||||
export const isEmptyEntityDomainFilter = (filter: EntityDomainFilter) =>
|
||||
filter.include_domains.length +
|
||||
filter.include_entities.length +
|
||||
filter.exclude_domains.length +
|
||||
filter.exclude_entities.length ===
|
||||
0;
|
||||
|
||||
export const generateEntityDomainFilter = (
|
||||
includeDomains?: string[],
|
||||
includeEntities?: string[],
|
||||
excludeDomains?: string[],
|
||||
excludeEntities?: string[]
|
||||
): EntityDomainFilterFunc => {
|
||||
const includeDomainsSet = new Set(includeDomains);
|
||||
const includeEntitiesSet = new Set(includeEntities);
|
||||
const excludeDomainsSet = new Set(excludeDomains);
|
||||
const excludeEntitiesSet = new Set(excludeEntities);
|
||||
|
||||
const haveInclude = includeDomainsSet.size > 0 || includeEntitiesSet.size > 0;
|
||||
const haveExclude = excludeDomainsSet.size > 0 || excludeEntitiesSet.size > 0;
|
||||
|
||||
// Case 1 - no includes or excludes - pass all entities
|
||||
if (!haveInclude && !haveExclude) {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
// Case 2 - includes, no excludes - only include specified entities
|
||||
if (haveInclude && !haveExclude) {
|
||||
return (entityId) =>
|
||||
includeEntitiesSet.has(entityId) ||
|
||||
includeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
|
||||
// Case 3 - excludes, no includes - only exclude specified entities
|
||||
if (!haveInclude && haveExclude) {
|
||||
return (entityId) =>
|
||||
!excludeEntitiesSet.has(entityId) &&
|
||||
!excludeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
|
||||
// Case 4 - both includes and excludes specified
|
||||
// Case 4a - include domain specified
|
||||
// - if domain is included, pass if entity not excluded
|
||||
// - if domain is not included, pass if entity is included
|
||||
// note: if both include and exclude domains specified,
|
||||
// the exclude domains are ignored
|
||||
if (includeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
includeDomainsSet.has(computeDomain(entityId))
|
||||
? !excludeEntitiesSet.has(entityId)
|
||||
: includeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4b - exclude domain specified
|
||||
// - if domain is excluded, pass if entity is included
|
||||
// - if domain is not excluded, pass if entity not excluded
|
||||
if (excludeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
excludeDomainsSet.has(computeDomain(entityId))
|
||||
? includeEntitiesSet.has(entityId)
|
||||
: !excludeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4c - neither include or exclude domain specified
|
||||
// - Only pass if entity is included. Ignore entity excludes.
|
||||
return (entityId) => includeEntitiesSet.has(entityId);
|
||||
};
|
|
@ -1,78 +1,121 @@
|
|||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { ensureArray } from "../array/ensure-array";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { getEntityContext } from "./context/get_entity_context";
|
||||
|
||||
export type FilterFunc = (entityId: string) => boolean;
|
||||
type EntityCategory = "none" | "config" | "diagnostic";
|
||||
|
||||
export interface EntityFilter {
|
||||
include_domains: string[];
|
||||
include_entities: string[];
|
||||
exclude_domains: string[];
|
||||
exclude_entities: string[];
|
||||
domain?: string | string[];
|
||||
device_class?: string | string[];
|
||||
device?: string | string[];
|
||||
area?: string | string[];
|
||||
floor?: string | string[];
|
||||
label?: string | string[];
|
||||
entity_category?: EntityCategory | EntityCategory[];
|
||||
hidden_platform?: string | string[];
|
||||
}
|
||||
|
||||
export const isEmptyFilter = (filter: EntityFilter) =>
|
||||
filter.include_domains.length +
|
||||
filter.include_entities.length +
|
||||
filter.exclude_domains.length +
|
||||
filter.exclude_entities.length ===
|
||||
0;
|
||||
export type EntityFilterFunc = (entityId: string) => boolean;
|
||||
|
||||
export const generateFilter = (
|
||||
includeDomains?: string[],
|
||||
includeEntities?: string[],
|
||||
excludeDomains?: string[],
|
||||
excludeEntities?: string[]
|
||||
): FilterFunc => {
|
||||
const includeDomainsSet = new Set(includeDomains);
|
||||
const includeEntitiesSet = new Set(includeEntities);
|
||||
const excludeDomainsSet = new Set(excludeDomains);
|
||||
const excludeEntitiesSet = new Set(excludeEntities);
|
||||
export const generateEntityFilter = (
|
||||
hass: HomeAssistant,
|
||||
filter: EntityFilter
|
||||
): EntityFilterFunc => {
|
||||
const domains = filter.domain
|
||||
? new Set(ensureArray(filter.domain))
|
||||
: undefined;
|
||||
const deviceClasses = filter.device_class
|
||||
? new Set(ensureArray(filter.device_class))
|
||||
: undefined;
|
||||
const floors = filter.floor ? new Set(ensureArray(filter.floor)) : undefined;
|
||||
const areas = filter.area ? new Set(ensureArray(filter.area)) : undefined;
|
||||
const devices = filter.device
|
||||
? new Set(ensureArray(filter.device))
|
||||
: undefined;
|
||||
const entityCategories = filter.entity_category
|
||||
? new Set(ensureArray(filter.entity_category))
|
||||
: undefined;
|
||||
const labels = filter.label ? new Set(ensureArray(filter.label)) : undefined;
|
||||
const hiddenPlatforms = filter.hidden_platform
|
||||
? new Set(ensureArray(filter.hidden_platform))
|
||||
: undefined;
|
||||
|
||||
const haveInclude = includeDomainsSet.size > 0 || includeEntitiesSet.size > 0;
|
||||
const haveExclude = excludeDomainsSet.size > 0 || excludeEntitiesSet.size > 0;
|
||||
return (entityId: string) => {
|
||||
const stateObj = hass.states[entityId] as HassEntity | undefined;
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
if (domains) {
|
||||
const domain = computeDomain(entityId);
|
||||
if (!domains.has(domain)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (deviceClasses) {
|
||||
const dc = stateObj.attributes.device_class || "none";
|
||||
if (!deviceClasses.has(dc)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 1 - no includes or excludes - pass all entities
|
||||
if (!haveInclude && !haveExclude) {
|
||||
return () => true;
|
||||
}
|
||||
const { area, floor, device, entity } = getEntityContext(entityId, hass);
|
||||
|
||||
// Case 2 - includes, no excludes - only include specified entities
|
||||
if (haveInclude && !haveExclude) {
|
||||
return (entityId) =>
|
||||
includeEntitiesSet.has(entityId) ||
|
||||
includeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
if (entity && entity.hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Case 3 - excludes, no includes - only exclude specified entities
|
||||
if (!haveInclude && haveExclude) {
|
||||
return (entityId) =>
|
||||
!excludeEntitiesSet.has(entityId) &&
|
||||
!excludeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
if (floors) {
|
||||
if (!floor) {
|
||||
return false;
|
||||
}
|
||||
if (!floors) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (areas) {
|
||||
if (!area) {
|
||||
return false;
|
||||
}
|
||||
if (!areas.has(area.area_id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (devices) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!devices.has(device.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (labels) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
if (!entity.labels.some((label) => labels.has(label))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (entityCategories) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
const category = entity?.entity_category || "none";
|
||||
if (!entityCategories.has(category)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hiddenPlatforms) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
if (entity.platform && hiddenPlatforms.has(entity.platform)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 4 - both includes and excludes specified
|
||||
// Case 4a - include domain specified
|
||||
// - if domain is included, pass if entity not excluded
|
||||
// - if domain is not included, pass if entity is included
|
||||
// note: if both include and exclude domains specified,
|
||||
// the exclude domains are ignored
|
||||
if (includeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
includeDomainsSet.has(computeDomain(entityId))
|
||||
? !excludeEntitiesSet.has(entityId)
|
||||
: includeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4b - exclude domain specified
|
||||
// - if domain is excluded, pass if entity is included
|
||||
// - if domain is not excluded, pass if entity not excluded
|
||||
if (excludeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
excludeDomainsSet.has(computeDomain(entityId))
|
||||
? includeEntitiesSet.has(entityId)
|
||||
: !excludeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4c - neither include or exclude domain specified
|
||||
// - Only pass if entity is included. Ignore entity excludes.
|
||||
return (entityId) => includeEntitiesSet.has(entityId);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface AreaContext {
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
export const getAreaContext = (
|
||||
area: AreaRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): AreaContext => {
|
||||
const floorId = area.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
floor: floor,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface DeviceContext {
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getDeviceContext = (
|
||||
device: DeviceRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): DeviceContext => {
|
||||
const areaId = device.area_id;
|
||||
const area = areaId ? hass.areas[areaId] : null;
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface EntityContext {
|
||||
device: DeviceRegistryEntry | null;
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getEntityContext = (
|
||||
stateObj: HassEntity,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const entry = hass.entities[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
if (!entry) {
|
||||
return {
|
||||
device: null,
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
return getEntityEntryContext(entry, hass);
|
||||
};
|
||||
|
||||
export const getEntityEntryContext = (
|
||||
entry:
|
||||
| EntityRegistryDisplayEntry
|
||||
| EntityRegistryEntry
|
||||
| ExtEntityRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const deviceId = entry?.device_id;
|
||||
const device = deviceId ? hass.devices[deviceId] : null;
|
||||
const areaId = entry?.area_id || device?.area_id;
|
||||
const area = areaId ? hass.areas[areaId] : null;
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
device: device,
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
|
@ -1,17 +1,17 @@
|
|||
const SUFFIXES = [" ", ": "];
|
||||
const SUFFIXES = [" ", ": ", " - "];
|
||||
|
||||
/**
|
||||
* Strips a device name from an entity name.
|
||||
* @param entityName the entity name
|
||||
* @param lowerCasedPrefix the prefix to strip, lower cased
|
||||
* @param prefix the prefix to strip
|
||||
* @returns
|
||||
*/
|
||||
export const stripPrefixFromEntityName = (
|
||||
entityName: string,
|
||||
lowerCasedPrefix: string
|
||||
prefix: string
|
||||
) => {
|
||||
const lowerCasedEntityName = entityName.toLowerCase();
|
||||
|
||||
const lowerCasedPrefix = prefix.toLowerCase();
|
||||
for (const suffix of SUFFIXES) {
|
||||
const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`;
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export const webComponentsSupported =
|
||||
"customElements" in window && "content" in document.createElement("template");
|
||||
export const webComponentsSupported = "attachShadow" in Element.prototype;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { getIntegrationDescriptions } from "../../data/integrations";
|
|||
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { showMatterAddDeviceDialog } from "../../panels/config/integrations/integration-panels/matter/show-dialog-add-matter-device";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/add-node/show-dialog-zwave_js-add-node";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { isComponentLoaded } from "../config/is_component_loaded";
|
||||
|
|
|
@ -45,3 +45,22 @@ export const caseInsensitiveStringCompare = (
|
|||
|
||||
return fallbackStringCompare(a.toLowerCase(), b.toLowerCase());
|
||||
};
|
||||
|
||||
export const orderCompare = (order: string[]) => (a: string, b: string) => {
|
||||
const idxA = order.indexOf(a);
|
||||
const idxB = order.indexOf(b);
|
||||
|
||||
if (idxA === idxB) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (idxA === -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (idxB === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return idxA - idxB;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
export function getDuplicates(array: string[]): Set<string> {
|
||||
const duplicates = new Set<string>();
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const item of array) {
|
||||
if (seen.has(item)) {
|
||||
duplicates.add(item);
|
||||
} else {
|
||||
seen.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return duplicates;
|
||||
}
|
|
@ -1,31 +1,38 @@
|
|||
import "@material/mwc-button";
|
||||
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-button";
|
||||
import "../ha-spinner";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
export class HaProgressButton extends LitElement {
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public progress = false;
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@property({ type: Boolean }) public unelevated = false;
|
||||
|
||||
@state() private _result?: "success" | "error";
|
||||
|
||||
public render(): TemplateResult {
|
||||
const overlay = this._result || this.progress;
|
||||
return html`
|
||||
<mwc-button
|
||||
?raised=${this.raised}
|
||||
<ha-button
|
||||
.raised=${this.raised}
|
||||
.label=${this.label}
|
||||
.unelevated=${this.unelevated}
|
||||
.disabled=${this.disabled || this.progress}
|
||||
class=${this._result || ""}
|
||||
>
|
||||
<slot name="icon" slot="icon"></slot>
|
||||
<slot></slot>
|
||||
</mwc-button>
|
||||
</ha-button>
|
||||
${!overlay
|
||||
? nothing
|
||||
: html`
|
||||
|
@ -35,12 +42,7 @@ export class HaProgressButton extends LitElement {
|
|||
: this._result === "error"
|
||||
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
|
||||
: this.progress
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
`
|
||||
? html`<ha-spinner size="small"></ha-spinner>`
|
||||
: nothing}
|
||||
</div>
|
||||
`}
|
||||
|
@ -70,12 +72,12 @@ export class HaProgressButton extends LitElement {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
ha-button {
|
||||
transition: all 1s;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
mwc-button.success {
|
||||
ha-button.success {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--success-color);
|
||||
transition: none;
|
||||
|
@ -83,12 +85,13 @@ export class HaProgressButton extends LitElement {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].success {
|
||||
ha-button[unelevated].success,
|
||||
ha-button[raised].success {
|
||||
--mdc-theme-primary: var(--success-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
}
|
||||
|
||||
mwc-button.error {
|
||||
ha-button.error {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--error-color);
|
||||
transition: none;
|
||||
|
@ -96,7 +99,8 @@ export class HaProgressButton extends LitElement {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].error {
|
||||
ha-button[unelevated].error,
|
||||
ha-button[raised].error {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
}
|
||||
|
@ -113,8 +117,8 @@ export class HaProgressButton extends LitElement {
|
|||
color: white;
|
||||
}
|
||||
|
||||
mwc-button.success slot,
|
||||
mwc-button.error slot {
|
||||
ha-button.success slot,
|
||||
ha-button.error slot {
|
||||
visibility: hidden;
|
||||
}
|
||||
:host([destructive]) {
|
||||
|
|
|
@ -226,7 +226,12 @@ export class HaChartBase extends LitElement {
|
|||
const overflowLimit = isMobile
|
||||
? LEGEND_OVERFLOW_LIMIT_MOBILE
|
||||
: LEGEND_OVERFLOW_LIMIT;
|
||||
return html`<div class="chart-legend">
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
"chart-legend": true,
|
||||
"multiple-items": items.length > 1,
|
||||
})}
|
||||
>
|
||||
<ul>
|
||||
${items.map((item: string, index: number) => {
|
||||
if (!this.expandLegend && index >= overflowLimit) {
|
||||
|
@ -262,7 +267,9 @@ export class HaChartBase extends LitElement {
|
|||
? this.hass.localize(
|
||||
"ui.components.history_charts.collapse_legend"
|
||||
)
|
||||
: `${this.hass.localize("ui.components.history_charts.expand_legend")} (${items.length - overflowLimit})`}
|
||||
: `${this.hass.localize(
|
||||
"ui.components.history_charts.expand_legend"
|
||||
)} (${items.length - overflowLimit})`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
|
@ -727,10 +734,10 @@ export class HaChartBase extends LitElement {
|
|||
align-items: center;
|
||||
padding: 0 2px;
|
||||
box-sizing: border-box;
|
||||
max-width: 220px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chart-legend.multiple-items li {
|
||||
max-width: 220px;
|
||||
}
|
||||
.chart-legend .hidden {
|
||||
color: var(--secondary-text-color);
|
||||
|
|
|
@ -124,7 +124,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||
const data = dataset.data || [];
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
const point = data[i];
|
||||
if (point && point[0] <= time && point[1]) {
|
||||
if (point && point[0] <= time && typeof point[1] === "number") {
|
||||
lastData = point;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -296,7 +296,11 @@ export class StatisticsChart extends LitElement {
|
|||
align: "left",
|
||||
},
|
||||
position: computeRTL(this.hass) ? "right" : "left",
|
||||
scale: true,
|
||||
scale:
|
||||
this.chartType !== "bar" ||
|
||||
this.logarithmicScale ||
|
||||
minYAxis !== undefined ||
|
||||
maxYAxis !== undefined,
|
||||
min: this._clampYAxis(minYAxis),
|
||||
max: this._clampYAxis(maxYAxis),
|
||||
splitLine: {
|
||||
|
@ -446,12 +450,14 @@ export class StatisticsChart extends LitElement {
|
|||
|
||||
const hasMean =
|
||||
this.statTypes.includes("mean") && statisticsHaveType(stats, "mean");
|
||||
const drawBands =
|
||||
hasMean ||
|
||||
(this.statTypes.includes("min") &&
|
||||
statisticsHaveType(stats, "min") &&
|
||||
this.statTypes.includes("max") &&
|
||||
statisticsHaveType(stats, "max"));
|
||||
const hasMax =
|
||||
this.statTypes.includes("max") && statisticsHaveType(stats, "max");
|
||||
const hasMin =
|
||||
this.statTypes.includes("min") && statisticsHaveType(stats, "min");
|
||||
const drawBands = [hasMean, hasMax, hasMin].filter(Boolean).length > 1;
|
||||
|
||||
const bandTop = hasMax ? "max" : "mean";
|
||||
const bandBottom = hasMin ? "min" : "mean";
|
||||
|
||||
const sortedTypes = drawBands
|
||||
? [...this.statTypes].sort((a, b) => {
|
||||
|
@ -468,10 +474,12 @@ export class StatisticsChart extends LitElement {
|
|||
let displayedLegend = false;
|
||||
sortedTypes.forEach((type) => {
|
||||
if (statisticsHaveType(stats, type)) {
|
||||
const band = drawBands && (type === "min" || type === "max");
|
||||
const band = drawBands && (type === bandTop || type === bandBottom);
|
||||
statTypes.push(type);
|
||||
const borderColor =
|
||||
band && hasMean ? color + (this.hideLegend ? "00" : "7F") : color;
|
||||
band && hasMin && hasMax && hasMean
|
||||
? color + (this.hideLegend ? "00" : "7F")
|
||||
: color;
|
||||
const backgroundColor = band ? color + "3F" : color + "7F";
|
||||
const series: LineSeriesOption | BarSeriesOption = {
|
||||
id: `${statistic_id}-${type}`,
|
||||
|
@ -506,7 +514,7 @@ export class StatisticsChart extends LitElement {
|
|||
if (band && this.chartType === "line") {
|
||||
series.stack = `band-${statistic_id}`;
|
||||
series.stackStrategy = "all";
|
||||
if (drawBands && type === "max") {
|
||||
if (drawBands && type === bandTop) {
|
||||
(series as LineSeriesOption).areaStyle = {
|
||||
color: color + "3F",
|
||||
};
|
||||
|
@ -549,10 +557,14 @@ export class StatisticsChart extends LitElement {
|
|||
} else {
|
||||
val.push((stat.sum || 0) - firstSum);
|
||||
}
|
||||
} else if (type === "max" && this.chartType === "line") {
|
||||
const max = stat.max || 0;
|
||||
val.push(Math.abs(max - (stat.min || 0)));
|
||||
val.push(max);
|
||||
} else if (
|
||||
type === bandTop &&
|
||||
this.chartType === "line" &&
|
||||
drawBands
|
||||
) {
|
||||
const top = stat[bandTop] || 0;
|
||||
val.push(Math.abs(top - (stat[bandBottom] || 0)));
|
||||
val.push(top);
|
||||
} else {
|
||||
val.push(stat[type] ?? null);
|
||||
}
|
||||
|
|
|
@ -645,15 +645,16 @@ export class HaDataTable extends LitElement {
|
|||
return;
|
||||
}
|
||||
|
||||
const prom = this.sortColumn
|
||||
? sortData(
|
||||
filteredData,
|
||||
this._sortColumns[this.sortColumn],
|
||||
this.sortDirection,
|
||||
this.sortColumn,
|
||||
this.hass.locale.language
|
||||
)
|
||||
: filteredData;
|
||||
const prom =
|
||||
this.sortColumn && this._sortColumns[this.sortColumn]
|
||||
? sortData(
|
||||
filteredData,
|
||||
this._sortColumns[this.sortColumn],
|
||||
this.sortDirection,
|
||||
this.sortColumn,
|
||||
this.hass.locale.language
|
||||
)
|
||||
: filteredData;
|
||||
|
||||
const [data] = await Promise.all([prom, nextRender]);
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
|
@ -13,15 +14,12 @@ import type {
|
|||
DeviceEntityDisplayLookup,
|
||||
DeviceRegistryEntry,
|
||||
} from "../../data/device_registry";
|
||||
import {
|
||||
computeDeviceName,
|
||||
getDeviceEntityDisplayLookup,
|
||||
} from "../../data/device_registry";
|
||||
import { getDeviceEntityDisplayLookup } from "../../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-list-item";
|
||||
import "../ha-combo-box-item";
|
||||
|
||||
interface Device {
|
||||
name: string;
|
||||
|
@ -37,11 +35,14 @@ export type HaDevicePickerDeviceFilterFunc = (
|
|||
|
||||
export type HaDevicePickerEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) =>
|
||||
html`<ha-list-item .twoline=${!!item.area}>
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.area}</span>
|
||||
</ha-list-item>`;
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.name}</span>
|
||||
${item.area
|
||||
? html`<span slot="supporting-text">${item.area}</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
@customElement("ha-device-picker")
|
||||
export class HaDevicePicker extends LitElement {
|
||||
|
@ -214,7 +215,7 @@ export class HaDevicePicker extends LitElement {
|
|||
}
|
||||
|
||||
const outputDevices = inputDevices.map((device) => {
|
||||
const name = computeDeviceName(
|
||||
const name = computeDeviceNameDisplay(
|
||||
device,
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
|
|
|
@ -1,35 +1,78 @@
|
|||
import "../ha-list-item";
|
||||
import { mdiMagnify, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
import type { ValueChangedEvent, HomeAssistant } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import { getEntityContext } from "../../common/entity/get_entity_context";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { showHelperDetailDialog } from "../../panels/config/helpers/show-dialog-helper-detail";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import type { HelperDomain } from "../../panels/config/helpers/const";
|
||||
import { isHelperDomain } from "../../panels/config/helpers/const";
|
||||
import { showHelperDetailDialog } from "../../panels/config/helpers/show-dialog-helper-detail";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-list-item";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
|
||||
interface HassEntityWithCachedName extends HassEntity, ScorableTextItem {
|
||||
friendly_name: string;
|
||||
const FAKE_ENTITY: HassEntity = {
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
interface EntityPickerItem extends HassEntity {
|
||||
label: string;
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
translated_domain?: string;
|
||||
show_entity_id?: boolean;
|
||||
entity_name?: string;
|
||||
area_name?: string;
|
||||
device_name?: string;
|
||||
friendly_name?: string;
|
||||
sorting_label?: string;
|
||||
icon_path?: string;
|
||||
}
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
|
||||
const CREATE_ID = "___create-new-entity___";
|
||||
|
||||
const DOMAIN_STYLE = styleMap({
|
||||
fontSize: "12px",
|
||||
fontWeight: "400",
|
||||
lineHeight: "18px",
|
||||
alignSelf: "flex-end",
|
||||
maxWidth: "30%",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
});
|
||||
|
||||
const ENTITY_ID_STYLE = styleMap({
|
||||
fontFamily: "var(--code-font-family, monospace)",
|
||||
fontSize: "11px",
|
||||
});
|
||||
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
@ -106,8 +149,7 @@ export class HaEntityPicker extends LitElement {
|
|||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@property({ attribute: "item-label-path" }) public itemLabelPath =
|
||||
"friendly_name";
|
||||
@property({ attribute: "item-label-path" }) public itemLabelPath = "label";
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
|
@ -123,30 +165,48 @@ export class HaEntityPicker extends LitElement {
|
|||
await this.comboBox?.focus();
|
||||
}
|
||||
|
||||
private _initedStates = false;
|
||||
private _initialItems = false;
|
||||
|
||||
private _states: HassEntityWithCachedName[] = [];
|
||||
private _items: EntityPickerItem[] = [];
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (
|
||||
item
|
||||
) =>
|
||||
html`<ha-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||
${item.state
|
||||
? html`<state-badge
|
||||
slot="graphic"
|
||||
.stateObj=${item}
|
||||
.hass=${this.hass}
|
||||
></state-badge>`
|
||||
: ""}
|
||||
<span>${item.friendly_name}</span>
|
||||
<span slot="secondary"
|
||||
>${item.entity_id.startsWith(CREATE_ID)
|
||||
? this.hass.localize("ui.components.entity.entity-picker.new_entity")
|
||||
: item.entity_id}</span
|
||||
>
|
||||
</ha-list-item>`;
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
|
||||
private _getStates = memoizeOne(
|
||||
private _rowRenderer: ComboBoxLitRenderer<EntityPickerItem> = (
|
||||
item,
|
||||
{ index }
|
||||
) => html`
|
||||
<ha-combo-box-item type="button" compact .borderTop=${index !== 0}>
|
||||
${item.icon_path
|
||||
? html`<ha-svg-icon slot="start" .path=${item.icon_path}></ha-svg-icon>`
|
||||
: html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`}
|
||||
|
||||
<span slot="headline">${item.primary} </span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${item.entity_id && item.show_entity_id
|
||||
? html`<span slot="supporting-text" style=${ENTITY_ID_STYLE}
|
||||
>${item.entity_id}</span
|
||||
>`
|
||||
: nothing}
|
||||
${item.translated_domain && !item.show_entity_id
|
||||
? html`<div slot="trailing-supporting-text" style=${DOMAIN_STYLE}>
|
||||
${item.translated_domain}
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(
|
||||
_opened: boolean,
|
||||
hass: this["hass"],
|
||||
|
@ -158,8 +218,8 @@ export class HaEntityPicker extends LitElement {
|
|||
includeEntities: this["includeEntities"],
|
||||
excludeEntities: this["excludeEntities"],
|
||||
createDomains: this["createDomains"]
|
||||
): HassEntityWithCachedName[] => {
|
||||
let states: HassEntityWithCachedName[] = [];
|
||||
): EntityPickerItem[] => {
|
||||
let states: EntityPickerItem[] = [];
|
||||
|
||||
if (!hass) {
|
||||
return [];
|
||||
|
@ -168,7 +228,7 @@ export class HaEntityPicker extends LitElement {
|
|||
|
||||
const createItems = createDomains?.length
|
||||
? createDomains.map((domain) => {
|
||||
const newFriendlyName = hass.localize(
|
||||
const primary = hass.localize(
|
||||
"ui.components.entity.entity-picker.create_helper",
|
||||
{
|
||||
domain: isHelperDomain(domain)
|
||||
|
@ -180,16 +240,14 @@ export class HaEntityPicker extends LitElement {
|
|||
);
|
||||
|
||||
return {
|
||||
...FAKE_ENTITY,
|
||||
entity_id: CREATE_ID + domain,
|
||||
state: "on",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
friendly_name: newFriendlyName,
|
||||
attributes: {
|
||||
icon: "mdi:plus",
|
||||
},
|
||||
strings: [domain, newFriendlyName],
|
||||
primary: primary,
|
||||
label: primary,
|
||||
secondary: this.hass.localize(
|
||||
"ui.components.entity.entity-picker.new_entity"
|
||||
),
|
||||
icon_path: mdiPlus,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
@ -197,21 +255,14 @@ export class HaEntityPicker extends LitElement {
|
|||
if (!entityIds.length) {
|
||||
return [
|
||||
{
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
friendly_name: this.hass!.localize(
|
||||
...FAKE_ENTITY,
|
||||
primary: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
attributes: {
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
icon: "mdi:magnify",
|
||||
},
|
||||
strings: [],
|
||||
label: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
icon_path: mdiMagnify,
|
||||
},
|
||||
...createItems,
|
||||
];
|
||||
|
@ -241,19 +292,49 @@ export class HaEntityPicker extends LitElement {
|
|||
);
|
||||
}
|
||||
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
states = entityIds
|
||||
.map((key) => {
|
||||
const friendly_name = computeStateName(hass!.states[key]) || key;
|
||||
.map<EntityPickerItem>((entityId) => {
|
||||
const stateObj = hass!.states[entityId];
|
||||
|
||||
const { area, device } = getEntityContext(stateObj, hass);
|
||||
|
||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||
const entityName = computeEntityName(stateObj, hass);
|
||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
const translatedDomain = domainToName(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
|
||||
return {
|
||||
...hass!.states[key],
|
||||
friendly_name,
|
||||
strings: [key, friendly_name],
|
||||
...hass!.states[entityId],
|
||||
primary: primary,
|
||||
secondary:
|
||||
secondary ||
|
||||
this.hass.localize("ui.components.device-picker.no_area"),
|
||||
label: friendlyName,
|
||||
translated_domain: translatedDomain,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("-"),
|
||||
entity_name: entityName || deviceName,
|
||||
area_name: areaName,
|
||||
device_name: deviceName,
|
||||
friendly_name: friendlyName,
|
||||
show_entity_id: hass.userData?.showEntityIdPicker,
|
||||
};
|
||||
})
|
||||
.sort((entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
entityA.friendly_name,
|
||||
entityB.friendly_name,
|
||||
entityA.sorting_label!,
|
||||
entityB.sorting_label!,
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
|
@ -291,21 +372,14 @@ export class HaEntityPicker extends LitElement {
|
|||
if (!states.length) {
|
||||
return [
|
||||
{
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
friendly_name: this.hass!.localize(
|
||||
...FAKE_ENTITY,
|
||||
primary: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
attributes: {
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
icon: "mdi:magnify",
|
||||
},
|
||||
strings: [],
|
||||
label: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
icon_path: mdiMagnify,
|
||||
},
|
||||
...createItems,
|
||||
];
|
||||
|
@ -331,8 +405,8 @@ export class HaEntityPicker extends LitElement {
|
|||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
|
||||
this._states = this._getStates(
|
||||
if (!this._initialItems || (changedProps.has("_opened") && this._opened)) {
|
||||
this._items = this._getItems(
|
||||
this._opened,
|
||||
this.hass,
|
||||
this.includeDomains,
|
||||
|
@ -344,10 +418,10 @@ export class HaEntityPicker extends LitElement {
|
|||
this.excludeEntities,
|
||||
this.createDomains
|
||||
);
|
||||
if (this._initedStates) {
|
||||
this.comboBox.filteredItems = this._states;
|
||||
if (this._initialItems) {
|
||||
this.comboBox.filteredItems = this._items;
|
||||
}
|
||||
this._initedStates = true;
|
||||
this._initialItems = true;
|
||||
}
|
||||
|
||||
if (changedProps.has("createDomains") && this.createDomains?.length) {
|
||||
|
@ -367,10 +441,11 @@ export class HaEntityPicker extends LitElement {
|
|||
: this.label}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._states}
|
||||
.filteredItems=${this._items}
|
||||
.renderer=${this._rowRenderer}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
|
@ -407,12 +482,49 @@ export class HaEntityPicker extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private _fuseKeys = [
|
||||
"entity_name",
|
||||
"device_name",
|
||||
"area_name",
|
||||
"translated_domain",
|
||||
"friendly_name", // for backwards compatibility
|
||||
"entity_id", // for technical search
|
||||
];
|
||||
|
||||
private _fuseIndex = memoizeOne((states: EntityPickerItem[]) =>
|
||||
Fuse.createIndex(this._fuseKeys, states)
|
||||
);
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const target = ev.target as HaComboBox;
|
||||
const filterString = ev.detail.value.trim().toLowerCase();
|
||||
target.filteredItems = filterString.length
|
||||
? fuzzyFilterSort<HassEntityWithCachedName>(filterString, this._states)
|
||||
: this._states;
|
||||
const filterString = ev.detail.value.trim().toLowerCase() as string;
|
||||
|
||||
const minLength = 2;
|
||||
|
||||
const searchTerms = (filterString.split(" ") ?? []).filter(
|
||||
(term) => term.length >= minLength
|
||||
);
|
||||
|
||||
if (searchTerms.length > 0) {
|
||||
const index = this._fuseIndex(this._items);
|
||||
|
||||
const options: IFuseOptions<EntityPickerItem> = {
|
||||
isCaseSensitive: false,
|
||||
threshold: 0.3,
|
||||
ignoreDiacritics: true,
|
||||
minMatchCharLength: minLength,
|
||||
};
|
||||
|
||||
const fuse = new Fuse(this._items, options, index);
|
||||
const results = fuse.search({
|
||||
$and: searchTerms.map((term) => ({
|
||||
$or: this._fuseKeys.map((key) => ({ [key]: term })),
|
||||
})),
|
||||
});
|
||||
target.filteredItems = results.map((result) => result.item);
|
||||
} else {
|
||||
target.filteredItems = this._items;
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: string | undefined) {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
import type { StatisticsMetaData } from "../../data/recorder";
|
||||
import { getStatisticIds, getStatisticLabel } from "../../data/recorder";
|
||||
import type { ValueChangedEvent, HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
|
||||
interface StatisticItem extends ScorableTextItem {
|
||||
id: string;
|
||||
|
@ -99,16 +99,18 @@ export class HaStatisticPicker extends LitElement {
|
|||
@state() private _filteredItems?: StatisticItem[] = undefined;
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<StatisticItem> = (item) =>
|
||||
html`<mwc-list-item graphic="avatar" twoline>
|
||||
html`<ha-combo-box-item type="button">
|
||||
${item.state
|
||||
? html`<state-badge
|
||||
slot="graphic"
|
||||
.stateObj=${item.state}
|
||||
.hass=${this.hass}
|
||||
></state-badge>`
|
||||
: ""}
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary"
|
||||
? html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item.state}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`
|
||||
: html`<span slot="start" style="width: 32px"></span>`}
|
||||
<span slot="headline">${item.name}</span>
|
||||
<span slot="supporting-text"
|
||||
>${item.id === "" || item.id === "__missing"
|
||||
? html`<a
|
||||
target="_blank"
|
||||
|
@ -120,7 +122,7 @@ export class HaStatisticPicker extends LitElement {
|
|||
>`
|
||||
: item.id}</span
|
||||
>
|
||||
</mwc-list-item>`;
|
||||
</ha-combo-box-item>`;
|
||||
|
||||
private _getStatistics = memoizeOne(
|
||||
(
|
||||
|
|
|
@ -79,6 +79,17 @@ export class StateBadge extends LitElement {
|
|||
</div>`;
|
||||
}
|
||||
|
||||
const cls = this.getClass();
|
||||
if (cls) {
|
||||
cls.forEach((toSet, className) => {
|
||||
if (!toSet) {
|
||||
this.classList.remove(className);
|
||||
} else {
|
||||
this.classList.add(className);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.icon) {
|
||||
return nothing;
|
||||
}
|
||||
|
@ -175,35 +186,57 @@ export class StateBadge extends LitElement {
|
|||
backgroundImage = `url(${imageUrl})`;
|
||||
this.icon = false;
|
||||
}
|
||||
|
||||
if (domain === "update") {
|
||||
this.style.borderRadius = "0";
|
||||
} else if (domain === "media_player" || domain === "camera") {
|
||||
this.style.borderRadius = "8%";
|
||||
}
|
||||
}
|
||||
|
||||
this._iconStyle = iconStyle;
|
||||
this.style.backgroundImage = backgroundImage;
|
||||
}
|
||||
|
||||
protected getClass() {
|
||||
const cls = new Map(
|
||||
["has-no-radius", "has-media-image", "has-image"].map((_cls) => [
|
||||
_cls,
|
||||
false,
|
||||
])
|
||||
);
|
||||
if (this.stateObj) {
|
||||
const domain = computeDomain(this.stateObj.entity_id);
|
||||
if (domain === "update") {
|
||||
cls.set("has-no-radius", true);
|
||||
} else if (domain === "media_player" || domain === "camera") {
|
||||
cls.set("has-media-image", true);
|
||||
} else if (this.style.backgroundImage !== "") {
|
||||
cls.set("has-image", true);
|
||||
}
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
iconColorCSS,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
width: 40px;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
border-radius: 50%;
|
||||
border-radius: var(--state-badge-border-radius, 50%);
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
background-size: cover;
|
||||
line-height: 40px;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
--state-inactive-color: initial;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
:host(.has-image) {
|
||||
border-radius: var(--state-badge-with-image-border-radius, 50%);
|
||||
}
|
||||
:host(.has-media-image) {
|
||||
border-radius: var(--state-badge-with-media-image-border-radius, 8%);
|
||||
}
|
||||
:host(.has-no-radius) {
|
||||
border-radius: 0;
|
||||
}
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
|
|
|
@ -10,20 +10,23 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
|
|||
import "./ha-alert";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-list-item";
|
||||
import "./ha-combo-box-item";
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) =>
|
||||
html`<ha-list-item twoline graphic="icon">
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.slug}</span>
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.name}</span>
|
||||
<span slot="supporting-text">${item.slug}</span>
|
||||
${item.icon
|
||||
? html`<img
|
||||
alt=""
|
||||
slot="graphic"
|
||||
.src="/api/hassio/addons/${item.slug}/icon"
|
||||
/>`
|
||||
: ""}
|
||||
</ha-list-item>`;
|
||||
? html`
|
||||
<img
|
||||
alt=""
|
||||
slot="start"
|
||||
.src="/api/hassio/addons/${item.slug}/icon"
|
||||
/>
|
||||
`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
@customElement("ha-addon-picker")
|
||||
class HaAddonPicker extends LitElement {
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { showAreaFilterDialog } from "../dialogs/area-filter/show-area-filter-dialog";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-next";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface AreaFilterValue {
|
||||
hidden?: string[];
|
||||
order?: string[];
|
||||
}
|
||||
|
||||
@customElement("ha-area-filter")
|
||||
export class HaAreaPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreaFilterValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const allAreasCount = Object.keys(this.hass.areas).length;
|
||||
const hiddenAreasCount = this.value?.hidden?.length ?? 0;
|
||||
|
||||
const description =
|
||||
hiddenAreasCount === 0
|
||||
? this.hass.localize("ui.components.area-filter.all_areas")
|
||||
: allAreasCount === hiddenAreasCount
|
||||
? this.hass.localize("ui.components.area-filter.no_areas")
|
||||
: this.hass.localize("ui.components.area-filter.area_count", {
|
||||
count: allAreasCount - hiddenAreasCount,
|
||||
});
|
||||
|
||||
return html`
|
||||
<ha-list-item
|
||||
tabindex="0"
|
||||
role="button"
|
||||
hasMeta
|
||||
twoline
|
||||
graphic="icon"
|
||||
@click=${this._edit}
|
||||
@keydown=${this._edit}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
<span>${this.label}</span>
|
||||
<span slot="secondary">${description}</span>
|
||||
<ha-icon-next
|
||||
slot="meta"
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _edit(ev) {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const value = await showAreaFilterDialog(this, {
|
||||
title: this.label,
|
||||
initialValue: this.value,
|
||||
});
|
||||
if (!value) return;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-list-item {
|
||||
--mdc-list-side-padding-left: 8px;
|
||||
--mdc-list-side-padding-right: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-area-filter": HaAreaPicker;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
|
|||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-floor-icon";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-list-item";
|
||||
|
@ -125,38 +126,38 @@ export class HaAreaFloorPicker extends LitElement {
|
|||
private _rowRenderer: ComboBoxLitRenderer<FloorAreaEntry> = (item) => {
|
||||
const rtl = computeRTL(this.hass);
|
||||
return html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
<ha-combo-box-item
|
||||
type="button"
|
||||
style=${item.type === "area" && item.hasFloor
|
||||
? rtl
|
||||
? "--mdc-list-side-padding-right: 48px;"
|
||||
: "--mdc-list-side-padding-left: 48px;"
|
||||
? "--md-list-item-leading-space: 48px;"
|
||||
: ""}
|
||||
>
|
||||
${item.type === "area" && item.hasFloor
|
||||
? html`<ha-tree-indicator
|
||||
style=${styleMap({
|
||||
width: "48px",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
left: rtl ? undefined : "8px",
|
||||
right: rtl ? "8px" : undefined,
|
||||
transform: rtl ? "scaleX(-1)" : "",
|
||||
})}
|
||||
.end=${item.lastArea}
|
||||
slot="graphic"
|
||||
></ha-tree-indicator>`
|
||||
? html`
|
||||
<ha-tree-indicator
|
||||
style=${styleMap({
|
||||
width: "48px",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
left: rtl ? undefined : "4px",
|
||||
right: rtl ? "4px" : undefined,
|
||||
transform: rtl ? "scaleX(-1)" : "",
|
||||
})}
|
||||
.end=${item.lastArea}
|
||||
slot="start"
|
||||
></ha-tree-indicator>
|
||||
`
|
||||
: nothing}
|
||||
${item.type === "floor"
|
||||
? html`<ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon>`
|
||||
? html`<ha-floor-icon slot="start" .floor=${item}></ha-floor-icon>`
|
||||
: item.icon
|
||||
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
${item.name}
|
||||
</ha-list-item>
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
|||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
|
@ -24,22 +23,21 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
|
|||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-list-item";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
|
||||
html`<ha-list-item
|
||||
graphic="icon"
|
||||
class=${classMap({ "add-new": item.area_id === ADD_NEW_ID })}
|
||||
>
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
${item.icon
|
||||
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon slot="graphic" .path=${mdiTextureBox}></ha-svg-icon>`}
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon slot="start" .path=${mdiTextureBox}></ha-svg-icon>`}
|
||||
${item.name}
|
||||
</ha-list-item>`;
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
const NO_ITEMS_ID = "___NO_ITEMS___";
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-expansion-panel";
|
||||
import "./ha-items-display-editor";
|
||||
import type { DisplayItem, DisplayValue } from "./ha-items-display-editor";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface AreasDisplayValue {
|
||||
hidden?: string[];
|
||||
order?: string[];
|
||||
}
|
||||
|
||||
@customElement("ha-areas-display-editor")
|
||||
export class HaAreasDisplayEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreasDisplayValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public expanded = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-navigation-button" })
|
||||
public showNavigationButton = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const compare = areaCompare(this.hass.areas);
|
||||
|
||||
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
|
||||
const items: DisplayItem[] = areas.map((area) => {
|
||||
const { floor } = getAreaContext(area.area_id, this.hass!);
|
||||
return {
|
||||
value: area.area_id,
|
||||
label: area.name,
|
||||
icon: area.icon ?? undefined,
|
||||
iconPath: mdiTextureBox,
|
||||
description: floor?.name,
|
||||
};
|
||||
});
|
||||
|
||||
const value: DisplayValue = {
|
||||
order: this.value?.order ?? [],
|
||||
hidden: this.value?.hidden ?? [],
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
outlined
|
||||
.header=${this.label}
|
||||
.expanded=${this.expanded}
|
||||
>
|
||||
<ha-svg-icon slot="leading-icon" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
<ha-items-display-editor
|
||||
.hass=${this.hass}
|
||||
.items=${items}
|
||||
.value=${value}
|
||||
@value-changed=${this._areaDisplayChanged}
|
||||
.showNavigationButton=${this.showNavigationButton}
|
||||
></ha-items-display-editor>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _areaDisplayChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value as DisplayValue;
|
||||
const newValue: AreasDisplayValue = {
|
||||
...this.value,
|
||||
...value,
|
||||
};
|
||||
if (newValue.hidden?.length === 0) {
|
||||
delete newValue.hidden;
|
||||
}
|
||||
if (newValue.order?.length === 0) {
|
||||
delete newValue.order;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-areas-display-editor": HaAreasDisplayEditor;
|
||||
}
|
||||
}
|
|
@ -295,6 +295,7 @@ export class HaAssistChat extends LitElement {
|
|||
this._addMessage(userMessage);
|
||||
this.requestUpdate("_audioRecorder");
|
||||
|
||||
let continueConversation = false;
|
||||
let hassMessage = {
|
||||
who: "hass",
|
||||
text: "…",
|
||||
|
@ -369,6 +370,8 @@ export class HaAssistChat extends LitElement {
|
|||
|
||||
if (event.type === "intent-end") {
|
||||
this._conversationId = event.data.intent_output.conversation_id;
|
||||
continueConversation =
|
||||
event.data.intent_output.continue_conversation;
|
||||
const plain = event.data.intent_output.response.speech?.plain;
|
||||
if (plain) {
|
||||
hassMessage.text = plain.speech;
|
||||
|
@ -380,7 +383,12 @@ export class HaAssistChat extends LitElement {
|
|||
const url = event.data.tts_output.url;
|
||||
this._audio = new Audio(url);
|
||||
this._audio.play();
|
||||
this._audio.addEventListener("ended", this._unloadAudio);
|
||||
this._audio.addEventListener("ended", () => {
|
||||
this._unloadAudio();
|
||||
if (continueConversation) {
|
||||
this._startListening();
|
||||
}
|
||||
});
|
||||
this._audio.addEventListener("pause", this._unloadAudio);
|
||||
this._audio.addEventListener("canplaythrough", this._playAudio);
|
||||
this._audio.addEventListener("error", this._audioError);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
|
@ -32,6 +33,10 @@ export class HaCameraStream extends LitElement {
|
|||
|
||||
@property({ attribute: false }) public stateObj?: CameraEntity;
|
||||
|
||||
@property({ attribute: false }) public aspectRatio?: number;
|
||||
|
||||
@property({ attribute: false }) public fitMode?: "cover" | "contain" | "fill";
|
||||
|
||||
@property({ type: Boolean, attribute: "controls" })
|
||||
public controls = false;
|
||||
|
||||
|
@ -101,6 +106,10 @@ export class HaCameraStream extends LitElement {
|
|||
: this._connected
|
||||
? computeMJPEGStreamUrl(this.stateObj)
|
||||
: this._posterUrl || ""}
|
||||
style=${styleMap({
|
||||
aspectRatio: this.aspectRatio,
|
||||
objectFit: this.fitMode,
|
||||
})}
|
||||
alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
|
||||
/>`;
|
||||
}
|
||||
|
@ -117,6 +126,8 @@ export class HaCameraStream extends LitElement {
|
|||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleHlsStreams}
|
||||
class=${stream.visible ? "" : "hidden"}
|
||||
.aspectRatio=${this.aspectRatio}
|
||||
.fitMode=${this.fitMode}
|
||||
></ha-hls-player>`;
|
||||
}
|
||||
|
||||
|
@ -131,6 +142,8 @@ export class HaCameraStream extends LitElement {
|
|||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleWebRtcStreams}
|
||||
class=${stream.visible ? "" : "hidden"}
|
||||
.aspectRatio=${this.aspectRatio}
|
||||
.fitMode=${this.fitMode}
|
||||
></ha-web-rtc-player>`;
|
||||
}
|
||||
|
||||
|
@ -259,6 +272,16 @@ export class HaCameraStream extends LitElement {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
ha-web-rtc-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ha-hls-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import { MdCircularProgress } from "@material/web/progress/circular-progress";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
export class HaCircularProgress extends MdCircularProgress {
|
||||
@property({ attribute: "aria-label", type: String }) public ariaLabel =
|
||||
"Loading";
|
||||
|
||||
@property() public size?: "tiny" | "small" | "medium" | "large";
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("size")) {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
this.style.setProperty("--md-circular-progress-size", "16px");
|
||||
break;
|
||||
case "small":
|
||||
this.style.setProperty("--md-circular-progress-size", "28px");
|
||||
break;
|
||||
case "medium":
|
||||
this.style.setProperty("--md-circular-progress-size", "48px");
|
||||
break;
|
||||
case "large":
|
||||
this.style.setProperty("--md-circular-progress-size", "68px");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
--md-sys-color-primary: var(--primary-color);
|
||||
--md-circular-progress-size: 48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-circular-progress": HaCircularProgress;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { HaMdListItem } from "./ha-md-list-item";
|
||||
|
||||
@customElement("ha-combo-box-item")
|
||||
export class HaComboBoxItem extends HaMdListItem {
|
||||
@property({ type: Boolean, reflect: true, attribute: "border-top" })
|
||||
public borderTop = false;
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
--md-list-item-two-line-container-height: 64px;
|
||||
}
|
||||
:host([border-top]) md-item {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
[slot="start"] {
|
||||
--paper-item-icon-color: var(--secondary-text-color);
|
||||
}
|
||||
[slot="headline"] {
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[slot="supporting-text"] {
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
::slotted(state-badge),
|
||||
::slotted(img) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-combo-box-item": HaComboBoxItem;
|
||||
}
|
||||
}
|
|
@ -16,8 +16,8 @@ import { customElement, property, query } from "lit/decorators";
|
|||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-list-item";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
|
@ -105,6 +105,9 @@ export class HaComboBox extends LitElement {
|
|||
|
||||
@property({ type: Boolean, reflect: true }) public opened = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-clear-icon" })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
|
||||
|
||||
@query("ha-textfield", true) private _inputElement!: HaTextField;
|
||||
|
@ -187,7 +190,7 @@ export class HaComboBox extends LitElement {
|
|||
>
|
||||
<slot name="icon" slot="leadingIcon"></slot>
|
||||
</ha-textfield>
|
||||
${this.value
|
||||
${this.value && !this.hideClearIcon
|
||||
? html`<ha-svg-icon
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
|
@ -204,6 +207,7 @@ export class HaComboBox extends LitElement {
|
|||
aria-expanded=${this.opened ? "true" : "false"}
|
||||
class="toggle-button"
|
||||
.path=${this.opened ? mdiMenuUp : mdiMenuDown}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._toggleOpen}
|
||||
></ha-svg-icon>
|
||||
</vaadin-combo-box-light>
|
||||
|
@ -212,10 +216,11 @@ export class HaComboBox extends LitElement {
|
|||
|
||||
private _defaultRowRenderer: ComboBoxLitRenderer<
|
||||
string | Record<string, any>
|
||||
> = (item) =>
|
||||
html`<ha-list-item>
|
||||
> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
${this.itemLabelPath ? item[this.itemLabelPath] : item}
|
||||
</ha-list-item>`;
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
|
@ -356,6 +361,10 @@ export class HaComboBox extends LitElement {
|
|||
:host([opened]) .toggle-button {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.toggle-button[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
.clear-button {
|
||||
--mdc-icon-size: 20px;
|
||||
top: -7px;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
|
@ -11,6 +10,7 @@ import type { ValueChangedEvent, HomeAssistant } from "../types";
|
|||
import { brandsUrl } from "../util/brands-url";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
|
||||
export interface ConfigEntryExtended extends ConfigEntry {
|
||||
localized_domain_name?: string;
|
||||
|
@ -48,18 +48,20 @@ class HaConfigEntryPicker extends LitElement {
|
|||
this._getConfigEntries();
|
||||
}
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<ConfigEntryExtended> = (item) =>
|
||||
html`<mwc-list-item twoline graphic="icon">
|
||||
<span
|
||||
>${item.title ||
|
||||
private _rowRenderer: ComboBoxLitRenderer<ConfigEntryExtended> = (
|
||||
item
|
||||
) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">
|
||||
${item.title ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.unnamed_entry"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">${item.localized_domain_name}</span>
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">${item.localized_domain_name}</span>
|
||||
<img
|
||||
alt=""
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
src=${brandsUrl({
|
||||
domain: item.domain,
|
||||
type: "icon",
|
||||
|
@ -70,7 +72,8 @@ class HaConfigEntryPicker extends LitElement {
|
|||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</mwc-list-item>`;
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
protected render() {
|
||||
if (!this._configEntries) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
||||
import { mdiMenuDown } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
|
@ -24,6 +25,16 @@ export class HaControlSelectMenu extends SelectBase {
|
|||
@property({ type: Boolean, attribute: "hide-label" })
|
||||
public hideLabel = false;
|
||||
|
||||
@property() public options;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.get("options")) {
|
||||
this.layoutOptions();
|
||||
this.selectByValue(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
public override render() {
|
||||
const classes = {
|
||||
"select-disabled": this.disabled,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue