Compare commits
333 Commits
allow-to-s
...
20231002.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
75c43d15e1 | ||
![]() |
e288b003d8 | ||
![]() |
4aa8518ed6 | ||
![]() |
6acbf6395c | ||
![]() |
2030feabf7 | ||
![]() |
46d1dbcb47 | ||
![]() |
2f6297ec17 | ||
![]() |
a3400a2f9c | ||
![]() |
c345f41416 | ||
![]() |
292cdc7621 | ||
![]() |
c5ba74e0b4 | ||
![]() |
41b24de559 | ||
![]() |
4fe7b18161 | ||
![]() |
399a979c33 | ||
![]() |
03c5482860 | ||
![]() |
a116a50604 | ||
![]() |
0dfa292c40 | ||
![]() |
5914a6c1a4 | ||
![]() |
8daff17d6a | ||
![]() |
59bd852e7a | ||
![]() |
246fe2861e | ||
![]() |
dbf623ada2 | ||
![]() |
c848356a6d | ||
![]() |
47022d3a04 | ||
![]() |
d732bd4776 | ||
![]() |
5a5265723c | ||
![]() |
30c6e4e35e | ||
![]() |
c5f909d89f | ||
![]() |
13a691606f | ||
![]() |
44748df3ac | ||
![]() |
4f3dc82fd9 | ||
![]() |
b2e260d6ba | ||
![]() |
7111a21173 | ||
![]() |
ad51d313a1 | ||
![]() |
60345f3fe8 | ||
![]() |
956723cf15 | ||
![]() |
dd6a69ea03 | ||
![]() |
98bd08c9dd | ||
![]() |
6b31c07459 | ||
![]() |
c3b41afb68 | ||
![]() |
46d8f2eefb | ||
![]() |
0ffc7b59d6 | ||
![]() |
74be4ae20a | ||
![]() |
5455ce2e0f | ||
![]() |
01405d96b6 | ||
![]() |
965f893a65 | ||
![]() |
0b6813d9dc | ||
![]() |
cbd424ff5a | ||
![]() |
b899e39a9e | ||
![]() |
49a66961e2 | ||
![]() |
c69fb77b62 | ||
![]() |
d794ec3408 | ||
![]() |
d8c98d8f96 | ||
![]() |
acb32ae5c8 | ||
![]() |
c567a61dd7 | ||
![]() |
2a8d98307e | ||
![]() |
5aaf0cd579 | ||
![]() |
f38e4dcf54 | ||
![]() |
1df1ce5423 | ||
![]() |
a68381a4d9 | ||
![]() |
f7604b136e | ||
![]() |
362d950515 | ||
![]() |
22f9dbd65d | ||
![]() |
579050bfc7 | ||
![]() |
6b33b4e656 | ||
![]() |
dac7c0f5fd | ||
![]() |
aaceff0d23 | ||
![]() |
48e5cc6b63 | ||
![]() |
799a0933ba | ||
![]() |
940618f72d | ||
![]() |
d8ff69b65d | ||
![]() |
391aca6388 | ||
![]() |
071d078e84 | ||
![]() |
61982bcb77 | ||
![]() |
1c79fcc244 | ||
![]() |
f3513e3e52 | ||
![]() |
b3b10fa2ef | ||
![]() |
0ffabcc055 | ||
![]() |
9da8499004 | ||
![]() |
04d1fccb87 | ||
![]() |
fdf1eb5170 | ||
![]() |
4e2877b035 | ||
![]() |
467059f515 | ||
![]() |
e6db63bb63 | ||
![]() |
78ddec2c8c | ||
![]() |
9217d5bf40 | ||
![]() |
90d01e4b63 | ||
![]() |
e7960bf8c0 | ||
![]() |
bf12eaa1b3 | ||
![]() |
6179c75182 | ||
![]() |
4b5c7021ff | ||
![]() |
3349031cbd | ||
![]() |
5e107d43d7 | ||
![]() |
e46f2cd9bf | ||
![]() |
713ebfcc22 | ||
![]() |
46e4eafe95 | ||
![]() |
e6fd18e23b | ||
![]() |
71cd71dfd5 | ||
![]() |
1019ccfd26 | ||
![]() |
577c1d8522 | ||
![]() |
63f0b469cc | ||
![]() |
e688417863 | ||
![]() |
a19633e2d4 | ||
![]() |
8797142cca | ||
![]() |
2a7403b6fd | ||
![]() |
22efe14149 | ||
![]() |
7cce24bcd1 | ||
![]() |
b8f0bb66cd | ||
![]() |
b950f990b4 | ||
![]() |
b511e7a37d | ||
![]() |
50f4b78f2e | ||
![]() |
7b0b4cdfe4 | ||
![]() |
c60e5c4c61 | ||
![]() |
709a63e6da | ||
![]() |
f689eed073 | ||
![]() |
cd55eee2fc | ||
![]() |
40bb6566b8 | ||
![]() |
cf27e68748 | ||
![]() |
472ed2fe82 | ||
![]() |
d0a60984ed | ||
![]() |
24d401061c | ||
![]() |
2352d05573 | ||
![]() |
87d53e38c4 | ||
![]() |
db3c535884 | ||
![]() |
158b24f902 | ||
![]() |
19c4ed4690 | ||
![]() |
eae4ca1271 | ||
![]() |
db272e3e18 | ||
![]() |
0276430ab5 | ||
![]() |
db7caf1c32 | ||
![]() |
7176a51fec | ||
![]() |
4a6539d75b | ||
![]() |
850699ea70 | ||
![]() |
c17cc22f88 | ||
![]() |
9e3f2d5cb7 | ||
![]() |
0677c9c7b0 | ||
![]() |
af7e385884 | ||
![]() |
35496ead23 | ||
![]() |
ba88fef09b | ||
![]() |
ad0e59c8f4 | ||
![]() |
14e6f5e8ca | ||
![]() |
41403a5d35 | ||
![]() |
3c48157793 | ||
![]() |
3a07af6ad2 | ||
![]() |
c1c05f8d22 | ||
![]() |
29aed5371c | ||
![]() |
76c878df57 | ||
![]() |
8acf557137 | ||
![]() |
d6e7ebe71d | ||
![]() |
085b26d5ea | ||
![]() |
32472ca627 | ||
![]() |
c3c4bb4421 | ||
![]() |
f7f1a0c32d | ||
![]() |
d4872b177f | ||
![]() |
5bb8c51d25 | ||
![]() |
4e62370d18 | ||
![]() |
77c08fd00f | ||
![]() |
d8894a0078 | ||
![]() |
4fd9c63633 | ||
![]() |
5e1583f925 | ||
![]() |
5d5894cae6 | ||
![]() |
5417513f49 | ||
![]() |
546ba8f12f | ||
![]() |
a398b37380 | ||
![]() |
321f35f30e | ||
![]() |
82dfb06a04 | ||
![]() |
e666aac1bd | ||
![]() |
9e9a0e377e | ||
![]() |
ba3f9a318b | ||
![]() |
f3b4eefb72 | ||
![]() |
6ac1db6953 | ||
![]() |
1b42189dd6 | ||
![]() |
0d893b3d2b | ||
![]() |
7b167a4d7e | ||
![]() |
8e2f1026e7 | ||
![]() |
fe3a63af80 | ||
![]() |
f90ab60354 | ||
![]() |
5da4e1860a | ||
![]() |
6dcb7f2273 | ||
![]() |
53ae7e5a0c | ||
![]() |
56381f9914 | ||
![]() |
be31aecf00 | ||
![]() |
cc5fffc174 | ||
![]() |
dd8a50af31 | ||
![]() |
c8feded4f2 | ||
![]() |
0d0fe75f4e | ||
![]() |
fb69deb617 | ||
![]() |
c291af5d97 | ||
![]() |
6d63028406 | ||
![]() |
3917739ad2 | ||
![]() |
e98e59a265 | ||
![]() |
16ed60902d | ||
![]() |
6c7efc17c2 | ||
![]() |
d187aa0ac6 | ||
![]() |
9c60a047c1 | ||
![]() |
1825749036 | ||
![]() |
93846a2867 | ||
![]() |
f3ed0160af | ||
![]() |
38b275f7f9 | ||
![]() |
c3a36efaa4 | ||
![]() |
68fa67e77a | ||
![]() |
806cebb024 | ||
![]() |
fa788a8223 | ||
![]() |
dfbaee1649 | ||
![]() |
cfb698d0a6 | ||
![]() |
63c3d6406d | ||
![]() |
d817e92a57 | ||
![]() |
96597b3963 | ||
![]() |
40c7bc08d9 | ||
![]() |
b8cd1760f7 | ||
![]() |
24dd45c8cd | ||
![]() |
e06bd41b5e | ||
![]() |
c0793fad83 | ||
![]() |
e002c5d96c | ||
![]() |
099e317d17 | ||
![]() |
ca1a183512 | ||
![]() |
c1cacf735e | ||
![]() |
515cfdb6d1 | ||
![]() |
3a6cffd6c1 | ||
![]() |
c84a826937 | ||
![]() |
c485e8d03e | ||
![]() |
2ab67328d4 | ||
![]() |
d350c35c4e | ||
![]() |
034ce56da5 | ||
![]() |
ae9fcebfd5 | ||
![]() |
6197b55da8 | ||
![]() |
4e5d57b5f3 | ||
![]() |
7040c6d469 | ||
![]() |
6f99a39b55 | ||
![]() |
7483833dcd | ||
![]() |
38fb48b231 | ||
![]() |
166acee1c6 | ||
![]() |
916a6df39b | ||
![]() |
70f37158fb | ||
![]() |
5011bba20e | ||
![]() |
8897bc703d | ||
![]() |
ea6e7d441a | ||
![]() |
f91396c986 | ||
![]() |
4598b530af | ||
![]() |
dfabb4bc36 | ||
![]() |
66e0100c95 | ||
![]() |
a08a989ef5 | ||
![]() |
000c28abf9 | ||
![]() |
6b67397c83 | ||
![]() |
f68823a09e | ||
![]() |
fc1782e676 | ||
![]() |
b4975344a1 | ||
![]() |
2dc08d782f | ||
![]() |
ed92958735 | ||
![]() |
5ce31f3177 | ||
![]() |
370ec9cd98 | ||
![]() |
52c12b5659 | ||
![]() |
3de4cfbc00 | ||
![]() |
4215854414 | ||
![]() |
88eba92f57 | ||
![]() |
f773c968f9 | ||
![]() |
bbb6fccaec | ||
![]() |
aa2b2b0d16 | ||
![]() |
5cc06ebf0b | ||
![]() |
85977e505b | ||
![]() |
3249a5225f | ||
![]() |
7e7205627a | ||
![]() |
d33430e53f | ||
![]() |
e3f53e90e2 | ||
![]() |
811edfcc0f | ||
![]() |
2483249b5f | ||
![]() |
3534617f81 | ||
![]() |
216a3c4c7e | ||
![]() |
567bd9831f | ||
![]() |
98d1a55d35 | ||
![]() |
92358b4859 | ||
![]() |
eca3ec7f98 | ||
![]() |
bfcdbbd70b | ||
![]() |
e764076b1a | ||
![]() |
693c77ce1c | ||
![]() |
a725b6c9de | ||
![]() |
014bbf12ce | ||
![]() |
07dceb8e6d | ||
![]() |
53f18bec53 | ||
![]() |
ac3e858738 | ||
![]() |
c76b2fb357 | ||
![]() |
5f015ac9af | ||
![]() |
ac7c354bfc | ||
![]() |
dddee87de3 | ||
![]() |
e8bd77a84e | ||
![]() |
46a036ddbe | ||
![]() |
bf912f7bd3 | ||
![]() |
196c15ff3e | ||
![]() |
d0a6e727f2 | ||
![]() |
09697148cf | ||
![]() |
76093d898d | ||
![]() |
00c69c0fc3 | ||
![]() |
93dd119ce5 | ||
![]() |
e4f3211e9f | ||
![]() |
c6ecdc9d5d | ||
![]() |
6bdd2d234d | ||
![]() |
9d169bcbeb | ||
![]() |
5c06ec1084 | ||
![]() |
38a317b7e7 | ||
![]() |
593b176ab8 | ||
![]() |
1a15c8da8c | ||
![]() |
060e67397a | ||
![]() |
d6de29ca8a | ||
![]() |
220767b347 | ||
![]() |
79e1fbe076 | ||
![]() |
cd19894ab0 | ||
![]() |
705b6aeb4b | ||
![]() |
6e27fbe10f | ||
![]() |
bbb99a6eee | ||
![]() |
8411efc1c3 | ||
![]() |
88721df637 | ||
![]() |
265faddfa9 | ||
![]() |
6584dc70b7 | ||
![]() |
d6b4dbe6a2 | ||
![]() |
d579f93aa7 | ||
![]() |
b91261a789 | ||
![]() |
872128d9a8 | ||
![]() |
4972db4648 | ||
![]() |
821cd7fe05 | ||
![]() |
8c24ffa710 | ||
![]() |
d50a130345 | ||
![]() |
ee8997fbd2 | ||
![]() |
613cf932b5 | ||
![]() |
12b61aea2f | ||
![]() |
51d9271c83 | ||
![]() |
bd5264308f | ||
![]() |
2c17d2fead | ||
![]() |
9f0b9782a0 | ||
![]() |
88ff4c2fa8 | ||
![]() |
cba246fc7f | ||
![]() |
7d80eb06b0 | ||
![]() |
a181189a49 | ||
![]() |
626b51112f |
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,9 +2,7 @@
|
||||
You are amazing! Thanks for contributing to our project!
|
||||
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
|
||||
-->
|
||||
|
||||
## Breaking change
|
||||
|
||||
<!--
|
||||
If your PR contains a breaking change for existing users, it is important
|
||||
to tell them what breaks, how to make it work again and why we did this.
|
||||
@@ -13,8 +11,8 @@
|
||||
Note: Remove this section if this PR is NOT a breaking change.
|
||||
-->
|
||||
|
||||
## Proposed change
|
||||
|
||||
## Proposed change
|
||||
<!--
|
||||
Describe the big picture of your changes here to communicate to the
|
||||
maintainers why we should accept this pull request. If it fixes a bug
|
||||
@@ -22,8 +20,8 @@
|
||||
in the additional information section.
|
||||
-->
|
||||
|
||||
## Type of change
|
||||
|
||||
## Type of change
|
||||
<!--
|
||||
What type of change does your PR introduce to the Home Assistant frontend?
|
||||
NOTE: Please, check only 1! box!
|
||||
@@ -38,7 +36,6 @@
|
||||
- [ ] Code quality improvements to existing code or addition of tests
|
||||
|
||||
## Example configuration
|
||||
|
||||
<!--
|
||||
Supplying a configuration snippet, makes it easier for a maintainer to test
|
||||
your PR.
|
||||
@@ -49,7 +46,6 @@
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
<!--
|
||||
Details are important, and help maintainers processing your PR.
|
||||
Please be sure to fill out additional details, if applicable.
|
||||
@@ -60,7 +56,6 @@
|
||||
- Link to documentation pull request:
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--
|
||||
Put an `x` in the boxes that apply. You can also fill these out after
|
||||
creating the PR. If you're unsure about any of them, don't hesitate to ask.
|
||||
|
8
.github/workflows/cast_deployment.yaml
vendored
@@ -21,12 +21,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
18
.github/workflows/ci.yaml
vendored
@@ -24,9 +24,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
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@v3.3.1
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: |
|
||||
node_modules/.cache/prettier
|
||||
@@ -55,9 +55,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -73,9 +73,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -91,9 +91,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
8
.github/workflows/demo_deployment.yaml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -58,12 +58,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
4
.github/workflows/design_deployment.yaml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
4
.github/workflows/design_preview.yaml
vendored
@@ -21,10 +21,10 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
4
.github/workflows/nightly.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
6
.github/workflows/release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
uses: home-assistant/wheels@2023.10.1
|
||||
with:
|
||||
abi: cp311
|
||||
tag: musllinux_1_2
|
||||
|
2
.github/workflows/translations.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
@@ -1,3 +1,4 @@
|
||||
CLA.md
|
||||
CODE_OF_CONDUCT.md
|
||||
LICENSE.md
|
||||
PULL_REQUEST_TEMPLATE.md
|
@@ -8,4 +8,4 @@ plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
||||
|
@@ -8,7 +8,7 @@ module.exports.sourceMapURL = () => {
|
||||
const ref = env.version().endsWith("dev")
|
||||
? process.env.GITHUB_SHA || "dev"
|
||||
: env.version();
|
||||
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}`;
|
||||
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}/`;
|
||||
};
|
||||
|
||||
// Files from NPM Packages that should not be imported
|
||||
@@ -100,6 +100,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
useBuiltIns: latestBuild ? false : "entry",
|
||||
corejs: latestBuild ? false : { version: "3.32", proposals: true },
|
||||
bugfixes: true,
|
||||
shippedProposals: true,
|
||||
},
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
|
@@ -1,10 +1,14 @@
|
||||
import fs from "fs/promises";
|
||||
import gulp from "gulp";
|
||||
import path from "path";
|
||||
import mapStream from "map-stream";
|
||||
import transform from "gulp-json-transform";
|
||||
import { LokaliseApi } from "@lokalise/node-api";
|
||||
import JSZip from "jszip";
|
||||
|
||||
const inDirFrontend = "translations/frontend";
|
||||
const inDirBackend = "translations/backend";
|
||||
const inDir = "translations";
|
||||
const inDirFrontend = `${inDir}/frontend`;
|
||||
const inDirBackend = `${inDir}/backend`;
|
||||
const srcMeta = "src/translations/translationMetadata.json";
|
||||
const encoding = "utf8";
|
||||
|
||||
@@ -68,8 +72,9 @@ gulp.task("convert-backend-translations", function () {
|
||||
});
|
||||
|
||||
gulp.task("check-translations-html", function () {
|
||||
// We exclude backend translations because they are not compliant with the HTML rule for now
|
||||
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
||||
return gulp
|
||||
.src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`])
|
||||
.pipe(checkHtml());
|
||||
});
|
||||
|
||||
gulp.task("check-all-files-exist", async function () {
|
||||
@@ -89,7 +94,83 @@ gulp.task("check-all-files-exist", async function () {
|
||||
await Promise.allSettled(writings);
|
||||
});
|
||||
|
||||
const lokaliseProjects = {
|
||||
backend: "130246255a974bd3b5e8a1.51616605",
|
||||
frontend: "3420425759f6d6d241f598.13594006",
|
||||
};
|
||||
|
||||
gulp.task("fetch-lokalise", async function () {
|
||||
let apiKey;
|
||||
try {
|
||||
apiKey =
|
||||
process.env.LOKALISE_TOKEN ||
|
||||
(await fs.readFile(".lokalise_token", { encoding }));
|
||||
} catch {
|
||||
throw new Error(
|
||||
"An Administrator Lokalise API token is required to download the latest set of translations. Place your token in a new file `.lokalise_token` in the repo root directory."
|
||||
);
|
||||
}
|
||||
const lokaliseApi = new LokaliseApi({ apiKey });
|
||||
|
||||
const mkdirPromise = Promise.all([
|
||||
fs.mkdir(inDirFrontend, { recursive: true }),
|
||||
fs.mkdir(inDirBackend, { recursive: true }),
|
||||
]);
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(lokaliseProjects).map(([project, projectId]) =>
|
||||
lokaliseApi
|
||||
.files()
|
||||
.download(projectId, {
|
||||
format: "json",
|
||||
original_filenames: false,
|
||||
replace_breaks: false,
|
||||
json_unescaped_slashes: true,
|
||||
export_empty_as: "skip",
|
||||
})
|
||||
.then((download) => fetch(download.bundle_url))
|
||||
.then((response) => {
|
||||
if (response.status === 200 || response.status === 0) {
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
})
|
||||
.then(JSZip.loadAsync)
|
||||
.then(async (contents) => {
|
||||
await mkdirPromise;
|
||||
return Promise.all(
|
||||
Object.keys(contents.files).map(async (filename) => {
|
||||
const file = contents.file(filename);
|
||||
if (!file) {
|
||||
// no file, probably a directory
|
||||
return Promise.resolve();
|
||||
}
|
||||
return file
|
||||
.async("nodebuffer")
|
||||
.then((content) =>
|
||||
fs.writeFile(
|
||||
path.join(
|
||||
inDir,
|
||||
project,
|
||||
filename.split("/").splice(-1)[0]
|
||||
),
|
||||
content,
|
||||
{ flag: "w", encoding }
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"check-downloaded-translations",
|
||||
gulp.series("check-translations-html", "check-all-files-exist")
|
||||
"download-translations",
|
||||
gulp.series(
|
||||
"fetch-lokalise",
|
||||
"convert-backend-translations",
|
||||
"check-translations-html",
|
||||
"check-all-files-exist"
|
||||
)
|
||||
);
|
||||
|
@@ -6,6 +6,8 @@ import presetEnv from "@babel/preset-env";
|
||||
import compilationTargets from "@babel/helper-compilation-targets";
|
||||
import coreJSCompat from "core-js-compat";
|
||||
import { logPlugin } from "@babel/preset-env/lib/debug.js";
|
||||
// eslint-disable-next-line import/no-relative-packages
|
||||
import shippedPolyfills from "../node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js";
|
||||
import { babelOptions } from "./bundle.cjs";
|
||||
|
||||
const detailsOpen = (heading) =>
|
||||
@@ -26,6 +28,22 @@ const dummyAPI = {
|
||||
targets: () => ({}),
|
||||
};
|
||||
|
||||
// Generate filter function based on proposal/method inputs
|
||||
// Copied and adapted from babel-plugin-polyfill-corejs3/esm/index.mjs
|
||||
const polyfillFilter = (method, proposals, shippedProposals) => (name) => {
|
||||
if (proposals || method === "entry-global") return true;
|
||||
if (shippedProposals && shippedPolyfills.default.has(name)) {
|
||||
return true;
|
||||
}
|
||||
if (name.startsWith("esnext.")) {
|
||||
const esName = `es.${name.slice(7)}`;
|
||||
// If its imaginative esName is not in latest compat data, it means the proposal is not stage 4
|
||||
return esName in coreJSCompat.data;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Log the plugins and polyfills for each build environment
|
||||
for (const buildType of ["Modern", "Legacy"]) {
|
||||
const browserslistEnv = buildType.toLowerCase();
|
||||
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
|
||||
@@ -46,7 +64,13 @@ for (const buildType of ["Modern", "Legacy"]) {
|
||||
const targets = compilationTargets.default(babelOpts?.targets, {
|
||||
browserslistEnv,
|
||||
});
|
||||
const polyfillList = coreJSCompat({ targets }).list;
|
||||
const polyfillList = coreJSCompat({ targets }).list.filter(
|
||||
polyfillFilter(
|
||||
`${presetEnvOpts.useBuiltIns}-global`,
|
||||
presetEnvOpts?.corejs?.proposals,
|
||||
presetEnvOpts?.shippedProposals
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
"The following %i polyfills may be injected by Babel:\n",
|
||||
polyfillList.length
|
||||
|
@@ -1,5 +1,6 @@
|
||||
const webpack = require("webpack");
|
||||
const { existsSync } = require("fs");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const log = require("fancy-log");
|
||||
@@ -164,6 +165,7 @@ const createWebpackConfig = ({
|
||||
"lit/directives/guard$": "lit/directives/guard.js",
|
||||
"lit/directives/cache$": "lit/directives/cache.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/directives/live$": "lit/directives/live.js",
|
||||
"lit/polyfill-support$": "lit/polyfill-support.js",
|
||||
"@lit-labs/virtualizer/layouts/grid":
|
||||
"@lit-labs/virtualizer/layouts/grid.js",
|
||||
@@ -191,19 +193,26 @@ const createWebpackConfig = ({
|
||||
// Since production source maps don't include sources, we need to point to them elsewhere
|
||||
// For dependencies, just provide the path (no source in browser)
|
||||
// Otherwise, point to the raw code on GitHub for browser to load
|
||||
devtoolModuleFilenameTemplate:
|
||||
!isTestBuild && isProdBuild
|
||||
? (info) => {
|
||||
const sourcePath = info.resourcePath.replace(/^\.\//, "");
|
||||
if (
|
||||
sourcePath.startsWith("node_modules") ||
|
||||
sourcePath.startsWith("webpack")
|
||||
) {
|
||||
return `no-source/${sourcePath}`;
|
||||
...Object.fromEntries(
|
||||
["", "Fallback"].map((v) => [
|
||||
`devtool${v}ModuleFilenameTemplate`,
|
||||
!isTestBuild && isProdBuild
|
||||
? (info) => {
|
||||
if (
|
||||
!path.isAbsolute(info.absoluteResourcePath) ||
|
||||
!existsSync(info.resourcePath) ||
|
||||
info.resourcePath.startsWith("./node_modules")
|
||||
) {
|
||||
// Source URLs are unknown for dependencies, so we use a relative URL with a
|
||||
// non - existent top directory. This results in a clean source tree in browser
|
||||
// dev tools, and they stay happy getting 404s with valid requests.
|
||||
return `/unknown${path.resolve("/", info.resourcePath)}`;
|
||||
}
|
||||
return new URL(info.resourcePath, bundle.sourceMapURL()).href;
|
||||
}
|
||||
return `${bundle.sourceMapURL()}/${sourcePath}`;
|
||||
}
|
||||
: undefined,
|
||||
: undefined,
|
||||
])
|
||||
),
|
||||
},
|
||||
experiments: {
|
||||
outputModule: true,
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
@@ -32,6 +32,8 @@ import { HassElement } from "../../../../src/state/hass-element";
|
||||
import { castContext } from "../cast_context";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
const DEFAULT_STRATEGY = "original-states";
|
||||
|
||||
let resourcesLoaded = false;
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@@ -258,7 +260,7 @@ export class HcMain extends HassElement {
|
||||
{
|
||||
strategy: {
|
||||
type: "energy",
|
||||
options: { show_date_selection: true },
|
||||
show_date_selection: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -320,10 +322,10 @@ export class HcMain extends HassElement {
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(
|
||||
{
|
||||
hass: this.hass!,
|
||||
narrow: false,
|
||||
type: DEFAULT_STRATEGY,
|
||||
},
|
||||
"original-states"
|
||||
this.hass!,
|
||||
{ narrow: false }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -8,25 +8,67 @@
|
||||
"src": "/static/icons/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/favicon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/favicon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/favicon-1024x1024.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"lang": "en-US",
|
||||
|
4
gallery/public/images/brand/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Note!
|
||||
|
||||
Note, the assets in this folder, are not part of the CC license this repository is shipped in.
|
||||
All rights reserved.
|
BIN
gallery/public/images/brand/logo-exclusion-zone.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
gallery/public/images/brand/logo-layout-variants.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
gallery/public/images/brand/logo.png
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB |
@@ -1,4 +1,3 @@
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
@@ -7,6 +6,7 @@ import "../../../src/components/ha-switch";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "./demo-card";
|
||||
import type { DemoCardConfig } from "./demo-card";
|
||||
import "../ha-demo-options";
|
||||
|
||||
@customElement("demo-cards")
|
||||
class DemoCards extends LitElement {
|
||||
@@ -20,20 +20,14 @@ class DemoCards extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<app-toolbar>
|
||||
<div class="filters">
|
||||
<ha-formfield label="Show config">
|
||||
<ha-switch
|
||||
.checked=${this._showConfig}
|
||||
@change=${this._showConfigToggled}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Dark theme">
|
||||
<ha-switch @change=${this._darkThemeToggled}> </ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<ha-demo-options>
|
||||
<ha-formfield label="Show config">
|
||||
<ha-switch @change=${this._showConfigToggled}> </ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Dark theme">
|
||||
<ha-switch @change=${this._darkThemeToggled}> </ha-switch>
|
||||
</ha-formfield>
|
||||
</ha-demo-options>
|
||||
<div id="container">
|
||||
<div class="cards">
|
||||
${this.configs.map(
|
||||
@@ -69,12 +63,6 @@ class DemoCards extends LitElement {
|
||||
demo-card {
|
||||
margin: 16px 16px 32px;
|
||||
}
|
||||
app-toolbar {
|
||||
background-color: var(--light-primary-color);
|
||||
}
|
||||
.filters {
|
||||
margin-left: 60px;
|
||||
}
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
@@ -1,93 +0,0 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
.root {
|
||||
display: flex;
|
||||
}
|
||||
#card {
|
||||
max-width: 400px;
|
||||
width: 100vw;
|
||||
}
|
||||
ha-card {
|
||||
width: 352px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
state-card-content {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
pre {
|
||||
width: 400px;
|
||||
margin: 0 16px;
|
||||
overflow: auto;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
@media only screen and (max-width: 800px) {
|
||||
.root {
|
||||
flex-direction: column;
|
||||
}
|
||||
pre {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="root">
|
||||
<div id="card">
|
||||
<ha-card>
|
||||
<state-card-content
|
||||
state-obj="[[_stateObj]]"
|
||||
hass="[[hass]]"
|
||||
in-dialog
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></more-info-content>
|
||||
</ha-card>
|
||||
</div>
|
||||
<template is="dom-if" if="[[showConfig]]">
|
||||
<pre>[[_jsonEntity(_stateObj)]]</pre>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
entityId: String,
|
||||
showConfig: Boolean,
|
||||
_stateObj: {
|
||||
type: Object,
|
||||
computed: "_getState(entityId, hass.states)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_getState(entityId, states) {
|
||||
return states[entityId];
|
||||
}
|
||||
|
||||
_jsonEntity(stateObj) {
|
||||
// We are caching some things on stateObj
|
||||
// (it sucks, we will remove in the future)
|
||||
const tmp = {};
|
||||
Object.keys(stateObj).forEach((key) => {
|
||||
if (key[0] !== "_") {
|
||||
tmp[key] = stateObj[key];
|
||||
}
|
||||
});
|
||||
return JSON.stringify(tmp, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-more-info", DemoMoreInfo);
|
93
gallery/src/components/demo-more-info.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
import "../ha-demo-options";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
@customElement("demo-more-info")
|
||||
class DemoMoreInfo extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public entityId!: string;
|
||||
|
||||
@property() public showConfig!: boolean;
|
||||
|
||||
render() {
|
||||
const state = this._getState(this.entityId, this.hass.states);
|
||||
return html`
|
||||
<div class="root">
|
||||
<div id="card">
|
||||
<ha-card>
|
||||
<state-card-content
|
||||
.stateObj=${state}
|
||||
.hass=${this.hass}
|
||||
in-dialog
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
.hass=${this.hass}
|
||||
.stateObj=${state}
|
||||
></more-info-content>
|
||||
</ha-card>
|
||||
</div>
|
||||
${this.showConfig ? html`<pre>${this._jsonEntity(state)}</pre>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _getState(entityId, states) {
|
||||
return states[entityId];
|
||||
}
|
||||
|
||||
private _jsonEntity(stateObj) {
|
||||
// We are caching some things on stateObj
|
||||
// (it sucks, we will remove in the future)
|
||||
const tmp = {};
|
||||
Object.keys(stateObj).forEach((key) => {
|
||||
if (key[0] !== "_") {
|
||||
tmp[key] = stateObj[key];
|
||||
}
|
||||
});
|
||||
return JSON.stringify(tmp, null, 2);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.root {
|
||||
display: flex;
|
||||
}
|
||||
#card {
|
||||
max-width: 400px;
|
||||
width: 100vw;
|
||||
}
|
||||
ha-card {
|
||||
width: 352px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
state-card-content {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
pre {
|
||||
width: 400px;
|
||||
margin: 0 16px;
|
||||
overflow: auto;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
@media only screen and (max-width: 800px) {
|
||||
.root {
|
||||
flex-direction: column;
|
||||
}
|
||||
pre {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info": DemoMoreInfo;
|
||||
}
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "./demo-more-info";
|
||||
|
||||
class DemoMoreInfos extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
#container {
|
||||
min-height: calc(100vh - 128px);
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
demo-more-info {
|
||||
margin: 16px 16px 32px;
|
||||
}
|
||||
app-toolbar {
|
||||
background-color: var(--light-primary-color);
|
||||
}
|
||||
.filters {
|
||||
margin-left: 60px;
|
||||
}
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class="filters">
|
||||
<ha-formfield label="Show entities">
|
||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Dark theme">
|
||||
<ha-switch on-change="_darkThemeToggled"> </ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div id="container">
|
||||
<div class="cards">
|
||||
<template is="dom-repeat" items="[[entities]]">
|
||||
<demo-more-info
|
||||
entity-id="[[item]]"
|
||||
show-config="[[_showConfig]]"
|
||||
hass="[[hass]]"
|
||||
></demo-more-info>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
entities: Array,
|
||||
hass: Object,
|
||||
_showConfig: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_showConfigToggled(ev) {
|
||||
this._showConfig = ev.target.checked;
|
||||
}
|
||||
|
||||
_darkThemeToggled(ev) {
|
||||
applyThemesOnElement(this.$.container, { themes: {} }, "default", {
|
||||
dark: ev.target.checked,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-more-infos", DemoMoreInfos);
|
87
gallery/src/components/demo-more-infos.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "./demo-more-info";
|
||||
import "../ha-demo-options";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
@customElement("demo-more-infos")
|
||||
class DemoMoreInfos extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public entities!: [];
|
||||
|
||||
@property({ attribute: false }) _showConfig: boolean = false;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-demo-options>
|
||||
<ha-formfield label="Show config">
|
||||
<ha-switch @change=${this._showConfigToggled}> </ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Dark theme">
|
||||
<ha-switch @change=${this._darkThemeToggled}> </ha-switch>
|
||||
</ha-formfield>
|
||||
</ha-demo-options>
|
||||
<div id="container">
|
||||
<div class="cards">
|
||||
${this.entities.map(
|
||||
(item) =>
|
||||
html`<demo-more-info
|
||||
.entityId=${item}
|
||||
.showConfig=${this._showConfig}
|
||||
.hass=${this.hass}
|
||||
></demo-more-info>`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
#container {
|
||||
min-height: calc(100vh - 128px);
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
demo-more-info {
|
||||
margin: 16px 16px 32px;
|
||||
}
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
_showConfigToggled(ev) {
|
||||
this._showConfig = ev.target.checked;
|
||||
}
|
||||
|
||||
_darkThemeToggled(ev) {
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector("#container"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: false,
|
||||
theme: "default",
|
||||
},
|
||||
"default",
|
||||
{
|
||||
dark: ev.target.checked,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-infos": DemoMoreInfos;
|
||||
}
|
||||
}
|
47
gallery/src/ha-demo-options.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import "@material/mwc-drawer";
|
||||
import "@material/mwc-top-app-bar-fixed";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/managers/notification-manager";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import "./components/page-description";
|
||||
|
||||
@customElement("ha-demo-options")
|
||||
class HaDemoOptions extends LitElement {
|
||||
render() {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
background-color: var(--light-primary-color);
|
||||
margin-left: 60px
|
||||
margin-right: 60px;
|
||||
display: var(--layout-horizontal_-_display);
|
||||
-ms-flex-direction: var(--layout-horizontal_-_-ms-flex-direction);
|
||||
-webkit-flex-direction: var(
|
||||
--layout-horizontal_-_-webkit-flex-direction
|
||||
);
|
||||
flex-direction: var(--layout-horizontal_-_flex-direction);
|
||||
-ms-flex-align: var(--layout-center_-_-ms-flex-align);
|
||||
-webkit-align-items: var(--layout-center_-_-webkit-align-items);
|
||||
align-items: var(--layout-center_-_align-items);
|
||||
position: relative;
|
||||
height: 64px;
|
||||
padding: 0 16px;
|
||||
pointer-events: none;
|
||||
font-size: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-demo-options": HaDemoOptions;
|
||||
}
|
||||
}
|
@@ -2,30 +2,86 @@
|
||||
title: "Logo"
|
||||
---
|
||||
|
||||
# Using our logo
|
||||
# Our logo
|
||||
|
||||
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
|
||||
As a community, we are proud of our logo. Follow these guidelines to ensure it always represents the identity of the Home Assistant project and community the best way possible.
|
||||
|
||||
[Download Logo](https://github.com/home-assistant/assets/tree/master/logo)
|
||||
|
||||

|
||||

|
||||
|
||||
## Using the icon
|
||||
Please note that this logo is not released under the CC license. All rights reserved.
|
||||
|
||||
Our icon is a shorter and most used version of our logo. The icon can exist without the wordmark, the wordmark should never exist without the icon.
|
||||
# Design
|
||||
|
||||

|
||||
At the core of the Home Assistant logomark is the Blue House with Antenna, the three most recognizable and distinct features of the previous logo throughout the past decade.
|
||||
|
||||
## Using the right variant
|
||||
### Blue
|
||||
|
||||
The pretty blue logo with a background shadow, pictured top left, is our primary logo. It should only be used with black, white, and non-duotone photography.
|
||||
Blue feels stable and essential. A bright sky blue is joyful, clear, and free of clouds.
|
||||
|
||||
When needed you can use our logo without a shadow, as seen as the second variant.
|
||||
### House
|
||||
|
||||
The outlined logo should only be used on packaging.
|
||||
Of all possible combinations of shapes, a home is best abstracted in the shape of a structure with a pitched roof. With the vast amount of logos based on this shape, the best we can do is to make it more iconic. The house is further simplified - there is no gable and there is no chimney - to an orthogonal shape with an elegant and deliberate proportion.
|
||||
|
||||
## Exclusion zone
|
||||
### Antenna
|
||||
|
||||
The logo needs some personal space. It's exclusion zone is equal to a quarter the height of the icon.
|
||||
Call it a tree, a set of nodes, a PCB, or an antenna. The antenna is the most recognizable and memorable part of the previous Home Assistant logo, and is an easily understandable symbol that conveys technologies that are smart, connected, and growing evergreen.
|
||||
|
||||

|
||||
# Usage
|
||||
|
||||
The default variation is the static colored wordmark in horizontal layout and dark text on a light background.
|
||||
|
||||
## Layout variations
|
||||
|
||||

|
||||
|
||||
The default layout is the wordmark in horizontal layout. It provides the clearest context to the brand identity of Home Assistant.
|
||||
|
||||
Use the logomark variant when the context is clear that the logo is about Home Assistant. For example, inside the Home Assistant app where users are already aware of where they are at, the logomark variant without the wordmark can be used. The logomark can exist without the wordmark, however, the wordmark should never exist without the icon.
|
||||
|
||||
Use the wordmark in vertical layout when the space available has an aspect ratio less than 4:3. For example, in a square space on a t-shirt where a logo is needed, since there is no established context of Home Assistant, the wordmark in vertical layout should be used.
|
||||
|
||||
Lastly, use the wordmark in vertical layout with small logomark when Home Assistant is displayed in context of other Home Assistant-related projects. For example, in a flowchart showing the voice pipeline, use this layout for Home Assistant and its other related projects.
|
||||
|
||||
## Color variations, backgrounds, and placement
|
||||
|
||||
The default color is the colored version on light background with dark text.
|
||||
|
||||
For backgrounds that are dark, for example, when it is used on a page in a dark theme, use the colored version on dark background with light text.
|
||||
|
||||
In printed materials where color is unavailable, use the monochrome color variations.
|
||||
|
||||
On background that are dark or photographic, use the light monochrome color on dark background variation.
|
||||
|
||||
On backgrounds that are light or photographic, use the colored version. Do not use the monochrome variations.
|
||||
|
||||
Do not enclose the logmark in a square or color or any confined backgrounds, except in specific situations enforced by another company's marketplace guidelines, for example, an iOS app icon.
|
||||
|
||||
Do not add drop shadow to the logomark or the wordmark. If legibility is compromised due to the background, change the background to provide more contrast, or in last resort, add a heavily blurred drop shadaow.
|
||||
|
||||
It should only be used with black, white, and non-duotone photography.
|
||||
|
||||
Unlike the previous version of our logo, no outlined variants are available. Use the monochrome variants in those spaces.
|
||||
|
||||
### Exclusion zone
|
||||
|
||||
The logo needs some personal space. Its exclusion zone is equal to a quarter the height of the icon.
|
||||
|
||||

|
||||
|
||||
## Animation
|
||||
|
||||
The default is the static variant.
|
||||
|
||||
Use the animated variant only for introductory purposes, for example, in the beginning of a video or on a loading screen.
|
||||
|
||||
Use the animated with sound variant only when sound is warranted in the user's context. For example, use it in the beginning of a video since sounds are expected in a video, but do not use it on a loading screen since sounds are not expected in a user interface.
|
||||
|
||||
Do not repeat the logo animation.
|
||||
|
||||
## Sizes and app icon variants
|
||||
|
||||
Special variants are created for specific contexts.
|
||||
|
||||
Use the tiny variants when the logomark is used in a very small space (16x16 dp), for example, the favicon of the Home Assistant website, a notification on Android, or the menubar of macOS.
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-chip";
|
||||
import "../../../../src/components/ha-chip-set";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
|
||||
|
||||
const chips: {
|
||||
icon?: string;
|
||||
|
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Control Number Buttons
|
||||
---
|
100
gallery/src/pages/components/ha-control-number-buttons.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-control-number-buttons";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
|
||||
const buttons: {
|
||||
id: string;
|
||||
label: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
class?: string;
|
||||
}[] = [
|
||||
{
|
||||
id: "basic",
|
||||
label: "Basic",
|
||||
},
|
||||
{
|
||||
id: "min_max_step",
|
||||
label: "With min/max and step",
|
||||
min: 5,
|
||||
max: 25,
|
||||
step: 0.5,
|
||||
},
|
||||
{
|
||||
id: "custom",
|
||||
label: "Custom",
|
||||
class: "custom",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-control-number-buttons")
|
||||
export class DemoHarControlNumberButtons extends LitElement {
|
||||
@state() value = 5;
|
||||
|
||||
private _valueChanged(ev) {
|
||||
this.value = ev.detail.value;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${repeat(buttons, (button) => {
|
||||
const { id, label, ...config } = button;
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label id=${id}>${label}</label>
|
||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
||||
<ha-control-number-buttons
|
||||
.value=${this.value}
|
||||
.min=${config.min}
|
||||
.max=${config.max}
|
||||
.step=${config.step}
|
||||
class=${ifDefined(config.class)}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${label}
|
||||
>
|
||||
</ha-control-number-buttons>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
.custom {
|
||||
color: #2196f3;
|
||||
--control-number-buttons-color: #2196f3;
|
||||
--control-number-buttons-background-color: #2196f3;
|
||||
--control-number-buttons-background-opacity: 0.1;
|
||||
--control-number-buttons-thickness: 100px;
|
||||
--control-number-buttons-border-radius: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-control-number-buttons": DemoHarControlNumberButtons;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Control Select Menu
|
||||
---
|
146
gallery/src/pages/components/ha-control-select-menu.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { mdiFan, mdiFanSpeed1, mdiFanSpeed2, mdiFanSpeed3 } from "@mdi/js";
|
||||
import { LitElement, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-control-select-menu";
|
||||
import "../../../../src/components/ha-list-item";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
|
||||
type SelectMenuOptions = {
|
||||
label: string;
|
||||
value: string;
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
type SelectMenu = {
|
||||
label: string;
|
||||
icon: string;
|
||||
class?: string;
|
||||
disabled?: boolean;
|
||||
options: SelectMenuOptions[];
|
||||
};
|
||||
|
||||
const selects: SelectMenu[] = [
|
||||
{
|
||||
label: "Basic select",
|
||||
icon: mdiFan,
|
||||
options: [
|
||||
{
|
||||
value: "low",
|
||||
label: "Low",
|
||||
},
|
||||
{
|
||||
value: "medium",
|
||||
label: "Medium",
|
||||
},
|
||||
{
|
||||
value: "high",
|
||||
label: "High",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Select with icons",
|
||||
icon: mdiFan,
|
||||
options: [
|
||||
{
|
||||
value: "low",
|
||||
label: "Low",
|
||||
icon: mdiFanSpeed1,
|
||||
},
|
||||
{
|
||||
value: "medium",
|
||||
label: "Medium",
|
||||
icon: mdiFanSpeed2,
|
||||
},
|
||||
{
|
||||
value: "high",
|
||||
label: "High",
|
||||
icon: mdiFanSpeed3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Disabled select",
|
||||
icon: mdiFan,
|
||||
options: [],
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-control-select-menu")
|
||||
export class DemoHaControlSelectMenu extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
${repeat(
|
||||
selects,
|
||||
(select) => html`
|
||||
<div class="card-content">
|
||||
<ha-control-select-menu
|
||||
.label=${select.label}
|
||||
?disabled=${select.disabled}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${select.icon}></ha-svg-icon>
|
||||
${select.options.map(
|
||||
(option) => html`
|
||||
<ha-list-item
|
||||
.value=${option.value}
|
||||
.graphic=${option.icon ? "icon" : undefined}
|
||||
>
|
||||
${option.icon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${option.icon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
${option.label ?? option.value}
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-control-select-menu>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
.custom {
|
||||
--control-button-icon-color: var(--primary-color);
|
||||
--control-button-background-color: var(--primary-color);
|
||||
--control-button-background-opacity: 0.2;
|
||||
--control-button-border-radius: 18px;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-control-select-menu": DemoHaControlSelectMenu;
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Temp Color Picker
|
||||
---
|
@@ -1,117 +0,0 @@
|
||||
import "../../../../src/components/ha-temp-color-picker";
|
||||
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-slider";
|
||||
|
||||
@customElement("demo-components-ha-temp-color-picker")
|
||||
export class DemoHaTempColorPicker extends LitElement {
|
||||
@state()
|
||||
min = 3000;
|
||||
|
||||
@state()
|
||||
max = 7000;
|
||||
|
||||
@state()
|
||||
value = 4000;
|
||||
|
||||
@state()
|
||||
liveValue?: number;
|
||||
|
||||
private _minChanged(ev) {
|
||||
this.min = Number(ev.target.value);
|
||||
}
|
||||
|
||||
private _maxChanged(ev) {
|
||||
this.max = Number(ev.target.value);
|
||||
}
|
||||
|
||||
private _valueChanged(ev) {
|
||||
this.value = Number(ev.target.value);
|
||||
}
|
||||
|
||||
private _tempColorCursor(ev) {
|
||||
this.liveValue = ev.detail.value;
|
||||
}
|
||||
|
||||
private _tempColorChanged(ev) {
|
||||
this.value = ev.detail.value;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="value">${this.liveValue ?? this.value} K</p>
|
||||
<ha-temp-color-picker
|
||||
.min=${this.min}
|
||||
.max=${this.max}
|
||||
.value=${this.value}
|
||||
@value-changed=${this._tempColorChanged}
|
||||
@cursor-moved=${this._tempColorCursor}
|
||||
></ha-temp-color-picker>
|
||||
<p>Min temp : ${this.min} K</p>
|
||||
<ha-slider
|
||||
step="1"
|
||||
pin
|
||||
min="2000"
|
||||
max="10000"
|
||||
.value=${this.min}
|
||||
@change=${this._minChanged}
|
||||
>
|
||||
</ha-slider>
|
||||
<p>Max temp : ${this.max} K</p>
|
||||
<ha-slider
|
||||
step="1"
|
||||
pin
|
||||
min="2000"
|
||||
max="10000"
|
||||
.value=${this.max}
|
||||
@change=${this._maxChanged}
|
||||
>
|
||||
</ha-slider>
|
||||
<p>Value : ${this.value} K</p>
|
||||
<ha-slider
|
||||
step="1"
|
||||
pin
|
||||
min=${this.min}
|
||||
max=${this.max}
|
||||
.value=${this.value}
|
||||
@change=${this._valueChanged}
|
||||
>
|
||||
</ha-slider>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-temp-color-picker {
|
||||
width: 400px;
|
||||
}
|
||||
.value {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-temp-color-picker": DemoHaTempColorPicker;
|
||||
}
|
||||
}
|
@@ -20,7 +20,7 @@ We want to make it as easy for designers to contribute as it is for developers.
|
||||
|
||||
- Meet us at <a href="https://discord.gg/BPBc8rZ9" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
|
||||
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
|
||||
- Find the lates UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
- Find the latest UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
|
||||
## Developers
|
||||
|
||||
|
@@ -14,7 +14,7 @@ const ENTITIES = [
|
||||
}),
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
supported_color_modes: [LightColorMode.HS],
|
||||
supported_color_modes: [LightColorMode.HS, LightColorMode.COLOR_TEMP],
|
||||
}),
|
||||
getEntity("light", "unavailable", "unavailable", {
|
||||
friendly_name: "Unavailable entity",
|
||||
@@ -116,6 +116,15 @@ const CONFIGS = [
|
||||
- type: "light-brightness"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Light color temperature feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: light.bed_light
|
||||
features:
|
||||
- type: "color-temp"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Vacuum commands feature",
|
||||
config: `
|
||||
|
@@ -284,6 +284,13 @@ const ENTITIES: HassEntity[] = [
|
||||
installed_version: "1.0.0",
|
||||
latest_version: "2.0.0",
|
||||
}),
|
||||
createEntity("water_heater.off", "off"),
|
||||
createEntity("water_heater.eco", "eco"),
|
||||
createEntity("water_heater.electric", "electric"),
|
||||
createEntity("water_heater.performance", "performance"),
|
||||
createEntity("water_heater.high_demand", "high_demand"),
|
||||
createEntity("water_heater.heat_pump", "heat_pump"),
|
||||
createEntity("water_heater.gas", "gas"),
|
||||
];
|
||||
|
||||
function createEntity(
|
||||
@@ -336,7 +343,7 @@ export class DemoEntityState extends LitElement {
|
||||
const columns: DataTableColumnContainer<EntityRowData> = {
|
||||
icon: {
|
||||
title: "Icon",
|
||||
template: (_, entry) => html`
|
||||
template: (entry) => html`
|
||||
<state-badge
|
||||
.stateObj=${entry.stateObj}
|
||||
.stateColor=${true}
|
||||
@@ -353,7 +360,7 @@ export class DemoEntityState extends LitElement {
|
||||
title: "State",
|
||||
width: "20%",
|
||||
sortable: true,
|
||||
template: (_, entry) =>
|
||||
template: (entry) =>
|
||||
html`${computeStateDisplay(
|
||||
hass.localize,
|
||||
entry.stateObj,
|
||||
@@ -364,14 +371,14 @@ export class DemoEntityState extends LitElement {
|
||||
},
|
||||
device_class: {
|
||||
title: "Device class",
|
||||
template: (dc) => html`${dc ?? "-"}`,
|
||||
template: (entry) => html`${entry.device_class ?? "-"}`,
|
||||
width: "20%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
domain: {
|
||||
title: "Domain",
|
||||
template: (_, entry) => html`${computeDomain(entry.entity_id)}`,
|
||||
template: (entry) => html`${computeDomain(entry.entity_id)}`,
|
||||
width: "20%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
|
@@ -43,6 +43,28 @@ const ENTITIES = [
|
||||
target_temp_low: 20,
|
||||
target_temp_high: 25,
|
||||
}),
|
||||
getEntity("climate", "advanced", "auto", {
|
||||
friendly_name: "Advanced hvac",
|
||||
supported_features:
|
||||
// eslint-disable-next-line no-bitwise
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE |
|
||||
ClimateEntityFeature.TARGET_HUMIDITY |
|
||||
ClimateEntityFeature.PRESET_MODE,
|
||||
hvac_modes: ["auto", "off"],
|
||||
hvac_mode: "auto",
|
||||
preset_modes: ["eco", "comfort", "boost"],
|
||||
preset_mode: "eco",
|
||||
current_temperature: 18,
|
||||
min_temp: 10,
|
||||
max_temp: 30,
|
||||
target_temp_step: 1,
|
||||
target_temp_low: 20,
|
||||
target_temp_high: 25,
|
||||
current_humidity: 40,
|
||||
min_humidity: 0,
|
||||
max_humidity: 100,
|
||||
humidity: 50,
|
||||
}),
|
||||
getEntity("climate", "unavailable", "unavailable", {
|
||||
friendly_name: "Unavailable heater",
|
||||
hvac_modes: ["heat", "off"],
|
||||
|
3
gallery/src/pages/more-info/water-heater.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Water Heater
|
||||
---
|
70
gallery/src/pages/more-info/water-heater.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import { WaterHeaterEntityFeature } from "../../../../src/data/water_heater";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("water_heater", "basic", "eco", {
|
||||
friendly_name: "Basic heater",
|
||||
operation_list: ["heat_pump", "eco", "performance", "off"],
|
||||
operation_mode: "eco",
|
||||
away_mode: "off",
|
||||
target_temp_step: 1,
|
||||
current_temperature: 55,
|
||||
temperature: 60,
|
||||
min_temp: 20,
|
||||
max_temp: 70,
|
||||
supported_features:
|
||||
// eslint-disable-next-line no-bitwise
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
|
||||
WaterHeaterEntityFeature.OPERATION_MODE |
|
||||
WaterHeaterEntityFeature.AWAY_MODE,
|
||||
}),
|
||||
getEntity("water_heater", "unavailable", "unavailable", {
|
||||
friendly_name: "Unavailable heater",
|
||||
operation_list: ["heat_pump", "eco", "performance", "off"],
|
||||
operation_mode: "off",
|
||||
min_temp: 20,
|
||||
max_temp: 70,
|
||||
supported_features:
|
||||
// eslint-disable-next-line no-bitwise
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
|
||||
WaterHeaterEntityFeature.OPERATION_MODE,
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-water-heater")
|
||||
class DemoMoreInfoWaterHeater extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-water-heater": DemoMoreInfoWaterHeater;
|
||||
}
|
||||
}
|
@@ -7,7 +7,6 @@ import {
|
||||
mdiDocker,
|
||||
mdiExclamationThick,
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiKey,
|
||||
mdiLinkLock,
|
||||
mdiNetwork,
|
||||
@@ -22,7 +21,7 @@ import {
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -40,11 +39,11 @@ import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
AddonCapability,
|
||||
fetchHassioAddonChangelog,
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
fetchHassioAddonChangelog,
|
||||
fetchHassioAddonInfo,
|
||||
installHassioAddon,
|
||||
rebuildLocalAddon,
|
||||
restartHassioAddon,
|
||||
@@ -56,9 +55,9 @@ import {
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import {
|
||||
HassioStats,
|
||||
extractApiErrorMessage,
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
StoreAddon,
|
||||
@@ -69,6 +68,7 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../../src/types";
|
||||
import { bytesToString } from "../../../../src/util/bytes-to-string";
|
||||
|
@@ -49,6 +49,10 @@ import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hass
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
type BackupItem = HassioBackup & {
|
||||
secondary: string;
|
||||
};
|
||||
|
||||
@customElement("hassio-backups")
|
||||
export class HassioBackups extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -117,15 +121,15 @@ export class HassioBackups extends LitElement {
|
||||
}
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer => ({
|
||||
(narrow: boolean): DataTableColumnContainer<BackupItem> => ({
|
||||
name: {
|
||||
title: this.supervisor.localize("backup.name"),
|
||||
main: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
template: (entry: string, backup: any) =>
|
||||
html`${entry || backup.slug}
|
||||
template: (backup) =>
|
||||
html`${backup.name || backup.slug}
|
||||
<div class="secondary">${backup.secondary}</div>`,
|
||||
},
|
||||
size: {
|
||||
@@ -134,7 +138,7 @@ export class HassioBackups extends LitElement {
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB",
|
||||
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
|
||||
},
|
||||
location: {
|
||||
title: this.supervisor.localize("backup.location"),
|
||||
@@ -142,8 +146,8 @@ export class HassioBackups extends LitElement {
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (entry: string | null) =>
|
||||
entry || this.supervisor.localize("backup.data_disk"),
|
||||
template: (backup) =>
|
||||
backup.location || this.supervisor.localize("backup.data_disk"),
|
||||
},
|
||||
date: {
|
||||
title: this.supervisor.localize("backup.created"),
|
||||
@@ -152,8 +156,8 @@ export class HassioBackups extends LitElement {
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (entry: string) =>
|
||||
relativeTime(new Date(entry), this.hass.locale),
|
||||
template: (backup) =>
|
||||
relativeTime(new Date(backup.date), this.hass.locale),
|
||||
},
|
||||
secondary: {
|
||||
title: "",
|
||||
@@ -163,7 +167,7 @@ export class HassioBackups extends LitElement {
|
||||
})
|
||||
);
|
||||
|
||||
private _backupData = memoizeOne((backups: HassioBackup[]) =>
|
||||
private _backupData = memoizeOne((backups: HassioBackup[]): BackupItem[] =>
|
||||
backups.map((backup) => ({
|
||||
...backup,
|
||||
secondary: this._computeBackupContent(backup),
|
||||
|
@@ -31,8 +31,8 @@ export class HassioUploadBackup extends LitElement {
|
||||
.icon=${mdiFolderUpload}
|
||||
accept="application/x-tar"
|
||||
label="Upload backup"
|
||||
supports="Supports .TAR files"
|
||||
@file-picked=${this._uploadFile}
|
||||
auto-open-file-dialog
|
||||
></ha-file-upload>
|
||||
`;
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import { mdiFolder, mdiPuzzle } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
HassioPartialBackupCreateParams,
|
||||
} from "../../../src/data/hassio/backup";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
|
||||
import {
|
||||
HomeAssistant,
|
||||
TranslationDict,
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
@@ -173,6 +173,7 @@ class HassioBackupDialog
|
||||
private async _restoreClicked() {
|
||||
const backupDetails = this._backupContent.backupDetails();
|
||||
this._restoringBackup = true;
|
||||
this._dialogParams?.onRestoring?.();
|
||||
if (this._backupContent.backupType === "full") {
|
||||
await this._fullRestoreClicked(backupDetails);
|
||||
} else {
|
||||
@@ -219,7 +220,7 @@ class HassioBackupDialog
|
||||
this._error = error.body.message;
|
||||
}
|
||||
} else {
|
||||
fireEvent(this, "restoring");
|
||||
this._dialogParams?.onRestoring?.();
|
||||
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(backupDetails),
|
||||
@@ -268,7 +269,7 @@ class HassioBackupDialog
|
||||
}
|
||||
);
|
||||
} else {
|
||||
fireEvent(this, "restoring");
|
||||
this._dialogParams?.onRestoring?.();
|
||||
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(backupDetails),
|
||||
|
@@ -5,6 +5,7 @@ import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
export interface HassioBackupDialogParams {
|
||||
slug: string;
|
||||
onDelete?: () => void;
|
||||
onRestoring?: () => void;
|
||||
onboarding?: boolean;
|
||||
supervisor?: Supervisor;
|
||||
localize?: LocalizeFunc;
|
||||
|
157
package.json
@@ -25,35 +25,35 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.22.10",
|
||||
"@babel/runtime": "7.23.1",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@codemirror/autocomplete": "6.9.0",
|
||||
"@codemirror/commands": "6.2.4",
|
||||
"@codemirror/language": "6.8.0",
|
||||
"@codemirror/autocomplete": "6.9.1",
|
||||
"@codemirror/commands": "6.2.5",
|
||||
"@codemirror/language": "6.9.1",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.1",
|
||||
"@codemirror/search": "6.5.4",
|
||||
"@codemirror/state": "6.2.1",
|
||||
"@codemirror/view": "6.16.0",
|
||||
"@codemirror/view": "6.20.2",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.10.0",
|
||||
"@formatjs/intl-displaynames": "6.5.0",
|
||||
"@formatjs/intl-datetimeformat": "6.10.3",
|
||||
"@formatjs/intl-displaynames": "6.5.2",
|
||||
"@formatjs/intl-getcanonicallocales": "2.2.1",
|
||||
"@formatjs/intl-listformat": "7.4.0",
|
||||
"@formatjs/intl-locale": "3.3.2",
|
||||
"@formatjs/intl-numberformat": "8.7.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.4",
|
||||
"@fullcalendar/core": "6.1.8",
|
||||
"@fullcalendar/daygrid": "6.1.8",
|
||||
"@fullcalendar/interaction": "6.1.8",
|
||||
"@fullcalendar/list": "6.1.8",
|
||||
"@fullcalendar/luxon3": "6.1.8",
|
||||
"@fullcalendar/timegrid": "6.1.8",
|
||||
"@formatjs/intl-listformat": "7.4.2",
|
||||
"@formatjs/intl-locale": "3.3.4",
|
||||
"@formatjs/intl-numberformat": "8.7.2",
|
||||
"@formatjs/intl-pluralrules": "5.2.6",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.6",
|
||||
"@fullcalendar/core": "6.1.9",
|
||||
"@fullcalendar/daygrid": "6.1.9",
|
||||
"@fullcalendar/interaction": "6.1.9",
|
||||
"@fullcalendar/list": "6.1.9",
|
||||
"@fullcalendar/luxon3": "6.1.9",
|
||||
"@fullcalendar/timegrid": "6.1.9",
|
||||
"@lezer/highlight": "1.1.6",
|
||||
"@lit-labs/context": "0.3.3",
|
||||
"@lit-labs/motion": "1.0.3",
|
||||
"@lit-labs/virtualizer": "2.0.4",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.11",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.4",
|
||||
"@lit-labs/virtualizer": "2.0.7",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.18",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-button": "0.27.0",
|
||||
@@ -62,6 +62,7 @@
|
||||
"@material/mwc-dialog": "0.27.0",
|
||||
"@material/mwc-drawer": "0.27.0",
|
||||
"@material/mwc-fab": "0.27.0",
|
||||
"@material/mwc-floating-label": "0.27.0",
|
||||
"@material/mwc-formfield": "0.27.0",
|
||||
"@material/mwc-icon-button": "0.27.0",
|
||||
"@material/mwc-linear-progress": "0.27.0",
|
||||
@@ -79,10 +80,9 @@
|
||||
"@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": "=1.0.0-pre.15",
|
||||
"@material/web": "=1.0.0-pre.17",
|
||||
"@mdi/js": "7.2.96",
|
||||
"@mdi/svg": "7.2.96",
|
||||
"@polymer/app-layout": "3.1.0",
|
||||
"@polymer/iron-flex-layout": "3.0.1",
|
||||
"@polymer/iron-input": "3.0.1",
|
||||
"@polymer/iron-resizable-behavior": "3.0.1",
|
||||
@@ -94,8 +94,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.1.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.1.5",
|
||||
"@vaadin/combo-box": "24.1.9",
|
||||
"@vaadin/vaadin-themable-mixin": "24.1.9",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -103,26 +103,26 @@
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "3.3.2",
|
||||
"chart.js": "4.4.0",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.32.0",
|
||||
"cropperjs": "1.5.13",
|
||||
"core-js": "3.32.2",
|
||||
"cropperjs": "1.6.1",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns-tz": "2.0.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"fuse.js": "6.6.2",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "1.4.10",
|
||||
"hls.js": "1.4.12",
|
||||
"home-assistant-js-websocket": "8.2.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.0",
|
||||
"intl-messageformat": "10.5.3",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.3.0",
|
||||
"marked": "4.3.0",
|
||||
"luxon": "3.4.3",
|
||||
"marked": "9.0.3",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
@@ -133,12 +133,14 @@
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.7.2",
|
||||
"sortablejs": "1.15.0",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "1.0.3",
|
||||
"tinykeys": "2.1.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "1.0.36",
|
||||
"unfetch": "5.0.0",
|
||||
"vis-data": "7.1.6",
|
||||
"vis-data": "7.1.7",
|
||||
"vis-network": "9.1.6",
|
||||
"vue": "2.7.14",
|
||||
"vue2-daterange-picker": "0.6.8",
|
||||
@@ -152,61 +154,62 @@
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.22.10",
|
||||
"@babel/plugin-proposal-decorators": "7.22.10",
|
||||
"@babel/plugin-transform-runtime": "7.22.10",
|
||||
"@babel/preset-env": "7.22.10",
|
||||
"@babel/preset-typescript": "7.22.5",
|
||||
"@babel/core": "7.23.0",
|
||||
"@babel/plugin-proposal-decorators": "7.23.0",
|
||||
"@babel/plugin-transform-runtime": "7.22.15",
|
||||
"@babel/preset-env": "7.22.20",
|
||||
"@babel/preset-typescript": "7.23.0",
|
||||
"@koa/cors": "4.0.0",
|
||||
"@octokit/auth-oauth-device": "6.0.0",
|
||||
"@octokit/plugin-retry": "6.0.0",
|
||||
"@octokit/rest": "20.0.1",
|
||||
"@lokalise/node-api": "12.0.0",
|
||||
"@octokit/auth-oauth-device": "6.0.1",
|
||||
"@octokit/plugin-retry": "6.0.1",
|
||||
"@octokit/rest": "20.0.2",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-commonjs": "25.0.4",
|
||||
"@rollup/plugin-json": "6.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.1.0",
|
||||
"@rollup/plugin-node-resolve": "15.2.1",
|
||||
"@rollup/plugin-replace": "5.0.2",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.2",
|
||||
"@types/chromecast-caf-receiver": "6.0.9",
|
||||
"@types/chromecast-caf-sender": "1.0.5",
|
||||
"@types/esprima": "4.0.3",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.3",
|
||||
"@types/chromecast-caf-receiver": "6.0.10",
|
||||
"@types/chromecast-caf-sender": "1.0.6",
|
||||
"@types/esprima": "4.0.4",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.0",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/leaflet": "1.9.3",
|
||||
"@types/leaflet-draw": "1.0.7",
|
||||
"@types/luxon": "3.3.1",
|
||||
"@types/marked": "4.3.1",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/qrcode": "1.5.1",
|
||||
"@types/serve-handler": "6.1.1",
|
||||
"@types/sortablejs": "1.15.1",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/js-yaml": "4.0.6",
|
||||
"@types/leaflet": "1.9.6",
|
||||
"@types/leaflet-draw": "1.0.8",
|
||||
"@types/luxon": "3.3.2",
|
||||
"@types/mocha": "10.0.2",
|
||||
"@types/qrcode": "1.5.2",
|
||||
"@types/serve-handler": "6.1.2",
|
||||
"@types/sortablejs": "1.15.3",
|
||||
"@types/tar": "6.1.6",
|
||||
"@types/ua-parser-js": "0.7.37",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "6.3.0",
|
||||
"@typescript-eslint/parser": "6.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.3",
|
||||
"@typescript-eslint/parser": "6.7.3",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "4.3.7",
|
||||
"del": "7.0.0",
|
||||
"eslint": "8.47.0",
|
||||
"chai": "4.3.10",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.1.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-import-resolver-webpack": "0.13.4",
|
||||
"eslint-import-resolver-webpack": "0.13.7",
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.28.0",
|
||||
"eslint-plugin-lit": "1.8.3",
|
||||
"eslint-plugin-lit-a11y": "3.0.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-lit": "1.9.1",
|
||||
"eslint-plugin-lit-a11y": "4.1.0",
|
||||
"eslint-plugin-unused-imports": "3.0.0",
|
||||
"eslint-plugin-wc": "1.5.0",
|
||||
"eslint-plugin-wc": "2.0.4",
|
||||
"esprima": "4.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"glob": "10.3.3",
|
||||
"glob": "10.3.10",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
"gulp-json-transform": "0.4.8",
|
||||
@@ -217,28 +220,28 @@
|
||||
"husky": "8.0.3",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "13.2.3",
|
||||
"lint-staged": "14.0.1",
|
||||
"lit-analyzer": "2.0.0-pre.3",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.2",
|
||||
"magic-string": "0.30.3",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.2.0",
|
||||
"object-hash": "3.0.0",
|
||||
"open": "9.1.0",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.0.1",
|
||||
"prettier": "3.0.3",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.9.2",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "15.2.0",
|
||||
"sinon": "16.0.0",
|
||||
"source-map-url": "0.4.1",
|
||||
"systemjs": "6.14.1",
|
||||
"tar": "6.1.15",
|
||||
"systemjs": "6.14.2",
|
||||
"tar": "6.2.0",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"ts-lit-plugin": "2.0.0-pre.1",
|
||||
"typescript": "5.1.6",
|
||||
"typescript": "5.2.2",
|
||||
"vinyl-buffer": "1.0.1",
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "5.88.2",
|
||||
@@ -255,5 +258,5 @@
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@3.6.1"
|
||||
"packageManager": "yarn@3.6.3"
|
||||
}
|
||||
|
12
public/static/icons/browserconfig.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/static/icons/tile-win-70x70.png"/>
|
||||
<square150x150logo src="/static/icons/tile-win-150x150.png"/>
|
||||
<wide310x150logo src="/static/icons/tile-win-310x150.png"/>
|
||||
<square310x310logo src="/static/icons/tile-win-310x310.png"/>
|
||||
<TileColor>#18bcf2</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 21 KiB |
BIN
public/static/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
public/static/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
@@ -1 +1,23 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="nonzero" fill="#000" d="M9,16 L9,17 L7,17 L7,16 L1,16 L1,9 L-1,9 L8.00163907,0 L13,4.785368 L13,3 L15,3 L15,7.035368 L17,9 L15,9 L15,16 L9,16 Z M9,16 L9,13.5 C9.49775077,13.0022492 10.1813086,12.3186914 11.0506735,11.4493265 C11.1951058,11.4824829 11.3455072,11.5 11.5,11.5 C12.6045695,11.5 13.5,10.6045695 13.5,9.5 C13.5,8.3954305 12.6045695,7.5 11.5,7.5 C10.3954305,7.5 9.5,8.3954305 9.5,9.5 C9.5,9.65449279 9.5175171,9.80489423 9.55067348,9.94932652 L9,10.5 L9,7.73243561 C9.59780137,7.38662619 10,6.74028236 10,6 C10,4.8954305 9.1045695,4 8,4 C6.8954305,4 6,4.8954305 6,6 C6,6.74028236 6.40219863,7.38662619 7,7.73243561 L7,10.5 L6.44932652,9.94932652 C6.4824829,9.80489423 6.5,9.65449279 6.5,9.5 C6.5,8.3954305 5.6045695,7.5 4.5,7.5 C3.3954305,7.5 2.5,8.3954305 2.5,9.5 C2.5,10.6045695 3.3954305,11.5 4.5,11.5 C4.65352068,11.5 4.80300134,11.4827027 4.9465994,11.4499505 C5.81726201,12.3268973 6.50172888,13.0147433 7,13.5134884 L7,16 L9,16 Z M11.5,10 C11.2238576,10 11,9.77614237 11,9.5 C11,9.22385763 11.2238576,9 11.5,9 C11.7761424,9 12,9.22385763 12,9.5 C12,9.77614237 11.7761424,10 11.5,10 Z M4.5,10 C4.22385763,10 4,9.77614237 4,9.5 C4,9.22385763 4.22385763,9 4.5,9 C4.77614237,9 5,9.22385763 5,9.5 C5,9.77614237 4.77614237,10 4.5,10 Z M8,6.5 C7.72385763,6.5 7.5,6.27614237 7.5,6 C7.5,5.72385763 7.72385763,5.5 8,5.5 C8.27614237,5.5 8.5,5.72385763 8.5,6 C8.5,6.27614237 8.27614237,6.5 8,6.5 Z" id="house-small-tree"/></svg>
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="480.000000pt" height="480.000000pt" viewBox="0 0 480.000000 480.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<g transform="translate(0.000000,480.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2313 4666 c-23 -7 -56 -23 -75 -34 -47 -30 -2059 -2048 -2095 -2102
|
||||
-45 -67 -77 -135 -109 -230 l-29 -85 0 -995 0 -995 27 -51 c31 -59 93 -118
|
||||
152 -145 39 -18 83 -19 1001 -19 l960 0 -406 405 c-395 395 -406 406 -433 395
|
||||
-15 -5 -63 -10 -107 -10 -429 0 -566 577 -181 767 67 34 86 38 164 42 105 4
|
||||
165 -13 246 -67 113 -74 175 -190 176 -327 1 -44 -3 -96 -7 -115 l-8 -35 316
|
||||
-315 315 -315 0 1160 -1 1160 -51 35 c-260 177 -226 567 62 704 82 39 209 48
|
||||
293 21 239 -78 354 -352 242 -575 -32 -63 -89 -125 -141 -156 l-44 -26 0 -811
|
||||
0 -812 315 315 c218 217 313 320 309 330 -14 35 -16 134 -4 190 26 122 111
|
||||
227 230 284 82 39 209 48 293 21 115 -38 214 -130 258 -242 19 -46 23 -78 24
|
||||
-153 0 -86 -3 -101 -32 -163 -40 -84 -118 -163 -198 -202 -49 -23 -77 -29
|
||||
-150 -33 -50 -2 -108 1 -130 7 l-40 11 -437 -438 -438 -437 0 -307 0 -308 998
|
||||
0 c981 0 998 1 1042 21 58 26 115 81 148 144 l27 50 0 995 0 995 -33 95 c-72
|
||||
209 -6 135 -1147 1278 -840 843 -1040 1037 -1082 1059 -64 31 -159 39 -220 19z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
public/static/icons/maskable_icon-128x128.png
Normal file
After Width: | Height: | Size: 992 B |
BIN
public/static/icons/maskable_icon-192x192.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/static/icons/maskable_icon-384x384.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/static/icons/maskable_icon-48x48.png
Normal file
After Width: | Height: | Size: 477 B |
BIN
public/static/icons/maskable_icon-512x512.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
public/static/icons/maskable_icon-72x72.png
Normal file
After Width: | Height: | Size: 597 B |
BIN
public/static/icons/maskable_icon-96x96.png
Normal file
After Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
public/static/images/logo_discord.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/static/images/logo_twitter.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 1.3 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230802.0"
|
||||
version = "20231002.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -15,7 +15,7 @@
|
||||
"lockFileMaintenance": {
|
||||
"description": ["Run after patch releases but before next beta"],
|
||||
"enabled": true,
|
||||
"schedule": ["on the 19th day of the month"]
|
||||
"schedule": ["on the 19th day of the month before 4am"]
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
|
40
script/core
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# Helper to start Home Assistant Core inside the devcontainer
|
||||
|
||||
# Stop on errors
|
||||
@@ -11,11 +11,35 @@ if [ -z "${DEVCONTAINER}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z $(which hass) ]; then
|
||||
echo "Installing Home Asstant core from dev."
|
||||
python3 -m pip install --upgrade \
|
||||
colorlog \
|
||||
git+https://github.com/home-assistant/home-assistant.git@dev
|
||||
# Default to installing (or upgrading to) dev branch
|
||||
coreURL="https://github.com/home-assistant/core.git"
|
||||
ref="dev"
|
||||
|
||||
while getopts "hr:s" opt; do
|
||||
case $opt in
|
||||
h) # Help
|
||||
echo "Usage: $0 [-h|-r <ref>|-s]"
|
||||
echo -n "Install and run core at the given ref, i.e. branch, tag, or commit. The dev branch is used if no option is specified."
|
||||
echo "The -s flag skips the install/upgrade, using whatever version is currently installed."
|
||||
exit 0
|
||||
;;
|
||||
r) # Git ref
|
||||
ref="${OPTARG}"
|
||||
;;
|
||||
s) # Skip (use current install)
|
||||
ref=""
|
||||
;;
|
||||
*)
|
||||
echo "Try $0 -h for help" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$ref" ]; then
|
||||
echo "Installing Home Assistant core at ${ref}..."
|
||||
python3 -m pip install --user --upgrade --src "$HOME/src" \
|
||||
--editable "git+${coreURL}@${ref}#egg=homeassistant"
|
||||
fi
|
||||
|
||||
if [ ! -d "${WD}/config" ]; then
|
||||
@@ -30,7 +54,7 @@ logger:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> "${WD}/config/configuration.yaml"
|
||||
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
if [ -n "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: ${WD}
|
||||
@@ -46,7 +70,7 @@ frontend:
|
||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
fi
|
||||
|
||||
if [ ! -z "${CODESPACES}" ]; then
|
||||
if [ -n "${CODESPACES}" ]; then
|
||||
echo "
|
||||
http:
|
||||
use_x_forwarded_for: true
|
||||
|
@@ -8,40 +8,4 @@ set -eu -o pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ -z "${LOKALISE_TOKEN-}" ] && [ ! -f .lokalise_token ] ; then
|
||||
echo "Lokalise API token is required to download the latest set of" \
|
||||
"translations. Please create an account by using the following link:" \
|
||||
"https://lokalise.co/signup/3420425759f6d6d241f598.13594006/all/" \
|
||||
"Place your token in a new file \".lokalise_token\" in the repo" \
|
||||
"root directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load token from file if not already in the environment
|
||||
[ -z "${LOKALISE_TOKEN-}" ] && LOKALISE_TOKEN="$(<.lokalise_token)"
|
||||
|
||||
declare -A PROJECT_ID=( \
|
||||
[frontend]="3420425759f6d6d241f598.13594006" \
|
||||
[backend]="130246255a974bd3b5e8a1.51616605" \
|
||||
)
|
||||
|
||||
for project in ${!PROJECT_ID[*]}; do
|
||||
LOCAL_DIR=`pwd`/translations/${project}
|
||||
rm -f ${LOCAL_DIR}/* || mkdir -p ${LOCAL_DIR}
|
||||
docker run \
|
||||
-v ${LOCAL_DIR}:/opt/dest/locale \
|
||||
--rm \
|
||||
lokalise/lokalise-cli-2@sha256:f1860b26be22fa73b8c93bc5f8690f2afc867610a42de6fc27adc790e5d4425d \
|
||||
lokalise2 \
|
||||
--token ${LOKALISE_TOKEN} \
|
||||
--project-id ${PROJECT_ID[${project}]} \
|
||||
file download \
|
||||
--export-empty-as skip \
|
||||
--format json \
|
||||
--json-unescaped-slashes=true \
|
||||
--replace-breaks=false \
|
||||
--original-filenames=false \
|
||||
--unzip-to /opt/dest
|
||||
done
|
||||
|
||||
./node_modules/.bin/gulp check-downloaded-translations
|
||||
./node_modules/.bin/gulp download-translations
|
@@ -1,19 +1,12 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import "@material/mwc-button";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
import "../components/ha-checkbox";
|
||||
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||
import "../components/ha-form/ha-form";
|
||||
import "../components/ha-formfield";
|
||||
import "../components/ha-markdown";
|
||||
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
|
||||
@@ -21,7 +14,7 @@ import {
|
||||
DataEntryFlowStep,
|
||||
DataEntryFlowStepForm,
|
||||
} from "../data/data_entry_flow";
|
||||
import "./ha-password-manager-polyfill";
|
||||
import "./ha-auth-form";
|
||||
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
@@ -49,6 +42,10 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@state() private _storeToken = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
@@ -79,13 +76,17 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-flow .action {
|
||||
margin: 24px 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-auth-flow .store-token {
|
||||
margin-top: 10px;
|
||||
margin-left: -16px;
|
||||
}
|
||||
</style>
|
||||
<form>${this._renderForm()}</form>
|
||||
<ha-password-manager-polyfill
|
||||
.step=${this._step}
|
||||
.stepData=${this._stepData}
|
||||
@form-submitted=${this._handleSubmit}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
></ha-password-manager-polyfill>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -128,12 +129,6 @@ export class HaAuthFlow extends LitElement {
|
||||
(form as any).focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
this.renderRoot.querySelector(
|
||||
"ha-password-manager-polyfill"
|
||||
)!.boundingRect = this.getBoundingClientRect();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _renderForm() {
|
||||
@@ -205,7 +200,7 @@ export class HaAuthFlow extends LitElement {
|
||||
></ha-markdown>
|
||||
`
|
||||
: nothing}
|
||||
<ha-form
|
||||
<ha-auth-form
|
||||
.data=${this._stepData}
|
||||
.schema=${autocompleteLoginFields(step.data_schema)}
|
||||
.error=${step.errors}
|
||||
@@ -213,7 +208,7 @@ export class HaAuthFlow extends LitElement {
|
||||
.computeLabel=${this._computeLabelCallback(step)}
|
||||
.computeError=${this._computeErrorCallback(step)}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
></ha-form>
|
||||
></ha-auth-form>
|
||||
${this.clientId === genClientId() &&
|
||||
!["select_mfa_module", "mfa"].includes(step.step_id)
|
||||
? html`
|
||||
@@ -395,20 +390,6 @@ export class HaAuthFlow extends LitElement {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.action {
|
||||
margin: 24px 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
/* Align with the rest of the form. */
|
||||
.store-token {
|
||||
margin-top: 10px;
|
||||
margin-left: -16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
77
src/auth/ha-auth-form-string.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaFormString } from "../components/ha-form/ha-form-string";
|
||||
import "../components/ha-icon-button";
|
||||
import "./ha-auth-textfield";
|
||||
|
||||
@customElement("ha-auth-form-string")
|
||||
export class HaAuthFormString extends HaFormString {
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-form-string {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
ha-auth-form-string[own-margin] {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
ha-auth-form-string ha-auth-textfield {
|
||||
display: block !important;
|
||||
}
|
||||
ha-auth-form-string ha-icon-button {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 12px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-auth-form-string ha-icon-button {
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 12px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
</style>
|
||||
<ha-auth-textfield
|
||||
.type=${
|
||||
!this.isPassword
|
||||
? this.stringType
|
||||
: this.unmaskedPassword
|
||||
? "text"
|
||||
: "password"
|
||||
}
|
||||
.label=${this.label}
|
||||
.value=${this.data || ""}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
.name=${this.schema.name}
|
||||
.autocomplete=${this.schema.autocomplete}
|
||||
.suffix=${
|
||||
this.isPassword
|
||||
? // reserve some space for the icon.
|
||||
html`<div style="width: 24px"></div>`
|
||||
: this.schema.description?.suffix
|
||||
}
|
||||
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||
@input=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
></ha-auth-textfield>
|
||||
${this.renderIcon()}
|
||||
</ha-auth-textfield>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-form-string": HaAuthFormString;
|
||||
}
|
||||
}
|
44
src/auth/ha-auth-form.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaForm } from "../components/ha-form/ha-form";
|
||||
import "./ha-auth-form-string";
|
||||
|
||||
@customElement("ha-auth-form")
|
||||
export class HaAuthForm extends HaForm {
|
||||
protected fieldElementName(type: string): string {
|
||||
if (type === "string") {
|
||||
return `ha-auth-form-${type}`;
|
||||
}
|
||||
return super.fieldElementName(type);
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
// attach it as soon as possible to make sure we fetch all events.
|
||||
this.addValueChangedListener(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-form .root > * {
|
||||
display: block;
|
||||
}
|
||||
ha-auth-form .root > *:not([own-margin]):not(:last-child) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-auth-form ha-alert[own-margin] {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
${super.render()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-form": HaAuthForm;
|
||||
}
|
||||
}
|
254
src/auth/ha-auth-textfield.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
/* eslint-disable lit/value-after-constraints */
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { floatingLabel } from "@material/mwc-floating-label/mwc-floating-label-directive";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { live } from "lit/directives/live";
|
||||
import { HaTextField } from "../components/ha-textfield";
|
||||
|
||||
@customElement("ha-auth-textfield")
|
||||
export class HaAuthTextField extends HaTextField {
|
||||
protected renderLabel(): TemplateResult | string {
|
||||
return !this.label
|
||||
? ""
|
||||
: html`
|
||||
<span
|
||||
.floatingLabelFoundation=${floatingLabel(
|
||||
this.label
|
||||
) as unknown as any}
|
||||
.id=${this.name}
|
||||
>${this.label}</span
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderInput(shouldRenderHelperText: boolean): TemplateResult {
|
||||
const minOrUndef = this.minLength === -1 ? undefined : this.minLength;
|
||||
const maxOrUndef = this.maxLength === -1 ? undefined : this.maxLength;
|
||||
const autocapitalizeOrUndef = this.autocapitalize
|
||||
? (this.autocapitalize as
|
||||
| "off"
|
||||
| "none"
|
||||
| "on"
|
||||
| "sentences"
|
||||
| "words"
|
||||
| "characters")
|
||||
: undefined;
|
||||
const showValidationMessage = this.validationMessage && !this.isUiValid;
|
||||
const ariaLabelledbyOrUndef = this.label ? this.name : undefined;
|
||||
const ariaControlsOrUndef = shouldRenderHelperText
|
||||
? "helper-text"
|
||||
: undefined;
|
||||
const ariaDescribedbyOrUndef =
|
||||
this.focused || this.helperPersistent || showValidationMessage
|
||||
? "helper-text"
|
||||
: undefined;
|
||||
// TODO: live() directive needs casting for lit-analyzer
|
||||
// https://github.com/runem/lit-analyzer/pull/91/files
|
||||
// TODO: lit-analyzer labels min/max as (number|string) instead of string
|
||||
return html` <input
|
||||
aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
|
||||
aria-controls=${ifDefined(ariaControlsOrUndef)}
|
||||
aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
|
||||
class="mdc-text-field__input"
|
||||
type=${this.type}
|
||||
.value=${live(this.value) as unknown as string}
|
||||
?disabled=${this.disabled}
|
||||
placeholder=${this.placeholder}
|
||||
?required=${this.required}
|
||||
?readonly=${this.readOnly}
|
||||
minlength=${ifDefined(minOrUndef)}
|
||||
maxlength=${ifDefined(maxOrUndef)}
|
||||
pattern=${ifDefined(this.pattern ? this.pattern : undefined)}
|
||||
min=${ifDefined(this.min === "" ? undefined : (this.min as number))}
|
||||
max=${ifDefined(this.max === "" ? undefined : (this.max as number))}
|
||||
step=${ifDefined(this.step === null ? undefined : (this.step as number))}
|
||||
size=${ifDefined(this.size === null ? undefined : this.size)}
|
||||
name=${ifDefined(this.name === "" ? undefined : this.name)}
|
||||
inputmode=${ifDefined(this.inputMode)}
|
||||
autocapitalize=${ifDefined(autocapitalizeOrUndef)}
|
||||
@input=${this.handleInputChange}
|
||||
@focus=${this.onInputFocus}
|
||||
@blur=${this.onInputBlur}
|
||||
/>`;
|
||||
}
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-textfield {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
}
|
||||
ha-auth-textfield:not([disabled]):hover
|
||||
:not(.mdc-text-field--invalid):not(.mdc-text-field--focused)
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-outlined-hover-border-color,
|
||||
rgba(0, 0, 0, 0.87)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field:not(.mdc-text-field--outlined) {
|
||||
background-color: var(--mdc-text-field-fill-color, whitesmoke);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--invalid
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-error-color,
|
||||
var(--mdc-theme-error, #b00020)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--invalid
|
||||
+ .mdc-text-field-helper-line
|
||||
.mdc-text-field-character-counter,
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--invalid
|
||||
.mdc-text-field__icon {
|
||||
color: var(
|
||||
--mdc-text-field-error-color,
|
||||
var(--mdc-theme-error, #b00020)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label,
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label::after {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-stroke-width: 2px;
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-focused-label-color,
|
||||
var(--mdc-theme-primary, rgba(98, 0, 238, 0.87))
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
|
||||
.mdc-floating-label {
|
||||
color: #6200ee;
|
||||
color: var(--mdc-theme-primary, #6200ee);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field
|
||||
.mdc-text-field__input {
|
||||
color: var(--mdc-text-field-ink-color, rgba(0, 0, 0, 0.87));
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field
|
||||
.mdc-text-field__input::placeholder {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field-helper-line
|
||||
.mdc-text-field-helper-text:not(
|
||||
.mdc-text-field-helper-text--validation-msg
|
||||
),
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field-helper-line:not(.mdc-text-field--invalid)
|
||||
.mdc-text-field-character-counter {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field:not(.mdc-text-field--outlined) {
|
||||
background-color: var(--mdc-text-field-disabled-fill-color, #fafafa);
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field.mdc-text-field--outlined
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-outlined-disabled-border-color,
|
||||
rgba(0, 0, 0, 0.06)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label,
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label::after {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled] .mdc-text-field .mdc-text-field__input,
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field
|
||||
.mdc-text-field__input::placeholder {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field-helper-line
|
||||
.mdc-text-field-helper-text,
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field-helper-line
|
||||
.mdc-text-field-character-counter {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
|
||||
.mdc-floating-label {
|
||||
color: var(--mdc-theme-primary, #6200ee);
|
||||
}
|
||||
ha-auth-textfield[no-spinner] input::-webkit-outer-spin-button,
|
||||
ha-auth-textfield[no-spinner] input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
ha-auth-textfield[no-spinner] input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
</style>
|
||||
${super.render()}
|
||||
`;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
// add parent style to light dom
|
||||
const style = document.createElement("style");
|
||||
style.textContent = HaTextField.elementStyles as unknown as string;
|
||||
this.append(style);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-textfield": HaAuthTextField;
|
||||
}
|
||||
}
|
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import punycode from "punycode";
|
||||
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
||||
@@ -35,6 +29,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property() public translationFragment = "page-authorize";
|
||||
|
||||
@state() private _authProvider?: AuthProvider;
|
||||
|
||||
@state() private _authProviders?: AuthProvider[];
|
||||
@@ -45,7 +41,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||
if (query.client_id) {
|
||||
this.clientId = query.client_id;
|
||||
@@ -60,13 +55,27 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
protected render() {
|
||||
if (this._error) {
|
||||
return html`<ha-alert alert-type="error"
|
||||
>${this._error} ${this.redirectUri}</ha-alert
|
||||
>`;
|
||||
return html`
|
||||
<style>
|
||||
ha-authorize ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
</style>
|
||||
<ha-alert alert-type="error"
|
||||
>${this._error} ${this.redirectUri}</ha-alert
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this._authProviders) {
|
||||
return html`
|
||||
<style>
|
||||
ha-authorize p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
<p>${this.localize("ui.panel.page-authorize.initializing")}</p>
|
||||
`;
|
||||
}
|
||||
@@ -78,6 +87,25 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
const app = this.clientId && this.clientId in appNames;
|
||||
|
||||
return html`
|
||||
<style>
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
ha-auth-flow {
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
${!this._ownInstance
|
||||
? html`<ha-alert .alertType=${app ? "info" : "warning"}>
|
||||
${app
|
||||
@@ -102,7 +130,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
: nothing}
|
||||
|
||||
<ha-auth-flow
|
||||
.resources=${this.resources}
|
||||
.clientId=${this.clientId}
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
@@ -123,6 +150,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
@@ -217,25 +248,4 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
private async _handleAuthProviderPick(ev) {
|
||||
this._authProvider = ev.detail;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
ha-auth-flow {
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,126 +0,0 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HaFormSchema } from "../components/ha-form/types";
|
||||
import { autocompleteLoginFields } from "../data/auth";
|
||||
import type { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"form-submitted": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const ENABLED_HANDLERS = [
|
||||
"homeassistant",
|
||||
"legacy_api_password",
|
||||
"command_line",
|
||||
];
|
||||
|
||||
@customElement("ha-password-manager-polyfill")
|
||||
export class HaPasswordManagerPolyfill extends LitElement {
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ attribute: false }) public stepData: any;
|
||||
|
||||
@property({ attribute: false }) public boundingRect?: DOMRect;
|
||||
|
||||
private _styleElement?: HTMLStyleElement;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._styleElement = document.createElement("style");
|
||||
this._styleElement.textContent = css`
|
||||
.password-manager-polyfill {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
.password-manager-polyfill input {
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.password-manager-polyfill input[type="submit"] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
`.toString();
|
||||
document.head.append(this._styleElement);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._styleElement?.remove();
|
||||
delete this._styleElement;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
// Add under document body so the element isn't placed inside any shadow roots
|
||||
return document.body;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
this.step &&
|
||||
this.step.type === "form" &&
|
||||
this.step.step_id === "init" &&
|
||||
ENABLED_HANDLERS.includes(this.step.handler[0])
|
||||
) {
|
||||
return html`
|
||||
<form
|
||||
class="password-manager-polyfill"
|
||||
style=${styleMap({
|
||||
top: `${this.boundingRect?.y || 148}px`,
|
||||
left: `calc(50% - ${(this.boundingRect?.width || 360) / 2}px)`,
|
||||
width: `${this.boundingRect?.width || 360}px`,
|
||||
})}
|
||||
aria-hidden="true"
|
||||
@submit=${this._handleSubmit}
|
||||
>
|
||||
${autocompleteLoginFields(this.step.data_schema).map((input) =>
|
||||
this.render_input(input)
|
||||
)}
|
||||
<input type="submit" />
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private render_input(schema: HaFormSchema) {
|
||||
const inputType = schema.name.includes("password") ? "password" : "text";
|
||||
if (schema.type !== "string") {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<input
|
||||
tabindex="-1"
|
||||
.id=${schema.name}
|
||||
.name=${schema.name}
|
||||
.type=${inputType}
|
||||
.value=${this.stepData[schema.name] || ""}
|
||||
.autocomplete=${schema.autocomplete}
|
||||
@input=${this._valueChanged}
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSubmit(ev: Event) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "form-submitted");
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const target = ev.target! as HTMLInputElement;
|
||||
this.stepData = { ...this.stepData, [target.id]: target.value };
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.stepData,
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import { clamp } from "../number/clamp";
|
||||
|
||||
const DEFAULT_MIN_KELVIN = 2700;
|
||||
const DEFAULT_MAX_KELVIN = 6500;
|
||||
export const DEFAULT_MIN_KELVIN = 2700;
|
||||
export const DEFAULT_MAX_KELVIN = 6500;
|
||||
|
||||
export const temperature2rgb = (
|
||||
temperature: number
|
||||
|
@@ -15,6 +15,7 @@ import {
|
||||
mdiCalendarClock,
|
||||
mdiCarCoolantLevel,
|
||||
mdiCash,
|
||||
mdiChatSleep,
|
||||
mdiClock,
|
||||
mdiCloudUpload,
|
||||
mdiCog,
|
||||
@@ -31,7 +32,6 @@ import {
|
||||
mdiGauge,
|
||||
mdiGoogleAssistant,
|
||||
mdiGoogleCirclesCommunities,
|
||||
mdiHomeAssistant,
|
||||
mdiHomeAutomation,
|
||||
mdiImage,
|
||||
mdiImageFilterFrames,
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
mdiProgressClock,
|
||||
mdiRayVertex,
|
||||
mdiRemote,
|
||||
mdiRobotMower,
|
||||
mdiRobotVacuum,
|
||||
mdiScriptText,
|
||||
mdiSineWave,
|
||||
@@ -69,6 +70,8 @@ import {
|
||||
mdiWifi,
|
||||
} from "@mdi/js";
|
||||
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
|
||||
// Constants should be alphabetically sorted by name.
|
||||
// Arrays with values should be alphabetically sorted if order doesn't matter.
|
||||
// Each constant should have a description what it is supposed to be used for.
|
||||
@@ -99,6 +102,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
input_number: mdiRayVertex,
|
||||
input_select: mdiFormatListBulleted,
|
||||
input_text: mdiFormTextbox,
|
||||
lawn_mower: mdiRobotMower,
|
||||
light: mdiLightbulb,
|
||||
mailbox: mdiMailbox,
|
||||
notify: mdiCommentAlert,
|
||||
@@ -121,6 +125,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
tts: mdiSpeakerMessage,
|
||||
updater: mdiCloudUpload,
|
||||
vacuum: mdiRobotVacuum,
|
||||
wake_word: mdiChatSleep,
|
||||
zone: mdiMapMarkerRadius,
|
||||
};
|
||||
|
||||
@@ -187,6 +192,7 @@ export const DOMAINS_WITH_CARD = [
|
||||
"input_number",
|
||||
"input_text",
|
||||
"humidifier",
|
||||
"lawn_mower",
|
||||
"lock",
|
||||
"media_player",
|
||||
"number",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ThemeVars } from "../../data/ws-themes";
|
||||
import { darkStyles, derivedStyles } from "../../resources/styles";
|
||||
import { darkStyles, derivedStyles } from "../../resources/styles-data";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
hex2rgb,
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
DOMAIN_ATTRIBUTES_UNITS,
|
||||
TEMPERATURE_ATTRIBUTES,
|
||||
} from "../../data/entity_attributes";
|
||||
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import { WeatherEntity, getWeatherUnit } from "../../data/weather";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import checkValidDate from "../datetime/check_valid_date";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
@@ -9,8 +14,10 @@ import { formatNumber } from "../number/format_number";
|
||||
import { capitalizeFirstLetter } from "../string/capitalize-first-letter";
|
||||
import { isDate } from "../string/is_date";
|
||||
import { isTimestamp } from "../string/is_timestamp";
|
||||
import { blankBeforePercent } from "../translations/blank_before_percent";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
|
||||
export const computeAttributeValueDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
@@ -31,7 +38,40 @@ export const computeAttributeValueDisplay = (
|
||||
|
||||
// Number value, return formatted number
|
||||
if (typeof attributeValue === "number") {
|
||||
return formatNumber(attributeValue, locale);
|
||||
const formattedValue = formatNumber(attributeValue, locale);
|
||||
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
let unit = DOMAIN_ATTRIBUTES_UNITS[domain]?.[attribute] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (domain === "light" && attribute === "brightness") {
|
||||
const percentage = Math.round((attributeValue / 255) * 100);
|
||||
return `${percentage}${blankBeforePercent(locale)}%`;
|
||||
}
|
||||
|
||||
if (domain === "weather") {
|
||||
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
|
||||
}
|
||||
|
||||
if (unit === "%") {
|
||||
return `${formattedValue}${blankBeforePercent(locale)}${unit}`;
|
||||
}
|
||||
|
||||
if (unit === "°") {
|
||||
return `${formattedValue}${unit}`;
|
||||
}
|
||||
|
||||
if (unit) {
|
||||
return `${formattedValue} ${unit}`;
|
||||
}
|
||||
|
||||
if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
|
||||
return `${formattedValue} ${config.unit_system.temperature}`;
|
||||
}
|
||||
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
// Special handling in case this is a string with an known format
|
||||
|
@@ -193,6 +193,7 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
"scene",
|
||||
"stt",
|
||||
"tts",
|
||||
"wake_word",
|
||||
].includes(domain) ||
|
||||
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||
) {
|
||||
|
@@ -7,7 +7,7 @@ export const computeStateNameFromEntityAttributes = (
|
||||
): string =>
|
||||
attributes.friendly_name === undefined
|
||||
? computeObjectId(entityId).replace(/_/g, " ")
|
||||
: attributes.friendly_name || "";
|
||||
: (attributes.friendly_name ?? "").toString();
|
||||
|
||||
export const computeStateName = (stateObj: HassEntity): string =>
|
||||
computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);
|
||||
|
@@ -26,6 +26,7 @@ export const FIXED_DOMAIN_STATES = {
|
||||
humidifier: ["on", "off"],
|
||||
input_boolean: ["on", "off"],
|
||||
input_button: [],
|
||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
||||
light: ["on", "off"],
|
||||
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
|
||||
media_player: [
|
||||
|
@@ -34,6 +34,8 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
case "device_tracker":
|
||||
case "person":
|
||||
return compareState !== "not_home";
|
||||
case "lawn_mower":
|
||||
return ["mowing", "error"].includes(compareState);
|
||||
case "lock":
|
||||
return compareState !== "locked";
|
||||
case "media_player":
|
||||
|
@@ -22,6 +22,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
||||
"group",
|
||||
"humidifier",
|
||||
"input_boolean",
|
||||
"lawn_mower",
|
||||
"light",
|
||||
"lock",
|
||||
"media_player",
|
||||
|
@@ -4,7 +4,7 @@ export const clamp = (value: number, min: number, max: number) =>
|
||||
// Variant that only applies the clamping to a border if the border is defined
|
||||
export const conditionalClamp = (value: number, min?: number, max?: number) => {
|
||||
let result: number;
|
||||
result = min ? Math.max(value, min) : value;
|
||||
result = max ? Math.min(result, max) : result;
|
||||
result = min != null ? Math.max(value, min) : value;
|
||||
result = max != null ? Math.min(result, max) : result;
|
||||
return result;
|
||||
};
|
||||
|
@@ -108,7 +108,7 @@ export const formatNumber = (
|
||||
* @returns An `Intl.NumberFormatOptions` object with `maximumFractionDigits` set to 0, or `undefined`
|
||||
*/
|
||||
export const getNumberFormatOptions = (
|
||||
entityState: HassEntity,
|
||||
entityState?: HassEntity,
|
||||
entity?: EntityRegistryDisplayEntry
|
||||
): Intl.NumberFormatOptions | undefined => {
|
||||
const precision = entity?.display_precision;
|
||||
@@ -119,8 +119,8 @@ export const getNumberFormatOptions = (
|
||||
};
|
||||
}
|
||||
if (
|
||||
Number.isInteger(Number(entityState.attributes?.step)) &&
|
||||
Number.isInteger(Number(entityState.state))
|
||||
Number.isInteger(Number(entityState?.attributes?.step)) &&
|
||||
Number.isInteger(Number(entityState?.state))
|
||||
) {
|
||||
return { maximumFractionDigits: 0 };
|
||||
}
|
||||
|