mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-15 12:19:25 +00:00
Compare commits
291 Commits
auth-onboa
...
template-e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
72bf0c918a | ||
![]() |
419f5d13bf | ||
![]() |
16549b3404 | ||
![]() |
cbddebeaa8 | ||
![]() |
bbe4c95109 | ||
![]() |
4c6f9f0dd8 | ||
![]() |
90f7dba793 | ||
![]() |
7c492338a2 | ||
![]() |
530f494df8 | ||
![]() |
8fd1f35c59 | ||
![]() |
af1518e924 | ||
![]() |
473e381d75 | ||
![]() |
7d3acc747d | ||
![]() |
bf7424a67c | ||
![]() |
a856337eae | ||
![]() |
6cf47ba4eb | ||
![]() |
3b7a189708 | ||
![]() |
79c542b76a | ||
![]() |
e37b7bd73f | ||
![]() |
d6f3c34b33 | ||
![]() |
bc5cb46e7d | ||
![]() |
c7b747c4fa | ||
![]() |
d3c51d7acd | ||
![]() |
b6881d797c | ||
![]() |
b9f802939c | ||
![]() |
6558c2c065 | ||
![]() |
37a089c868 | ||
![]() |
f68eff6bb3 | ||
![]() |
88a525f1a7 | ||
![]() |
34fddd5940 | ||
![]() |
0e5d6fe8d8 | ||
![]() |
e1342a0d9d | ||
![]() |
0cc2d3aaa7 | ||
![]() |
67814505b3 | ||
![]() |
bae29c6d62 | ||
![]() |
a0e67d4c03 | ||
![]() |
131bc5fbf7 | ||
![]() |
051218e29b | ||
![]() |
6ace8307d8 | ||
![]() |
e84bef44b7 | ||
![]() |
3186d762f2 | ||
![]() |
c97a3b0a56 | ||
![]() |
78f1bb3b91 | ||
![]() |
67707fbc90 | ||
![]() |
2a57ffa615 | ||
![]() |
216fce74f8 | ||
![]() |
6cd3e6652a | ||
![]() |
fe7d79cee6 | ||
![]() |
2f4e7b388b | ||
![]() |
2e289cd152 | ||
![]() |
21a3dcf06c | ||
![]() |
7f56add914 | ||
![]() |
88701c6167 | ||
![]() |
e4ce6117a1 | ||
![]() |
cec2a61bdf | ||
![]() |
8275ac5853 | ||
![]() |
b7bcf97365 | ||
![]() |
fa28b480f1 | ||
![]() |
4bb95b7396 | ||
![]() |
5a9bd73e8b | ||
![]() |
4fe0276914 | ||
![]() |
5e8bda55b4 | ||
![]() |
d09c4898c1 | ||
![]() |
6ae67ed299 | ||
![]() |
32ff166a74 | ||
![]() |
8feae04281 | ||
![]() |
129f9c147b | ||
![]() |
6e336dd207 | ||
![]() |
161561c48a | ||
![]() |
c162e84383 | ||
![]() |
dc8d80a6e5 | ||
![]() |
293f67968c | ||
![]() |
4dcf26236e | ||
![]() |
a0e8d69243 | ||
![]() |
33cd9bf516 | ||
![]() |
0132797f2f | ||
![]() |
7e2db0aa4e | ||
![]() |
cc1d50491b | ||
![]() |
461b86a04b | ||
![]() |
9a3a7c28f4 | ||
![]() |
1c9d0200ca | ||
![]() |
0037cd2e69 | ||
![]() |
028ae061da | ||
![]() |
2e47763ecc | ||
![]() |
924e4a45d0 | ||
![]() |
8361b9553b | ||
![]() |
e52be20fba | ||
![]() |
da12233ade | ||
![]() |
57500f6c97 | ||
![]() |
199e17d0b1 | ||
![]() |
3b91343082 | ||
![]() |
1753c9163c | ||
![]() |
89e5953e89 | ||
![]() |
5bfd25c8c6 | ||
![]() |
e555b24f50 | ||
![]() |
14db37459f | ||
![]() |
1d9779d47c | ||
![]() |
3dedbc5457 | ||
![]() |
facb3266c6 | ||
![]() |
01fe5dd2f7 | ||
![]() |
9b22b1e499 | ||
![]() |
4bc8818145 | ||
![]() |
48ef8c86c2 | ||
![]() |
89f359a52f | ||
![]() |
13b8160d74 | ||
![]() |
f1c16d6674 | ||
![]() |
76a088e177 | ||
![]() |
630d8c3bb6 | ||
![]() |
744efa30f2 | ||
![]() |
bf4a94dc48 | ||
![]() |
ce4ba2f6f1 | ||
![]() |
5b232b5d35 | ||
![]() |
35151bbac7 | ||
![]() |
f0e959319e | ||
![]() |
d0c4475724 | ||
![]() |
99935f1e59 | ||
![]() |
fbb43821ba | ||
![]() |
c7f5c6c1d1 | ||
![]() |
d26f1fa371 | ||
![]() |
c3718ff7dd | ||
![]() |
d63493a859 | ||
![]() |
a72183851a | ||
![]() |
40b2387667 | ||
![]() |
d814aa36a7 | ||
![]() |
e37eebe4ad | ||
![]() |
0baaaefdf8 | ||
![]() |
58a58906e7 | ||
![]() |
bec0d9b00e | ||
![]() |
e6a4ab789b | ||
![]() |
36c1d3230c | ||
![]() |
30466ec3fe | ||
![]() |
ce414a5ca9 | ||
![]() |
e4e6edd573 | ||
![]() |
79927f4dc9 | ||
![]() |
603b833757 | ||
![]() |
ba99d1a10d | ||
![]() |
efe97e8f51 | ||
![]() |
5ec23bb7ab | ||
![]() |
9b4d01ab75 | ||
![]() |
40191a88d4 | ||
![]() |
a19477d179 | ||
![]() |
bf98a78f3d | ||
![]() |
ba4c2fc1bd | ||
![]() |
b56e9ef028 | ||
![]() |
dbbd34c520 | ||
![]() |
ccb69dbdfa | ||
![]() |
11e555ef6f | ||
![]() |
61e17395c9 | ||
![]() |
733ce3b6b8 | ||
![]() |
375f143199 | ||
![]() |
2419f35eb9 | ||
![]() |
21867c3576 | ||
![]() |
28853b28bc | ||
![]() |
e2f27568a5 | ||
![]() |
98b2b796b0 | ||
![]() |
b8f3fcf00b | ||
![]() |
d3fda9a821 | ||
![]() |
19e69dc13e | ||
![]() |
48543a2dad | ||
![]() |
b22f5ae5c2 | ||
![]() |
2acb6a28fe | ||
![]() |
1064cdb79d | ||
![]() |
bd7cb1c877 | ||
![]() |
6c314982dc | ||
![]() |
d54710f113 | ||
![]() |
1346156ecd | ||
![]() |
a2d9f9b417 | ||
![]() |
3de78cca2d | ||
![]() |
5fa7cd9fa9 | ||
![]() |
a78c00fb41 | ||
![]() |
edc2a03d1c | ||
![]() |
174f8f5823 | ||
![]() |
9fbc94e8d8 | ||
![]() |
6aff35196d | ||
![]() |
eceed4ed74 | ||
![]() |
7428731eac | ||
![]() |
89b07ea0ae | ||
![]() |
d16daf0fd9 | ||
![]() |
211ab4eea8 | ||
![]() |
dbd53f8d14 | ||
![]() |
a27680b8c0 | ||
![]() |
07fc9b98cc | ||
![]() |
33582c0448 | ||
![]() |
73be0fef75 | ||
![]() |
611202c905 | ||
![]() |
e553f35a68 | ||
![]() |
673649a603 | ||
![]() |
c4ed743370 | ||
![]() |
682fa0d3eb | ||
![]() |
30f34eee22 | ||
![]() |
eab76bf85b | ||
![]() |
bcf405bf9d | ||
![]() |
3c4b0d4a74 | ||
![]() |
fb9bd0eb7d | ||
![]() |
7e2dc04123 | ||
![]() |
54ec37994c | ||
![]() |
4a5935ee36 | ||
![]() |
01b9a07320 | ||
![]() |
0fcf0dcd18 | ||
![]() |
80481f142a | ||
![]() |
2be08ce7ab | ||
![]() |
37eb5af3d4 | ||
![]() |
8c8151be92 | ||
![]() |
baf31d1c1e | ||
![]() |
af2250835a | ||
![]() |
6f2a759ba3 | ||
![]() |
5065901196 | ||
![]() |
41b59e6e11 | ||
![]() |
43afdaadc6 | ||
![]() |
83c5151792 | ||
![]() |
0880ab67c6 | ||
![]() |
c0b2143c7c | ||
![]() |
c1de162c99 | ||
![]() |
a7ef8aba68 | ||
![]() |
3ee4c11a99 | ||
![]() |
990ae10dc2 | ||
![]() |
52b2fd046b | ||
![]() |
9f41f80a91 | ||
![]() |
eec4a91ad8 | ||
![]() |
7c51001c3c | ||
![]() |
a4ea4b1f5f | ||
![]() |
19fc37539e | ||
![]() |
ce7acb0feb | ||
![]() |
105b7678b8 | ||
![]() |
b67575586e | ||
![]() |
3dc6898673 | ||
![]() |
a73754c1b5 | ||
![]() |
1ebf1c00d6 | ||
![]() |
7dac7d757e | ||
![]() |
b1f3192b95 | ||
![]() |
16984d18bb | ||
![]() |
e603893d77 | ||
![]() |
a7998b30c6 | ||
![]() |
3277a4e8c3 | ||
![]() |
7e769d0e14 | ||
![]() |
713e0579f8 | ||
![]() |
6e130cc020 | ||
![]() |
eb036a12d9 | ||
![]() |
534d1f5055 | ||
![]() |
cbef909657 | ||
![]() |
874f3b32b3 | ||
![]() |
2fd017cf73 | ||
![]() |
5740b018a7 | ||
![]() |
288bf6805a | ||
![]() |
02d37a369a | ||
![]() |
1d316c3258 | ||
![]() |
a56ce62f1a | ||
![]() |
c268f42851 | ||
![]() |
7251e802ab | ||
![]() |
5b1a2d10c2 | ||
![]() |
2dd7f292b1 | ||
![]() |
213c53e307 | ||
![]() |
ce07dfd8ac | ||
![]() |
c1dba462e8 | ||
![]() |
47f0d74812 | ||
![]() |
ce80285f8d | ||
![]() |
d2dd1a43dd | ||
![]() |
12d73fe90d | ||
![]() |
c2741638b2 | ||
![]() |
4a7fb3d509 | ||
![]() |
f6ff652ca4 | ||
![]() |
6165cb0f83 | ||
![]() |
1f361b7b10 | ||
![]() |
5269ff978b | ||
![]() |
55595493a9 | ||
![]() |
ad3ff0aba7 | ||
![]() |
ce48546cef | ||
![]() |
35b3bc995e | ||
![]() |
63f60019d1 | ||
![]() |
0d741b6275 | ||
![]() |
0df9080bbb | ||
![]() |
ddcf89e6a2 | ||
![]() |
5de225d5d4 | ||
![]() |
5cddb482f1 | ||
![]() |
c000d724de | ||
![]() |
504055f331 | ||
![]() |
7f6880f40e | ||
![]() |
02e4e3c892 | ||
![]() |
b5b1849ab3 | ||
![]() |
0e10c81025 | ||
![]() |
cce7ad449a | ||
![]() |
d437dd5919 | ||
![]() |
f1980730d2 | ||
![]() |
47773e9cae | ||
![]() |
60969b0916 | ||
![]() |
ecc7925d03 | ||
![]() |
6d3010dcc7 | ||
![]() |
0164bafbf1 | ||
![]() |
98a64e3114 | ||
![]() |
6ef3d091e1 | ||
![]() |
b612c0e0d6 |
13
.devcontainer/Dockerfile
Normal file
13
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
|
DEVCONTAINER=true \
|
||||||
|
PATH=$PATH:./node_modules/.bin
|
||||||
|
|
||||||
|
# Install nvm
|
||||||
|
COPY .nvmrc /tmp/.nvmrc
|
||||||
|
RUN \
|
||||||
|
su vscode -c \
|
||||||
|
"source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1"
|
31
.devcontainer/devcontainer.json
Normal file
31
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "Home Assistant Frontend",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"context": ".."
|
||||||
|
},
|
||||||
|
"appPort": 8123,
|
||||||
|
"context": "..",
|
||||||
|
"postCreateCommand": "script/bootstrap",
|
||||||
|
"extensions": [
|
||||||
|
"github.vscode-pull-request-github",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"bierner.lit-html",
|
||||||
|
"runem.lit-plugin",
|
||||||
|
"ms-python.vscode-pylance"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
|
"files.eol": "\n",
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnType": true,
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"files.trimTrailingWhitespace": true
|
||||||
|
}
|
||||||
|
}
|
@@ -75,6 +75,7 @@
|
|||||||
"object-curly-newline": 0,
|
"object-curly-newline": 0,
|
||||||
"default-case": 0,
|
"default-case": 0,
|
||||||
"wc/no-self-class": 0,
|
"wc/no-self-class": 0,
|
||||||
|
"no-shadow": 0,
|
||||||
"@typescript-eslint/camelcase": 0,
|
"@typescript-eslint/camelcase": 0,
|
||||||
"@typescript-eslint/ban-ts-comment": 0,
|
"@typescript-eslint/ban-ts-comment": 0,
|
||||||
"@typescript-eslint/no-use-before-define": 0,
|
"@typescript-eslint/no-use-before-define": 0,
|
||||||
@@ -82,7 +83,8 @@
|
|||||||
"@typescript-eslint/no-explicit-any": 0,
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
"@typescript-eslint/no-unused-vars": 0,
|
"@typescript-eslint/no-unused-vars": 0,
|
||||||
"@typescript-eslint/explicit-function-return-type": 0,
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
"@typescript-eslint/explicit-module-boundary-types": 0
|
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||||
|
"@typescript-eslint/no-shadow": ["error"]
|
||||||
},
|
},
|
||||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||||
"processor": "disable/disable"
|
"processor": "disable/disable"
|
||||||
|
26
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
26
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: Request a feature for the UI, Frontend or Lovelace
|
|
||||||
about: Request an new feature for the Home Assistant frontend.
|
|
||||||
labels: feature request
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
DO NOT DELETE ANY TEXT from this template!
|
|
||||||
Otherwise, your request may be closed without comment.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## The request
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe to our maintainers, the feature you would like to be added.
|
|
||||||
Please be clear and concise and, if possible, provide a screenshot or mockup.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## The alternatives
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Are you currently using, or have you considered alternatives?
|
|
||||||
If so, could you please describe those?
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Additional information
|
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Request a feature for the UI, Frontend or Lovelace
|
||||||
|
url: https://github.com/home-assistant/frontend/discussions/category_choices
|
||||||
|
about: Request an new feature for the Home Assistant frontend.
|
||||||
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
|
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
|
||||||
url: https://github.com/home-assistant/core/issues
|
url: https://github.com/home-assistant/core/issues
|
||||||
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
|
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
|
||||||
|
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -18,8 +18,8 @@
|
|||||||
<!--
|
<!--
|
||||||
Describe the big picture of your changes here to communicate to the
|
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
|
maintainers why we should accept this pull request. If it fixes a bug
|
||||||
or resolves a feature request, be sure to link to that issue in the
|
or resolves a feature request, be sure to link to that issue or discussion
|
||||||
additional information section.
|
in the additional information section.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Type of change
|
## Type of change
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- This PR fixes or closes issue: fixes #
|
- This PR fixes or closes issue: fixes #
|
||||||
- This PR is related to issue:
|
- This PR is related to issue or discussion:
|
||||||
- Link to documentation pull request:
|
- Link to documentation pull request:
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
27
.github/lock.yml
vendored
27
.github/lock.yml
vendored
@@ -1,27 +0,0 @@
|
|||||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
|
||||||
|
|
||||||
# Number of days of inactivity before a closed issue or pull request is locked
|
|
||||||
daysUntilLock: 1
|
|
||||||
|
|
||||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
|
||||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
|
||||||
skipCreatedBefore: 2020-01-01
|
|
||||||
|
|
||||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
|
||||||
exemptLabels: []
|
|
||||||
|
|
||||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
|
||||||
lockLabel: false
|
|
||||||
|
|
||||||
# Comment to post before locking. Set to `false` to disable
|
|
||||||
lockComment: false
|
|
||||||
|
|
||||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
|
||||||
setLockReason: false
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
only: pulls
|
|
||||||
|
|
||||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
|
||||||
issues:
|
|
||||||
daysUntilLock: 30
|
|
56
.github/stale.yml
vendored
56
.github/stale.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
||||||
daysUntilStale: 90
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: 7
|
|
||||||
|
|
||||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
|
||||||
onlyLabels: []
|
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
||||||
exemptLabels:
|
|
||||||
- feature request
|
|
||||||
- Help wanted
|
|
||||||
- to do
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
|
||||||
exemptProjects: true
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
|
||||||
exemptMilestones: true
|
|
||||||
|
|
||||||
# Set to true to ignore issues with an assignee (defaults to false)
|
|
||||||
exemptAssignees: false
|
|
||||||
|
|
||||||
# Label to use when marking as stale
|
|
||||||
staleLabel: stale
|
|
||||||
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
There hasn't been any activity on this issue recently. Due to the high number
|
|
||||||
of incoming GitHub notifications, we have to clean some of the old issues,
|
|
||||||
as many of them have already been resolved with the latest updates.
|
|
||||||
|
|
||||||
Please make sure to update to the latest Home Assistant version and check
|
|
||||||
if that solves the issue. Let us know if that works for you by adding a
|
|
||||||
comment 👍
|
|
||||||
|
|
||||||
This issue now has been marked as stale and will be closed if no further
|
|
||||||
activity occurs. Thank you for your contributions.
|
|
||||||
|
|
||||||
# Comment to post when removing the stale label.
|
|
||||||
# unmarkComment: >
|
|
||||||
# Your comment here.
|
|
||||||
|
|
||||||
# Comment to post when closing a stale Issue or Pull Request.
|
|
||||||
# closeComment: >
|
|
||||||
# Your comment here.
|
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
|
||||||
limitPerRun: 30
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
only: issues
|
|
20
.github/workflows/lock.yml
vendored
Normal file
20
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: Lock
|
||||||
|
|
||||||
|
# yamllint disable-line rule:truthy
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v2.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-lock-inactive-days: "30"
|
||||||
|
issue-exclude-created-before: "2020-10-01T00:00:00Z"
|
||||||
|
issue-lock-reason: ""
|
||||||
|
pr-lock-inactive-days: "1"
|
||||||
|
pr-exclude-created-before: "2020-11-01T00:00:00Z"
|
||||||
|
pr-lock-reason: ""
|
42
.github/workflows/stale.yml
vendored
Normal file
42
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Stale
|
||||||
|
|
||||||
|
# yamllint disable-line rule:truthy
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 90 days stale policy
|
||||||
|
uses: actions/stale@v3.0.13
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-stale: 90
|
||||||
|
days-before-close: 7
|
||||||
|
operations-per-run: 25
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted,feature-request,feature%20request"
|
||||||
|
stale-issue-message: >
|
||||||
|
There hasn't been any activity on this issue recently. Due to the
|
||||||
|
high number of incoming GitHub notifications, we have to clean some
|
||||||
|
of the old issues, as many of them have already been resolved with
|
||||||
|
the latest updates.
|
||||||
|
|
||||||
|
Please make sure to update to the latest Home Assistant version and
|
||||||
|
check if that solves the issue. Let us know if that works for you by
|
||||||
|
adding a comment 👍
|
||||||
|
|
||||||
|
This issue has now been marked as stale and will be closed if no
|
||||||
|
further activity occurs. Thank you for your contributions.
|
||||||
|
|
||||||
|
stale-pr-label: "stale"
|
||||||
|
exempt-pr-labels: "no-stale"
|
||||||
|
stale-pr-message: >
|
||||||
|
There hasn't been any activity on this pull request recently. This
|
||||||
|
pull request has been automatically marked as stale because of that
|
||||||
|
and will be closed if no further activity occurs within 7 days.
|
||||||
|
|
||||||
|
Thank you for your contributions.
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -23,6 +23,8 @@ dist
|
|||||||
# vscode
|
# vscode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
|
||||||
# Cast dev settings
|
# Cast dev settings
|
||||||
src/cast/dev_const.ts
|
src/cast/dev_const.ts
|
||||||
@@ -33,3 +35,6 @@ yarn-error.log
|
|||||||
|
|
||||||
#asdf
|
#asdf
|
||||||
.tool-versions
|
.tool-versions
|
||||||
|
|
||||||
|
# Home Assistant config
|
||||||
|
/config
|
||||||
|
44
.vscode/launch.json
vendored
Normal file
44
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
// https://github.com/microsoft/vscode-js-debug/blob/master/OPTIONS.md
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Frontend",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"url": "http://localhost:8123/",
|
||||||
|
"webRoot": "${workspaceFolder}/hass_frontend",
|
||||||
|
"disableNetworkCache": true,
|
||||||
|
"preLaunchTask": "Develop Frontend",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/hass_frontend/frontend_latest/*.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Gallery",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"url": "http://localhost:8100/",
|
||||||
|
"webRoot": "${workspaceFolder}/gallery/dist",
|
||||||
|
"disableNetworkCache": true,
|
||||||
|
"preLaunchTask": "Develop Gallery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Demo",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"url": "http://localhost:8090/",
|
||||||
|
"webRoot": "${workspaceFolder}/demo/dist",
|
||||||
|
"disableNetworkCache": true,
|
||||||
|
"preLaunchTask": "Develop Demo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Cast",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"url": "http://localhost:8080/",
|
||||||
|
"webRoot": "${workspaceFolder}/cast/dist",
|
||||||
|
"disableNetworkCache": true,
|
||||||
|
"preLaunchTask": "Develop Cast"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
208
.vscode/tasks.json
vendored
Normal file
208
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Develop Frontend",
|
||||||
|
"type": "gulp",
|
||||||
|
"task": "develop-app",
|
||||||
|
// Sync changes here to other tasks until issue resolved
|
||||||
|
// https://github.com/Microsoft/vscode/issues/61497
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "ha-build",
|
||||||
|
"source": "ha-build",
|
||||||
|
"fileLocation": "absolute",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
|
||||||
|
"severity": 1,
|
||||||
|
"file": 2,
|
||||||
|
"message": 3,
|
||||||
|
"line": 4,
|
||||||
|
"column": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Changes detected. Starting compilation",
|
||||||
|
"endsPattern": "Build done @"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Develop Supervisor panel",
|
||||||
|
"type": "gulp",
|
||||||
|
"task": "develop-hassio",
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "ha-build",
|
||||||
|
"source": "ha-build",
|
||||||
|
"fileLocation": "absolute",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
|
||||||
|
"severity": 1,
|
||||||
|
"file": 2,
|
||||||
|
"message": 3,
|
||||||
|
"line": 4,
|
||||||
|
"column": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Changes detected. Starting compilation",
|
||||||
|
"endsPattern": "Build done @"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"group": "build",
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Develop Gallery",
|
||||||
|
"type": "gulp",
|
||||||
|
"task": "develop-gallery",
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "ha-build",
|
||||||
|
"source": "ha-build",
|
||||||
|
"fileLocation": "absolute",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
|
||||||
|
"severity": 1,
|
||||||
|
"file": 2,
|
||||||
|
"message": 3,
|
||||||
|
"line": 4,
|
||||||
|
"column": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Changes detected. Starting compilation",
|
||||||
|
"endsPattern": "Build done @"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"isBackground": true,
|
||||||
|
"group": "build",
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Develop Demo",
|
||||||
|
"type": "gulp",
|
||||||
|
"task": "develop-demo",
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "ha-build",
|
||||||
|
"source": "ha-build",
|
||||||
|
"fileLocation": "absolute",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
|
||||||
|
"severity": 1,
|
||||||
|
"file": 2,
|
||||||
|
"message": 3,
|
||||||
|
"line": 4,
|
||||||
|
"column": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Changes detected. Starting compilation",
|
||||||
|
"endsPattern": "Build done @"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"isBackground": true,
|
||||||
|
"group": "build",
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Develop Cast",
|
||||||
|
"type": "gulp",
|
||||||
|
"task": "develop-cast",
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "ha-build",
|
||||||
|
"source": "ha-build",
|
||||||
|
"fileLocation": "absolute",
|
||||||
|
"severity": "error",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
|
||||||
|
"severity": 1,
|
||||||
|
"file": 2,
|
||||||
|
"message": 3,
|
||||||
|
"line": 4,
|
||||||
|
"column": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Changes detected. Starting compilation",
|
||||||
|
"endsPattern": "Build done @"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"isBackground": true,
|
||||||
|
"group": "build",
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run HA Core in devcontainer",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "script/core",
|
||||||
|
"isBackground": true,
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": [],
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run HA Core for Supervisor in devcontainer",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core",
|
||||||
|
"isBackground": true,
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": [],
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "supervisorHost",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "The IP of the Supervisor host running the Remote API proxy add-on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "supervisorToken",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "The token for the Remote API proxy add-on"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -26,4 +26,4 @@ A complete guide can be found at the following [link](https://www.home-assistant
|
|||||||
|
|
||||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||||
|
|
||||||
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variation of devices.
|
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variety of devices.
|
||||||
|
@@ -54,7 +54,10 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
|||||||
presets: [
|
presets: [
|
||||||
!latestBuild && [
|
!latestBuild && [
|
||||||
require("@babel/preset-env").default,
|
require("@babel/preset-env").default,
|
||||||
{ modules: false, useBuiltIns: "entry", corejs: 3 },
|
{
|
||||||
|
useBuiltIns: "entry",
|
||||||
|
corejs: "3.6",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
require("@babel/preset-typescript").default,
|
require("@babel/preset-typescript").default,
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
@@ -66,7 +69,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
|||||||
],
|
],
|
||||||
// Only support the syntax, Webpack will handle it.
|
// Only support the syntax, Webpack will handle it.
|
||||||
"@babel/plugin-syntax-import-meta",
|
"@babel/plugin-syntax-import-meta",
|
||||||
"@babel/syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"@babel/plugin-proposal-optional-chaining",
|
"@babel/plugin-proposal-optional-chaining",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
[
|
[
|
||||||
|
@@ -7,7 +7,6 @@ const gulp = require("gulp");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const foreach = require("gulp-foreach");
|
const foreach = require("gulp-foreach");
|
||||||
const merge = require("gulp-merge-json");
|
const merge = require("gulp-merge-json");
|
||||||
const minify = require("gulp-jsonminify");
|
|
||||||
const rename = require("gulp-rename");
|
const rename = require("gulp-rename");
|
||||||
const transform = require("gulp-json-transform");
|
const transform = require("gulp-json-transform");
|
||||||
const { mapFiles } = require("../util");
|
const { mapFiles } = require("../util");
|
||||||
@@ -301,7 +300,6 @@ gulp.task("build-flattened-translations", function () {
|
|||||||
return flatten(data);
|
return flatten(data);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.pipe(minify())
|
|
||||||
.pipe(
|
.pipe(
|
||||||
rename((filePath) => {
|
rename((filePath) => {
|
||||||
if (filePath.dirname === "core") {
|
if (filePath.dirname === "core") {
|
||||||
|
@@ -18,6 +18,14 @@ const bothBuilds = (createConfigFunc, params) => [
|
|||||||
createConfigFunc({ ...params, latestBuild: false }),
|
createConfigFunc({ ...params, latestBuild: false }),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{
|
||||||
|
* compiler: import("webpack").Compiler,
|
||||||
|
* contentBase: string,
|
||||||
|
* port: number,
|
||||||
|
* listenHost?: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
const runDevServer = ({
|
const runDevServer = ({
|
||||||
compiler,
|
compiler,
|
||||||
contentBase,
|
contentBase,
|
||||||
@@ -33,7 +41,10 @@ const runDevServer = ({
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
// Server listening
|
// Server listening
|
||||||
log("[webpack-dev-server]", `http://localhost:${port}`);
|
log(
|
||||||
|
"[webpack-dev-server]",
|
||||||
|
`Project is running at http://localhost:${port}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handler = (done) => (err, stats) => {
|
const handler = (done) => (err, stats) => {
|
||||||
@@ -45,12 +56,12 @@ const handler = (done) => (err, stats) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
|
||||||
|
|
||||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||||
log.warn(stats.toString("minimal"));
|
console.log(stats.toString("minimal"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
var path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
polymer_dir: path.resolve(__dirname, ".."),
|
polymer_dir: path.resolve(__dirname, ".."),
|
||||||
|
@@ -4,6 +4,21 @@ const TerserPlugin = require("terser-webpack-plugin");
|
|||||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||||
const paths = require("./paths.js");
|
const paths = require("./paths.js");
|
||||||
const bundle = require("./bundle");
|
const bundle = require("./bundle");
|
||||||
|
const log = require("fancy-log");
|
||||||
|
|
||||||
|
class LogStartCompilePlugin {
|
||||||
|
ignoredFirst = false;
|
||||||
|
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.beforeCompile.tap("LogStartCompilePlugin", () => {
|
||||||
|
if (!this.ignoredFirst) {
|
||||||
|
this.ignoredFirst = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Changes detected. Starting compilation");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createWebpackConfig = ({
|
const createWebpackConfig = ({
|
||||||
entry,
|
entry,
|
||||||
@@ -29,7 +44,7 @@ const createWebpackConfig = ({
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.js$|\.ts$/,
|
test: /\.m?js$|\.ts$/,
|
||||||
exclude: bundle.babelExclude(),
|
exclude: bundle.babelExclude(),
|
||||||
use: {
|
use: {
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
@@ -45,10 +60,8 @@ const createWebpackConfig = ({
|
|||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new TerserPlugin({
|
new TerserPlugin({
|
||||||
cache: true,
|
|
||||||
parallel: true,
|
parallel: true,
|
||||||
extractComments: true,
|
extractComments: true,
|
||||||
sourceMap: true,
|
|
||||||
terserOptions: bundle.terserOptions(latestBuild),
|
terserOptions: bundle.terserOptions(latestBuild),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -97,7 +110,17 @@ const createWebpackConfig = ({
|
|||||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
||||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||||
),
|
),
|
||||||
],
|
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
new RegExp(
|
||||||
|
require.resolve(
|
||||||
|
"lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
|
||||||
|
),
|
||||||
|
!isProdBuild && new LogStartCompilePlugin(),
|
||||||
|
].filter(Boolean),
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js", ".json"],
|
extensions: [".ts", ".js", ".json"],
|
||||||
},
|
},
|
||||||
@@ -108,6 +131,22 @@ const createWebpackConfig = ({
|
|||||||
}
|
}
|
||||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||||
},
|
},
|
||||||
|
environment: {
|
||||||
|
// The environment supports arrow functions ('() => { ... }').
|
||||||
|
arrowFunction: latestBuild,
|
||||||
|
// The environment supports BigInt as literal (123n).
|
||||||
|
bigIntLiteral: false,
|
||||||
|
// The environment supports const and let for variable declarations.
|
||||||
|
const: latestBuild,
|
||||||
|
// The environment supports destructuring ('{ a, b } = obj').
|
||||||
|
destructuring: latestBuild,
|
||||||
|
// The environment supports an async import() function to import EcmaScript modules.
|
||||||
|
dynamicImport: latestBuild,
|
||||||
|
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
|
||||||
|
forOf: latestBuild,
|
||||||
|
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
|
||||||
|
module: latestBuild,
|
||||||
|
},
|
||||||
chunkFilename:
|
chunkFilename:
|
||||||
isProdBuild && !isStatsBuild
|
isProdBuild && !isStatsBuild
|
||||||
? "chunk.[chunkhash].js"
|
? "chunk.[chunkhash].js"
|
||||||
|
@@ -30,7 +30,7 @@ class HcLayout extends LitElement {
|
|||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<img class="hero" src="/images/google-nest-hub.png" />
|
<img class="hero" src="/images/google-nest-hub.png" />
|
||||||
<div class="card-header">
|
<h1 class="card-header">
|
||||||
Home Assistant Cast${this.subtitle ? ` – ${this.subtitle}` : ""}
|
Home Assistant Cast${this.subtitle ? ` – ${this.subtitle}` : ""}
|
||||||
${this.auth
|
${this.auth
|
||||||
? html`
|
? html`
|
||||||
@@ -44,7 +44,7 @@ class HcLayout extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</h1>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
|
@@ -7,205 +7,183 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
cards: [
|
cards: [
|
||||||
{ type: "custom:ha-demo-card" },
|
{ type: "custom:ha-demo-card" },
|
||||||
{
|
{
|
||||||
|
type: "grid",
|
||||||
|
columns: 4,
|
||||||
cards: [
|
cards: [
|
||||||
{
|
{
|
||||||
cards: [
|
image: "/assets/teachingbirds/isa_square.jpg",
|
||||||
|
type: "picture-entity",
|
||||||
|
show_name: false,
|
||||||
|
tap_action: {
|
||||||
|
action: "more-info",
|
||||||
|
},
|
||||||
|
entity: "sensor.presence_isa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/assets/teachingbirds/Stefan_square.jpg",
|
||||||
|
type: "picture-entity",
|
||||||
|
show_name: false,
|
||||||
|
tap_action: {
|
||||||
|
action: "more-info",
|
||||||
|
},
|
||||||
|
entity: "sensor.presence_stefan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/assets/teachingbirds/background_square.png",
|
||||||
|
elements: [
|
||||||
{
|
{
|
||||||
image: "/assets/teachingbirds/isa_square.jpg",
|
state_image: {
|
||||||
type: "picture-entity",
|
on: "/assets/teachingbirds/radiator_on.jpg",
|
||||||
show_name: false,
|
off: "/assets/teachingbirds/radiator_off.jpg",
|
||||||
|
},
|
||||||
|
type: "image",
|
||||||
|
style: {
|
||||||
|
width: "100%",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
},
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "more-info",
|
action: "more-info",
|
||||||
},
|
},
|
||||||
entity: "sensor.presence_isa",
|
entity: "switch.stefan_radiator_3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: "/assets/teachingbirds/Stefan_square.jpg",
|
style: {
|
||||||
type: "picture-entity",
|
top: "90%",
|
||||||
show_name: false,
|
left: "50%",
|
||||||
tap_action: {
|
|
||||||
action: "more-info",
|
|
||||||
},
|
},
|
||||||
entity: "sensor.presence_stefan",
|
type: "state-label",
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/assets/teachingbirds/background_square.png",
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
state_image: {
|
|
||||||
on: "/assets/teachingbirds/radiator_on.jpg",
|
|
||||||
off: "/assets/teachingbirds/radiator_off.jpg",
|
|
||||||
},
|
|
||||||
type: "image",
|
|
||||||
style: {
|
|
||||||
width: "100%",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
},
|
|
||||||
tap_action: {
|
|
||||||
action: "more-info",
|
|
||||||
},
|
|
||||||
entity: "switch.stefan_radiator_3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
top: "90%",
|
|
||||||
left: "50%",
|
|
||||||
},
|
|
||||||
type: "state-label",
|
|
||||||
entity: "sensor.temperature_stefan",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: "picture-elements",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/assets/teachingbirds/background_square.png",
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
"--mdc-icon-size": "100%",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
},
|
|
||||||
type: "icon",
|
|
||||||
tap_action: {
|
|
||||||
action: "navigate",
|
|
||||||
navigation_path: "/lovelace/home_info",
|
|
||||||
},
|
|
||||||
icon: "mdi:car",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: "picture-elements",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: "horizontal-stack",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
show_name: false,
|
|
||||||
type: "picture-entity",
|
|
||||||
name: "Alarm",
|
|
||||||
image: "/assets/teachingbirds/House_square.jpg",
|
|
||||||
entity: "alarm_control_panel.house",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Roomba",
|
|
||||||
image: "/assets/teachingbirds/roomba_square.jpg",
|
|
||||||
show_name: false,
|
|
||||||
type: "picture-entity",
|
|
||||||
state_image: {
|
|
||||||
"Not Today": "/assets/teachingbirds/roomba_bw_square.jpg",
|
|
||||||
},
|
|
||||||
entity: "input_select.roomba_mode",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
show_name: false,
|
|
||||||
type: "picture-entity",
|
|
||||||
state_image: {
|
|
||||||
Mail: "/assets/teachingbirds/mailbox_square.jpg",
|
|
||||||
"Package and mail":
|
|
||||||
"/assets/teachingbirds/mailbox_square.jpg",
|
|
||||||
Empty: "/assets/teachingbirds/mailbox_bw_square.jpg",
|
|
||||||
Package: "/assets/teachingbirds/mailbox_square.jpg",
|
|
||||||
},
|
|
||||||
entity: "sensor.mailbox",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
show_name: false,
|
|
||||||
state_image: {
|
|
||||||
"Put out": "/assets/teachingbirds/trash_square.jpg",
|
|
||||||
"Take in": "/assets/teachingbirds/trash_square.jpg",
|
|
||||||
},
|
|
||||||
type: "picture-entity",
|
|
||||||
image: "/assets/teachingbirds/trash_bear_bw_square.jpg",
|
|
||||||
entity: "sensor.trash_status",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: "horizontal-stack",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
state_image: {
|
|
||||||
Idle: "/assets/teachingbirds/washer_square.jpg",
|
|
||||||
Running: "/assets/teachingbirds/laundry_running_square.jpg",
|
|
||||||
Clean: "/assets/teachingbirds/laundry_clean_2_square.jpg",
|
|
||||||
},
|
|
||||||
entity: "input_select.washing_machine_status",
|
|
||||||
type: "picture-entity",
|
|
||||||
show_name: false,
|
|
||||||
name: "Washer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state_image: {
|
|
||||||
Idle: "/assets/teachingbirds/dryer_square.jpg",
|
|
||||||
Running: "/assets/teachingbirds/clothes_drying_square.jpg",
|
|
||||||
Clean: "/assets/teachingbirds/folded_clothes_square.jpg",
|
|
||||||
},
|
|
||||||
entity: "input_select.dryer_status",
|
|
||||||
type: "picture-entity",
|
|
||||||
show_name: false,
|
|
||||||
name: "Dryer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/assets/teachingbirds/guests_square.jpg",
|
|
||||||
type: "picture-entity",
|
|
||||||
show_name: false,
|
|
||||||
tap_action: {
|
|
||||||
action: "toggle",
|
|
||||||
},
|
|
||||||
entity: "input_boolean.guest_mode",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/assets/teachingbirds/cleaning_square.jpg",
|
|
||||||
type: "picture-entity",
|
|
||||||
show_name: false,
|
|
||||||
tap_action: {
|
|
||||||
action: "toggle",
|
|
||||||
},
|
|
||||||
entity: "input_boolean.cleaning_day",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: "horizontal-stack",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: "vertical-stack",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "vertical-stack",
|
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
cards: [
|
|
||||||
{
|
|
||||||
graph: "line",
|
|
||||||
type: "sensor",
|
|
||||||
entity: "sensor.temperature_bedroom",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
graph: "line",
|
|
||||||
type: "sensor",
|
|
||||||
name: "S's room",
|
|
||||||
entity: "sensor.temperature_stefan",
|
entity: "sensor.temperature_stefan",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: "horizontal-stack",
|
type: "picture-elements",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cards: [
|
image: "/assets/teachingbirds/background_square.png",
|
||||||
|
elements: [
|
||||||
{
|
{
|
||||||
graph: "line",
|
style: {
|
||||||
type: "sensor",
|
"--mdc-icon-size": "100%",
|
||||||
entity: "sensor.temperature_passage",
|
top: "50%",
|
||||||
},
|
left: "50%",
|
||||||
{
|
},
|
||||||
graph: "line",
|
type: "icon",
|
||||||
type: "sensor",
|
tap_action: {
|
||||||
name: "Laundry",
|
action: "navigate",
|
||||||
entity: "sensor.temperature_downstairs_bathroom",
|
navigation_path: "/lovelace/home_info",
|
||||||
|
},
|
||||||
|
icon: "mdi:car",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: "horizontal-stack",
|
type: "picture-elements",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
show_name: false,
|
||||||
|
type: "picture-entity",
|
||||||
|
name: "Alarm",
|
||||||
|
image: "/assets/teachingbirds/House_square.jpg",
|
||||||
|
entity: "alarm_control_panel.house",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Roomba",
|
||||||
|
image: "/assets/teachingbirds/roomba_square.jpg",
|
||||||
|
show_name: false,
|
||||||
|
type: "picture-entity",
|
||||||
|
state_image: {
|
||||||
|
"Not Today": "/assets/teachingbirds/roomba_bw_square.jpg",
|
||||||
|
},
|
||||||
|
entity: "input_select.roomba_mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show_name: false,
|
||||||
|
type: "picture-entity",
|
||||||
|
state_image: {
|
||||||
|
Mail: "/assets/teachingbirds/mailbox_square.jpg",
|
||||||
|
"Package and mail": "/assets/teachingbirds/mailbox_square.jpg",
|
||||||
|
Empty: "/assets/teachingbirds/mailbox_bw_square.jpg",
|
||||||
|
Package: "/assets/teachingbirds/mailbox_square.jpg",
|
||||||
|
},
|
||||||
|
entity: "sensor.mailbox",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show_name: false,
|
||||||
|
state_image: {
|
||||||
|
"Put out": "/assets/teachingbirds/trash_square.jpg",
|
||||||
|
"Take in": "/assets/teachingbirds/trash_square.jpg",
|
||||||
|
},
|
||||||
|
type: "picture-entity",
|
||||||
|
image: "/assets/teachingbirds/trash_bear_bw_square.jpg",
|
||||||
|
entity: "sensor.trash_status",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
state_image: {
|
||||||
|
Idle: "/assets/teachingbirds/washer_square.jpg",
|
||||||
|
Running: "/assets/teachingbirds/laundry_running_square.jpg",
|
||||||
|
Clean: "/assets/teachingbirds/laundry_clean_2_square.jpg",
|
||||||
|
},
|
||||||
|
entity: "input_select.washing_machine_status",
|
||||||
|
type: "picture-entity",
|
||||||
|
show_name: false,
|
||||||
|
name: "Washer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state_image: {
|
||||||
|
Idle: "/assets/teachingbirds/dryer_square.jpg",
|
||||||
|
Running: "/assets/teachingbirds/clothes_drying_square.jpg",
|
||||||
|
Clean: "/assets/teachingbirds/folded_clothes_square.jpg",
|
||||||
|
},
|
||||||
|
entity: "input_select.dryer_status",
|
||||||
|
type: "picture-entity",
|
||||||
|
show_name: false,
|
||||||
|
name: "Dryer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/assets/teachingbirds/guests_square.jpg",
|
||||||
|
type: "picture-entity",
|
||||||
|
show_name: false,
|
||||||
|
tap_action: {
|
||||||
|
action: "toggle",
|
||||||
|
},
|
||||||
|
entity: "input_boolean.guest_mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/assets/teachingbirds/cleaning_square.jpg",
|
||||||
|
type: "picture-entity",
|
||||||
|
show_name: false,
|
||||||
|
tap_action: {
|
||||||
|
action: "toggle",
|
||||||
|
},
|
||||||
|
entity: "input_boolean.cleaning_day",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
columns: 2,
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
graph: "line",
|
||||||
|
type: "sensor",
|
||||||
|
entity: "sensor.temperature_bedroom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
graph: "line",
|
||||||
|
type: "sensor",
|
||||||
|
name: "S's room",
|
||||||
|
entity: "sensor.temperature_stefan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
graph: "line",
|
||||||
|
type: "sensor",
|
||||||
|
entity: "sensor.temperature_passage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
graph: "line",
|
||||||
|
type: "sensor",
|
||||||
|
name: "Laundry",
|
||||||
|
entity: "sensor.temperature_downstairs_bathroom",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -6,4 +6,11 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
|
|||||||
body: { message: "Template dev tool does not work in the demo." },
|
body: { message: "Template dev tool does not work in the demo." },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
hass.mockWS("render_template", (msg, onChange) => {
|
||||||
|
onChange!({
|
||||||
|
result: msg.template,
|
||||||
|
listeners: { all: false, domains: [], entities: [], time: false },
|
||||||
|
});
|
||||||
|
return () => {};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
BIN
gallery/public/images/sunflowers.jpg
Normal file
BIN
gallery/public/images/sunflowers.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
@@ -5,11 +5,16 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|||||||
import "../../../src/components/ha-switch";
|
import "../../../src/components/ha-switch";
|
||||||
import "../../../src/components/ha-formfield";
|
import "../../../src/components/ha-formfield";
|
||||||
import "./demo-card";
|
import "./demo-card";
|
||||||
|
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||||
|
|
||||||
class DemoCards extends PolymerElement {
|
class DemoCards extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
|
#container {
|
||||||
|
min-height: calc(100vh - 128px);
|
||||||
|
background: var(--primary-background-color);
|
||||||
|
}
|
||||||
.cards {
|
.cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -24,6 +29,9 @@ class DemoCards extends PolymerElement {
|
|||||||
.filters {
|
.filters {
|
||||||
margin-left: 60px;
|
margin-left: 60px;
|
||||||
}
|
}
|
||||||
|
ha-formfield {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
@@ -31,16 +39,21 @@ class DemoCards extends PolymerElement {
|
|||||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||||
</ha-switch>
|
</ha-switch>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
<ha-formfield label="Dark theme">
|
||||||
|
<ha-switch on-change="_darkThemeToggled"> </ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
</div>
|
</div>
|
||||||
</app-toolbar>
|
</app-toolbar>
|
||||||
<div class="cards">
|
<div id="container">
|
||||||
<template is="dom-repeat" items="[[configs]]">
|
<div class="cards">
|
||||||
<demo-card
|
<template is="dom-repeat" items="[[configs]]">
|
||||||
config="[[item]]"
|
<demo-card
|
||||||
show-config="[[_showConfig]]"
|
config="[[item]]"
|
||||||
hass="[[hass]]"
|
show-config="[[_showConfig]]"
|
||||||
></demo-card>
|
hass="[[hass]]"
|
||||||
</template>
|
></demo-card>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -59,6 +72,12 @@ class DemoCards extends PolymerElement {
|
|||||||
_showConfigToggled(ev) {
|
_showConfigToggled(ev) {
|
||||||
this._showConfig = ev.target.checked;
|
this._showConfig = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_darkThemeToggled(ev) {
|
||||||
|
applyThemesOnElement(this.$.container, { themes: {} }, "default", {
|
||||||
|
dark: ev.target.checked,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("demo-cards", DemoCards);
|
customElements.define("demo-cards", DemoCards);
|
||||||
|
@@ -3,7 +3,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/state-summary/state-card-content";
|
import "../../../src/state-summary/state-card-content";
|
||||||
import "./more-info-content";
|
import "../../../src/dialogs/more-info/more-info-content";
|
||||||
|
|
||||||
class DemoMoreInfo extends PolymerElement {
|
class DemoMoreInfo extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
@@ -16,15 +16,12 @@ class DemoMoreInfo extends PolymerElement {
|
|||||||
|
|
||||||
ha-card {
|
ha-card {
|
||||||
width: 333px;
|
width: 333px;
|
||||||
|
padding: 20px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
state-card-content {
|
state-card-content {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
|
||||||
|
|
||||||
more-info-content {
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
@@ -1,73 +0,0 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
|
||||||
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
|
|
||||||
import { stateMoreInfoType } from "../../../src/dialogs/more-info/state_more_info_control";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-automation";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-camera";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-climate";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-configurator";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-counter";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-cover";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-default";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-fan";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-group";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-light";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-lock";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-media_player";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-person";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-script";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-sun";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-timer";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
|
|
||||||
import "../../../src/dialogs/more-info/controls/more-info-weather";
|
|
||||||
import { HomeAssistant } from "../../../src/types";
|
|
||||||
|
|
||||||
class MoreInfoContent extends UpdatingElement {
|
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public stateObj?: HassEntity;
|
|
||||||
|
|
||||||
private _detachedChild?: ChildNode;
|
|
||||||
|
|
||||||
protected firstUpdated(): void {
|
|
||||||
this.style.position = "relative";
|
|
||||||
this.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is not a lit element, but an updating element, so we implement update
|
|
||||||
protected update(changedProps: PropertyValues): void {
|
|
||||||
super.update(changedProps);
|
|
||||||
const stateObj = this.stateObj;
|
|
||||||
const hass = this.hass;
|
|
||||||
|
|
||||||
if (!stateObj || !hass) {
|
|
||||||
if (this.lastChild) {
|
|
||||||
this._detachedChild = this.lastChild;
|
|
||||||
// Detach child to prevent it from doing work.
|
|
||||||
this.removeChild(this.lastChild);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._detachedChild) {
|
|
||||||
this.appendChild(this._detachedChild);
|
|
||||||
this._detachedChild = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moreInfoType =
|
|
||||||
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
|
|
||||||
? stateObj.attributes.custom_ui_more_info
|
|
||||||
: "more-info-" + stateMoreInfoType(stateObj);
|
|
||||||
|
|
||||||
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
|
|
||||||
hass,
|
|
||||||
stateObj,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-content", MoreInfoContent);
|
|
@@ -6,7 +6,9 @@ export const createMediaPlayerEntities = () => [
|
|||||||
media_content_type: "music",
|
media_content_type: "music",
|
||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
media_artist: "Technohead",
|
media_artist: "Technohead",
|
||||||
supported_features: 64063,
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
|
||||||
|
supported_features: 195135,
|
||||||
entity_picture: "/images/album_cover_2.jpg",
|
entity_picture: "/images/album_cover_2.jpg",
|
||||||
media_duration: 300,
|
media_duration: 300,
|
||||||
media_position: 50,
|
media_position: 50,
|
||||||
@@ -14,12 +16,15 @@ export const createMediaPlayerEntities = () => [
|
|||||||
// 23 seconds in
|
// 23 seconds in
|
||||||
new Date().getTime() - 23000
|
new Date().getTime() - 23000
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
|
volume_level: 0.5,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "music_playing", "playing", {
|
getEntity("media_player", "music_playing", "playing", {
|
||||||
friendly_name: "Playing The Music",
|
friendly_name: "Playing The Music",
|
||||||
media_content_type: "music",
|
media_content_type: "music",
|
||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
media_artist: "Technohead",
|
media_artist: "Technohead",
|
||||||
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Stop + Clear + Play + Shuffle Set
|
||||||
supported_features: 64063,
|
supported_features: 64063,
|
||||||
entity_picture: "/images/album_cover.jpg",
|
entity_picture: "/images/album_cover.jpg",
|
||||||
media_duration: 300,
|
media_duration: 300,
|
||||||
@@ -28,6 +33,7 @@ export const createMediaPlayerEntities = () => [
|
|||||||
// 23 seconds in
|
// 23 seconds in
|
||||||
new Date().getTime() - 23000
|
new Date().getTime() - 23000
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
|
volume_level: 0.5,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "stream_playing", "playing", {
|
getEntity("media_player", "stream_playing", "playing", {
|
||||||
friendly_name: "Playing the Stream",
|
friendly_name: "Playing the Stream",
|
||||||
@@ -35,50 +41,125 @@ export const createMediaPlayerEntities = () => [
|
|||||||
media_title: "Epic sax guy 10 hours",
|
media_title: "Epic sax guy 10 hours",
|
||||||
app_name: "YouTube",
|
app_name: "YouTube",
|
||||||
entity_picture: "/images/frenck.jpg",
|
entity_picture: "/images/frenck.jpg",
|
||||||
supported_features: 33,
|
// Pause + Next Track + Play + Browse Media
|
||||||
|
supported_features: 147489,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "living_room", "playing", {
|
getEntity("media_player", "stream_paused", "paused", {
|
||||||
friendly_name: "Pause, No skip, tvshow",
|
friendly_name: "Paused the Stream",
|
||||||
|
media_content_type: "movie",
|
||||||
|
media_title: "Epic sax guy 10 hours",
|
||||||
|
app_name: "YouTube",
|
||||||
|
entity_picture: "/images/frenck.jpg",
|
||||||
|
// Pause + Next Track + Play
|
||||||
|
supported_features: 16417,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "stream_playing_previous", "playing", {
|
||||||
|
friendly_name: 'Playing the Stream (with "previous" support)',
|
||||||
|
media_content_type: "movie",
|
||||||
|
media_title: "Epic sax guy 10 hours",
|
||||||
|
app_name: "YouTube",
|
||||||
|
entity_picture: "/images/frenck.jpg",
|
||||||
|
// Pause + Previous Track + Play
|
||||||
|
supported_features: 16401,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "tv_playing", "playing", {
|
||||||
|
friendly_name: "Playing non-skip TV Show",
|
||||||
media_content_type: "tvshow",
|
media_content_type: "tvshow",
|
||||||
media_title: "Chapter 1",
|
media_title: "Chapter 1",
|
||||||
media_series_title: "House of Cards",
|
media_series_title: "House of Cards",
|
||||||
app_name: "Netflix",
|
app_name: "Netflix",
|
||||||
entity_picture: "/images/netflix.jpg",
|
entity_picture: "/images/netflix.jpg",
|
||||||
|
// Pause
|
||||||
supported_features: 1,
|
supported_features: 1,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "sonos_idle", "idle", {
|
getEntity("media_player", "sonos_idle", "idle", {
|
||||||
friendly_name: "Sonos Idle",
|
friendly_name: "Sonos Idle",
|
||||||
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Stop + Clear + Play + Shuffle Set
|
||||||
supported_features: 64063,
|
supported_features: 64063,
|
||||||
|
volume_level: 0.33,
|
||||||
|
is_volume_muted: true,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "theater", "off", {
|
getEntity("media_player", "idle_browse_media", "idle", {
|
||||||
|
friendly_name: "Idle waiting for Browse Media (e.g. Spotify)",
|
||||||
|
// Pause + Seek + Volume Set + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Play + Shuffle Set + Browse Media
|
||||||
|
supported_features: 182839,
|
||||||
|
volume_level: 0.79,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_off", "off", {
|
||||||
friendly_name: "TV Off",
|
friendly_name: "TV Off",
|
||||||
|
// On + Off + Play + Next + Pause
|
||||||
|
supported_features: 16801,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_on", "on", {
|
||||||
|
friendly_name: "TV On",
|
||||||
|
// On + Off + Play + Next + Pause
|
||||||
|
supported_features: 16801,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_off_static", "off", {
|
||||||
|
friendly_name: "TV Off (cannot be switched on)",
|
||||||
|
// Off + Next + Pause
|
||||||
|
supported_features: 289,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_on_static", "on", {
|
||||||
|
friendly_name: "TV On (cannot be switched off)",
|
||||||
|
// On + Next + Pause
|
||||||
supported_features: 161,
|
supported_features: 161,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "android_cast", "playing", {
|
getEntity("media_player", "android_cast", "playing", {
|
||||||
friendly_name: "Casting App",
|
friendly_name: "Casting App (no supported features)",
|
||||||
media_title: "Android Screen Casting",
|
media_title: "Android Screen Casting",
|
||||||
app_name: "Screen Mirroring",
|
app_name: "Screen Mirroring",
|
||||||
// supported_features: 21437,
|
}),
|
||||||
|
getEntity("media_player", "image_display", "playing", {
|
||||||
|
friendly_name: "Digital Picture Frame",
|
||||||
|
media_content_type: "image",
|
||||||
|
media_title: "Famous Painting",
|
||||||
|
media_artist: "Famous Artist",
|
||||||
|
entity_picture: "/images/sunflowers.jpg",
|
||||||
|
// On + Off + Browse Media
|
||||||
|
supported_features: 131456,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "unavailable", "unavailable", {
|
getEntity("media_player", "unavailable", "unavailable", {
|
||||||
friendly_name: "Player Unavailable",
|
friendly_name: "Player Unavailable",
|
||||||
|
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
supported_features: 21437,
|
supported_features: 21437,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "unknown", "unknown", {
|
getEntity("media_player", "unknown", "unknown", {
|
||||||
friendly_name: "Player Unknown",
|
friendly_name: "Player Unknown",
|
||||||
|
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
supported_features: 21437,
|
supported_features: 21437,
|
||||||
}),
|
}),
|
||||||
|
getEntity("media_player", "playing", "playing", {
|
||||||
|
friendly_name: "Player Playing (no Pause support)",
|
||||||
|
// Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
|
supported_features: 21436,
|
||||||
|
volume_level: 1,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "idle", "idle", {
|
||||||
|
friendly_name: "Player Idle",
|
||||||
|
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
|
supported_features: 21437,
|
||||||
|
volume_level: 0,
|
||||||
|
}),
|
||||||
getEntity("media_player", "receiver_on", "on", {
|
getEntity("media_player", "receiver_on", "on", {
|
||||||
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
volume_level: 0.63,
|
volume_level: 0.63,
|
||||||
is_volume_muted: false,
|
is_volume_muted: false,
|
||||||
source: "TV",
|
source: "TV",
|
||||||
friendly_name: "Receiver",
|
friendly_name: "Receiver (selectable sources)",
|
||||||
|
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
||||||
supported_features: 84364,
|
supported_features: 84364,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "receiver_off", "off", {
|
getEntity("media_player", "receiver_off", "off", {
|
||||||
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
friendly_name: "Receiver",
|
friendly_name: "Receiver (selectable sources)",
|
||||||
|
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
||||||
supported_features: 84364,
|
supported_features: 84364,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@@ -15,6 +15,10 @@ const ENTITIES = [
|
|||||||
getEntity("alarm_control_panel", "unavailable", "unavailable", {
|
getEntity("alarm_control_panel", "unavailable", "unavailable", {
|
||||||
friendly_name: "Alarm",
|
friendly_name: "Alarm",
|
||||||
}),
|
}),
|
||||||
|
getEntity("alarm_control_panel", "alarm_code", "disarmed", {
|
||||||
|
friendly_name: "Alarm",
|
||||||
|
code_format: "number",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -30,7 +34,14 @@ const CONFIGS = [
|
|||||||
config: `
|
config: `
|
||||||
- type: alarm-panel
|
- type: alarm-panel
|
||||||
entity: alarm_control_panel.alarm_armed
|
entity: alarm_control_panel.alarm_armed
|
||||||
title: My Alarm
|
name: My Alarm
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Code Example",
|
||||||
|
config: `
|
||||||
|
- type: alarm-panel
|
||||||
|
entity: alarm_control_panel.alarm_code
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -83,8 +94,12 @@ class DemoAlarmPanelEntity extends PolymerElement {
|
|||||||
|
|
||||||
public ready() {
|
public ready() {
|
||||||
super.ready();
|
super.ready();
|
||||||
|
this._setupDemo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setupDemo() {
|
||||||
const hass = provideHass(this.$.demos);
|
const hass = provideHass(this.$.demos);
|
||||||
hass.updateTranslations(null, "en");
|
await hass.updateTranslations(null, "en");
|
||||||
hass.addEntities(ENTITIES);
|
hass.addEntities(ENTITIES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -98,4 +98,4 @@ class DemoButtonEntity extends PolymerElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("demo-hui-button-card", DemoButtonEntity);
|
customElements.define("demo-hui-entity-button-card", DemoButtonEntity);
|
||||||
|
@@ -7,7 +7,10 @@ import "../components/demo-cards";
|
|||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("sensor", "brightness", "12", {}),
|
getEntity("sensor", "brightness", "12", {}),
|
||||||
|
getEntity("sensor", "brightness_medium", "53", {}),
|
||||||
|
getEntity("sensor", "brightness_high", "87", {}),
|
||||||
getEntity("plant", "bonsai", "ok", {}),
|
getEntity("plant", "bonsai", "ok", {}),
|
||||||
|
getEntity("sensor", "not_working", "unavailable", {}),
|
||||||
getEntity("sensor", "outside_humidity", "54", {
|
getEntity("sensor", "outside_humidity", "54", {
|
||||||
unit_of_measurement: "%",
|
unit_of_measurement: "%",
|
||||||
}),
|
}),
|
||||||
@@ -20,16 +23,10 @@ const CONFIGS = [
|
|||||||
{
|
{
|
||||||
heading: "Basic example",
|
heading: "Basic example",
|
||||||
config: `
|
config: `
|
||||||
- type: gauge
|
|
||||||
entity: sensor.brightness
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
heading: "With title",
|
|
||||||
config: `
|
|
||||||
- type: gauge
|
- type: gauge
|
||||||
title: Humidity
|
title: Humidity
|
||||||
entity: sensor.outside_humidity
|
entity: sensor.outside_humidity
|
||||||
|
name: Outside Humidity
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -38,6 +35,7 @@ const CONFIGS = [
|
|||||||
- type: gauge
|
- type: gauge
|
||||||
entity: sensor.outside_temperature
|
entity: sensor.outside_temperature
|
||||||
unit_of_measurement: C
|
unit_of_measurement: C
|
||||||
|
name: Outside Temperature
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -45,19 +43,45 @@ const CONFIGS = [
|
|||||||
config: `
|
config: `
|
||||||
- type: gauge
|
- type: gauge
|
||||||
entity: sensor.brightness
|
entity: sensor.brightness
|
||||||
|
name: Brightness Low
|
||||||
severity:
|
severity:
|
||||||
red: 32
|
red: 75
|
||||||
green: 0
|
green: 0
|
||||||
yellow: 23
|
yellow: 50
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Setting Min and Max Values",
|
heading: "Setting Severity Levels",
|
||||||
|
config: `
|
||||||
|
- type: gauge
|
||||||
|
entity: sensor.brightness_medium
|
||||||
|
name: Brightness Medium
|
||||||
|
severity:
|
||||||
|
red: 75
|
||||||
|
green: 0
|
||||||
|
yellow: 50
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Setting Severity Levels",
|
||||||
|
config: `
|
||||||
|
- type: gauge
|
||||||
|
entity: sensor.brightness_high
|
||||||
|
name: Brightness High
|
||||||
|
severity:
|
||||||
|
red: 75
|
||||||
|
green: 0
|
||||||
|
yellow: 50
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Setting Min (0) and Max (15) Values",
|
||||||
config: `
|
config: `
|
||||||
- type: gauge
|
- type: gauge
|
||||||
entity: sensor.brightness
|
entity: sensor.brightness
|
||||||
|
name: Brightness
|
||||||
min: 0
|
min: 0
|
||||||
max: 38
|
max: 15
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,6 +98,13 @@ const CONFIGS = [
|
|||||||
entity: plant.bonsai
|
entity: plant.bonsai
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Unavailable entity",
|
||||||
|
config: `
|
||||||
|
- type: gauge
|
||||||
|
entity: sensor.not_working
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
class DemoGaugeEntity extends PolymerElement {
|
class DemoGaugeEntity extends PolymerElement {
|
||||||
|
@@ -8,29 +8,43 @@ import "../components/demo-cards";
|
|||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("light", "bed_light", "on", {
|
getEntity("light", "bed_light", "on", {
|
||||||
friendly_name: "Bed Light",
|
friendly_name: "Bed Light",
|
||||||
brightness: 130,
|
brightness: 255,
|
||||||
}),
|
}),
|
||||||
getEntity("light", "dim", "off", {
|
getEntity("light", "dim_on", "on", {
|
||||||
|
friendly_name: "Dining Room",
|
||||||
|
supported_features: 1,
|
||||||
|
brightness: 100,
|
||||||
|
}),
|
||||||
|
getEntity("light", "dim_off", "off", {
|
||||||
|
friendly_name: "Dining Room",
|
||||||
supported_features: 1,
|
supported_features: 1,
|
||||||
}),
|
}),
|
||||||
getEntity("light", "unavailable", "unavailable", {
|
getEntity("light", "unavailable", "unavailable", {
|
||||||
|
friendly_name: "Lost Light",
|
||||||
supported_features: 1,
|
supported_features: 1,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: "Basic example",
|
heading: "Switchable Light",
|
||||||
config: `
|
config: `
|
||||||
- type: light
|
- type: light
|
||||||
entity: light.bed_light
|
entity: light.bed_light
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Dim",
|
heading: "Dimmable Light On",
|
||||||
config: `
|
config: `
|
||||||
- type: light
|
- type: light
|
||||||
entity: light.dim
|
entity: light.dim_on
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Dimmable Light Off",
|
||||||
|
config: `
|
||||||
|
- type: light
|
||||||
|
entity: light.dim_off
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
import { mockTemplate } from "../../../demo/src/stubs/template";
|
||||||
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
import "../components/demo-cards";
|
import "../components/demo-cards";
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -254,7 +256,7 @@ const CONFIGS = [
|
|||||||
|
|
||||||
class DemoMarkdown extends PolymerElement {
|
class DemoMarkdown extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html` <demo-cards configs="[[_configs]]"></demo-cards> `;
|
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -265,6 +267,12 @@ class DemoMarkdown extends PolymerElement {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ready() {
|
||||||
|
super.ready();
|
||||||
|
const hass = provideHass(this.$.demos);
|
||||||
|
mockTemplate(hass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("demo-hui-markdown-card", DemoMarkdown);
|
customElements.define("demo-hui-markdown-card", DemoMarkdown);
|
||||||
|
@@ -7,40 +7,61 @@ import { createMediaPlayerEntities } from "../data/media_players";
|
|||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: "Paused music",
|
heading: "Paused Music",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.music_paused
|
entity: media_player.music_paused
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Playing music",
|
heading: "Playing Music",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.music_playing
|
entity: media_player.music_playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Playing stream",
|
heading: "Playing Stream",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.stream_playing
|
entity: media_player.stream_playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Pause, No skip, tvshow",
|
heading: "Paused Stream",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.living_room
|
entity: media_player.stream_paused
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Screen casting",
|
heading: 'Playing Stream (with "previous" support)',
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.stream_playing_previous
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Playing non-skip TV Show",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.tv_playing
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Screen Casting",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.android_cast
|
entity: media_player.android_cast
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Digital Picture Frame",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.image_display
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Sonos Idle",
|
heading: "Sonos Idle",
|
||||||
config: `
|
config: `
|
||||||
@@ -48,11 +69,53 @@ const CONFIGS = [
|
|||||||
entity: media_player.sonos_idle
|
entity: media_player.sonos_idle
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Idle waiting for Browse Media",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.idle_browse_media
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Player Off",
|
heading: "Player Off",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.theater
|
entity: media_player.theater_off
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player On",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.theater_on
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player Off (cannot be switched on)",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.theater_off_static
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player On (cannot be switched off)",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.theater_on_static
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player Idle",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.idle
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player Playing",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,14 +133,14 @@ const CONFIGS = [
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Receiver On",
|
heading: "Receiver On (selectable sources)",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.receiver_on
|
entity: media_player.receiver_on
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Receiver Off",
|
heading: "Receiver Off (selectable sources)",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.receiver_off
|
entity: media_player.receiver_off
|
||||||
|
@@ -12,23 +12,45 @@ const CONFIGS = [
|
|||||||
- type: entities
|
- type: entities
|
||||||
entities:
|
entities:
|
||||||
- entity: media_player.music_paused
|
- entity: media_player.music_paused
|
||||||
name: Paused music
|
name: Paused Music
|
||||||
- entity: media_player.music_playing
|
- entity: media_player.music_playing
|
||||||
name: Playing music
|
name: Playing Music
|
||||||
- entity: media_player.stream_playing
|
- entity: media_player.stream_playing
|
||||||
name: Paused, no play
|
name: Playing Stream
|
||||||
- entity: media_player.living_room
|
- entity: media_player.stream_paused
|
||||||
name: Pause, No skip, tvshow
|
name: Paused Stream
|
||||||
|
- entity: media_player.stream_playing_previous
|
||||||
|
name: Playing Stream (with "previous" support)
|
||||||
|
- entity: media_player.tv_playing
|
||||||
|
name: Playing non-skip TV Show
|
||||||
- entity: media_player.android_cast
|
- entity: media_player.android_cast
|
||||||
name: Screen casting
|
name: Screen casting
|
||||||
|
- entity: media_player.image_display
|
||||||
|
name: Digital Picture Frame
|
||||||
- entity: media_player.sonos_idle
|
- entity: media_player.sonos_idle
|
||||||
name: Chromcast Idle
|
name: Sonos Idle
|
||||||
- entity: media_player.theater
|
- entity: media_player.idle_browse_media
|
||||||
|
name: Idle waiting for Browse Media
|
||||||
|
- entity: media_player.theater_off
|
||||||
name: Player Off
|
name: Player Off
|
||||||
|
- entity: media_player.theater_on
|
||||||
|
name: Player On
|
||||||
|
- entity: media_player.theater_off_static
|
||||||
|
name: Player Off (cannot be switched on)
|
||||||
|
- entity: media_player.theater_on_static
|
||||||
|
name: Player On (cannot be switched off)
|
||||||
|
- entity: media_player.idle
|
||||||
|
name: Player Idle
|
||||||
|
- entity: media_player.playing
|
||||||
|
name: Player Playing
|
||||||
- entity: media_player.unavailable
|
- entity: media_player.unavailable
|
||||||
name: Player Unavailable
|
name: Player Unavailable
|
||||||
- entity: media_player.unknown
|
- entity: media_player.unknown
|
||||||
name: Player Unknown
|
name: Player Unknown
|
||||||
|
- entity: media_player.receiver_on
|
||||||
|
name: Receiver On (selectable sources)
|
||||||
|
- entity: media_player.receiver_off
|
||||||
|
name: Receiver Off (selectable sources)
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
import { mockHistory } from "../../../demo/src/stubs/history";
|
||||||
import { getEntity } from "../../../src/fake_data/entity";
|
import { getEntity } from "../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
import "../components/demo-cards";
|
import "../components/demo-cards";
|
||||||
@@ -36,6 +37,10 @@ const ENTITIES = [
|
|||||||
battery: 71,
|
battery: 71,
|
||||||
friendly_name: "Home Boy",
|
friendly_name: "Home Boy",
|
||||||
}),
|
}),
|
||||||
|
getEntity("sensor", "illumination", "23", {
|
||||||
|
friendly_name: "Illumination",
|
||||||
|
unit_of_measurement: "lx",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -89,6 +94,42 @@ const CONFIGS = [
|
|||||||
entity: light.bed_light
|
entity: light.bed_light
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Default Grid",
|
||||||
|
config: `
|
||||||
|
- type: grid
|
||||||
|
cards:
|
||||||
|
- type: entity
|
||||||
|
entity: light.kitchen_lights
|
||||||
|
- type: entity
|
||||||
|
entity: light.bed_light
|
||||||
|
- type: entity
|
||||||
|
entity: device_tracker.demo_paulus
|
||||||
|
- type: sensor
|
||||||
|
entity: sensor.illumination
|
||||||
|
graph: line
|
||||||
|
- type: entity
|
||||||
|
entity: device_tracker.demo_anne_therese
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Non-square Grid with 2 columns",
|
||||||
|
config: `
|
||||||
|
- type: grid
|
||||||
|
columns: 2
|
||||||
|
square: false
|
||||||
|
cards:
|
||||||
|
- type: entity
|
||||||
|
entity: light.kitchen_lights
|
||||||
|
- type: entity
|
||||||
|
entity: light.bed_light
|
||||||
|
- type: entity
|
||||||
|
entity: device_tracker.demo_paulus
|
||||||
|
- type: sensor
|
||||||
|
entity: sensor.illumination
|
||||||
|
graph: line
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
class DemoStack extends PolymerElement {
|
class DemoStack extends PolymerElement {
|
||||||
@@ -110,6 +151,7 @@ class DemoStack extends PolymerElement {
|
|||||||
const hass = provideHass(this.$.demos);
|
const hass = provideHass(this.$.demos);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
hass.addEntities(ENTITIES);
|
hass.addEntities(ENTITIES);
|
||||||
|
mockHistory(hass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
|||||||
import { getEntity } from "../../../src/fake_data/entity";
|
import { getEntity } from "../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
import "../components/demo-more-infos";
|
import "../components/demo-more-infos";
|
||||||
import "../components/more-info-content";
|
import "../../../src/dialogs/more-info/more-info-content";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("light", "bed_light", "on", {
|
getEntity("light", "bed_light", "on", {
|
||||||
@@ -40,8 +40,12 @@ class DemoMoreInfoLight extends PolymerElement {
|
|||||||
|
|
||||||
public ready() {
|
public ready() {
|
||||||
super.ready();
|
super.ready();
|
||||||
|
this._setupDemo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setupDemo() {
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
await hass.updateTranslations(null, "en");
|
||||||
hass.addEntities(ENTITIES);
|
hass.addEntities(ENTITIES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,9 +23,9 @@ import { hassioStyle } from "../resources/hassio-style";
|
|||||||
class HassioAddonRepositoryEl extends LitElement {
|
class HassioAddonRepositoryEl extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public repo!: HassioAddonRepository;
|
@property({ attribute: false }) public repo!: HassioAddonRepository;
|
||||||
|
|
||||||
@property() public addons!: HassioAddonInfo[];
|
@property({ attribute: false }) public addons!: HassioAddonInfo[];
|
||||||
|
|
||||||
@property() public filter!: string;
|
@property() public filter!: string;
|
||||||
|
|
||||||
@@ -78,18 +78,18 @@ class HassioAddonRepositoryEl extends LitElement {
|
|||||||
.title=${addon.name}
|
.title=${addon.name}
|
||||||
.description=${addon.description}
|
.description=${addon.description}
|
||||||
.available=${addon.available}
|
.available=${addon.available}
|
||||||
.icon=${addon.installed && addon.installed !== addon.version
|
.icon=${addon.installed && addon.update_available
|
||||||
? mdiArrowUpBoldCircle
|
? mdiArrowUpBoldCircle
|
||||||
: mdiPuzzle}
|
: mdiPuzzle}
|
||||||
.iconTitle=${addon.installed
|
.iconTitle=${addon.installed
|
||||||
? addon.installed !== addon.version
|
? addon.update_available
|
||||||
? "New version available"
|
? "New version available"
|
||||||
: "Add-on is installed"
|
: "Add-on is installed"
|
||||||
: addon.available
|
: addon.available
|
||||||
? "Add-on is not installed"
|
? "Add-on is not installed"
|
||||||
: "Add-on is not available on your system"}
|
: "Add-on is not available on your system"}
|
||||||
.iconClass=${addon.installed
|
.iconClass=${addon.installed
|
||||||
? addon.installed !== addon.version
|
? addon.update_available
|
||||||
? "update"
|
? "update"
|
||||||
: "installed"
|
: "installed"
|
||||||
: !addon.available
|
: !addon.available
|
||||||
@@ -104,7 +104,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
|||||||
: undefined}
|
: undefined}
|
||||||
.showTopbar=${addon.installed || !addon.available}
|
.showTopbar=${addon.installed || !addon.available}
|
||||||
.topbarClass=${addon.installed
|
.topbarClass=${addon.installed
|
||||||
? addon.installed !== addon.version
|
? addon.update_available
|
||||||
? "update"
|
? "update"
|
||||||
: "installed"
|
: "installed"
|
||||||
: !addon.available
|
: !addon.available
|
||||||
|
@@ -11,6 +11,7 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { html, TemplateResult } from "lit-html";
|
import { html, TemplateResult } from "lit-html";
|
||||||
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import "../../../src/common/search/search-input";
|
import "../../../src/common/search/search-input";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-svg-icon";
|
import "../../../src/components/ha-svg-icon";
|
||||||
@@ -24,6 +25,7 @@ import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
|||||||
import "../../../src/layouts/hass-loading-screen";
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
import "../../../src/layouts/hass-tabs-subpage";
|
import "../../../src/layouts/hass-tabs-subpage";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
|
import { showRegistriesDialog } from "../dialogs/registries/show-dialog-registries";
|
||||||
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
|
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
|
||||||
import { supervisorTabs } from "../hassio-tabs";
|
import { supervisorTabs } from "../hassio-tabs";
|
||||||
import "./hassio-addon-repository";
|
import "./hassio-addon-repository";
|
||||||
@@ -113,6 +115,12 @@ class HassioAddonStore extends LitElement {
|
|||||||
<mwc-list-item>
|
<mwc-list-item>
|
||||||
Reload
|
Reload
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
|
${this.hass.userData?.showAdvanced &&
|
||||||
|
atLeastVersion(this.hass.config.version, 0, 117)
|
||||||
|
? html`<mwc-list-item>
|
||||||
|
Registries
|
||||||
|
</mwc-list-item>`
|
||||||
|
: ""}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
${repos.length === 0
|
${repos.length === 0
|
||||||
? html`<hass-loading-screen no-toolbar></hass-loading-screen>`
|
? html`<hass-loading-screen no-toolbar></hass-loading-screen>`
|
||||||
@@ -157,6 +165,9 @@ class HassioAddonStore extends LitElement {
|
|||||||
case 1:
|
case 1:
|
||||||
this.refreshData();
|
this.refreshData();
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
this._manageRegistries();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +184,10 @@ class HassioAddonStore extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _manageRegistries() {
|
||||||
|
showRegistriesDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
private async _loadData() {
|
private async _loadData() {
|
||||||
try {
|
try {
|
||||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||||
|
@@ -39,13 +39,11 @@ class HassioAddonConfig extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) private _configHasChanged = false;
|
@property({ type: Boolean }) private _configHasChanged = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) private _valid = true;
|
||||||
|
|
||||||
@query("ha-yaml-editor", true) private _editor!: HaYamlEditor;
|
@query("ha-yaml-editor", true) private _editor!: HaYamlEditor;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const editor = this._editor;
|
|
||||||
// If editor not rendered, don't show the error.
|
|
||||||
const valid = editor ? editor.isValid : true;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<h1>${this.addon.name}</h1>
|
<h1>${this.addon.name}</h1>
|
||||||
<ha-card header="Configuration">
|
<ha-card header="Configuration">
|
||||||
@@ -54,7 +52,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
@value-changed=${this._configChanged}
|
@value-changed=${this._configChanged}
|
||||||
></ha-yaml-editor>
|
></ha-yaml-editor>
|
||||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||||
${valid ? "" : html` <div class="errors">Invalid YAML</div> `}
|
${this._valid ? "" : html` <div class="errors">Invalid YAML</div> `}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||||
@@ -62,7 +60,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
<ha-progress-button
|
<ha-progress-button
|
||||||
@click=${this._saveTapped}
|
@click=${this._saveTapped}
|
||||||
.disabled=${!this._configHasChanged || !valid}
|
.disabled=${!this._configHasChanged || !this._valid}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
@@ -78,9 +76,9 @@ class HassioAddonConfig extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configChanged(): void {
|
private _configChanged(ev): void {
|
||||||
this._configHasChanged = true;
|
this._configHasChanged = true;
|
||||||
this.requestUpdate();
|
this._valid = ev.detail.isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||||
|
@@ -135,7 +135,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this._computeUpdateAvailable
|
${this.addon.update_available
|
||||||
? html`
|
? html`
|
||||||
<ha-card header="Update available! 🎉">
|
<ha-card header="Update available! 🎉">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -178,7 +178,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
${!this.addon.protected
|
${!this.addon.protected
|
||||||
? html`
|
? html`
|
||||||
<ha-card class="warning">
|
<ha-card class="warning">
|
||||||
<div class="card-header">Warning: Protection mode is disabled!</div>
|
<h1 class="card-header">Warning: Protection mode is disabled!</h1>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
|
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
|
||||||
</div>
|
</div>
|
||||||
@@ -609,15 +609,6 @@ class HassioAddonInfo extends LitElement {
|
|||||||
return this.addon?.state === "started";
|
return this.addon?.state === "started";
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _computeUpdateAvailable(): boolean | "" {
|
|
||||||
return (
|
|
||||||
this.addon &&
|
|
||||||
!this.addon.detached &&
|
|
||||||
this.addon.version &&
|
|
||||||
this.addon.version !== this.addon.version_latest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _pathWebui(): string | null {
|
private get _pathWebui(): string | null {
|
||||||
return (
|
return (
|
||||||
this.addon.webui &&
|
this.addon.webui &&
|
||||||
|
@@ -50,7 +50,7 @@ class HassioCardContent extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class=${this.iconClass}
|
class=${this.iconClass!}
|
||||||
.path=${this.icon}
|
.path=${this.icon}
|
||||||
.title=${this.iconTitle}
|
.title=${this.iconTitle}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "../../../src/components/ha-file-upload";
|
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import { mdiFolderUpload } from "@mdi/js";
|
import { mdiFolderUpload } from "@mdi/js";
|
||||||
import "@polymer/iron-input/iron-input";
|
import "@polymer/iron-input/iron-input";
|
||||||
@@ -12,13 +11,15 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/ha-circular-progress";
|
import "../../../src/components/ha-circular-progress";
|
||||||
|
import "../../../src/components/ha-file-upload";
|
||||||
import "../../../src/components/ha-svg-icon";
|
import "../../../src/components/ha-svg-icon";
|
||||||
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import {
|
import {
|
||||||
HassioSnapshot,
|
HassioSnapshot,
|
||||||
uploadSnapshot,
|
uploadSnapshot,
|
||||||
} from "../../../src/data/hassio/snapshot";
|
} from "../../../src/data/hassio/snapshot";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
|
||||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@@ -65,7 +66,7 @@ export class HassioUploadSnapshot extends LitElement {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: "Upload failed",
|
title: "Upload failed",
|
||||||
text: err.toString(),
|
text: extractApiErrorMessage(err),
|
||||||
confirmText: "ok",
|
confirmText: "ok",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
@@ -52,22 +52,21 @@ class HassioAddons extends LitElement {
|
|||||||
.title=${addon.name}
|
.title=${addon.name}
|
||||||
.description=${addon.description}
|
.description=${addon.description}
|
||||||
available
|
available
|
||||||
.showTopbar=${addon.installed !== addon.version}
|
.showTopbar=${addon.update_available}
|
||||||
topbarClass="update"
|
topbarClass="update"
|
||||||
.icon=${addon.installed !== addon.version
|
.icon=${addon.update_available!
|
||||||
? mdiArrowUpBoldCircle
|
? mdiArrowUpBoldCircle
|
||||||
: mdiPuzzle}
|
: mdiPuzzle}
|
||||||
.iconTitle=${addon.state !== "started"
|
.iconTitle=${addon.state !== "started"
|
||||||
? "Add-on is stopped"
|
? "Add-on is stopped"
|
||||||
: addon.installed !== addon.version
|
: addon.update_available!
|
||||||
? "New version available"
|
? "New version available"
|
||||||
: "Add-on is running"}
|
: "Add-on is running"}
|
||||||
.iconClass=${addon.installed &&
|
.iconClass=${addon.update_available
|
||||||
addon.installed !== addon.version
|
|
||||||
? addon.state === "started"
|
? addon.state === "started"
|
||||||
? "update"
|
? "update"
|
||||||
: "update stopped"
|
: "update stopped"
|
||||||
: addon.installed && addon.state === "started"
|
: addon.state === "started"
|
||||||
? "running"
|
? "running"
|
||||||
: "stopped"}
|
: "stopped"}
|
||||||
.iconImage=${atLeastVersion(
|
.iconImage=${atLeastVersion(
|
||||||
|
@@ -5,11 +5,11 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
internalProperty,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/components/ha-svg-icon";
|
import "../../../src/components/ha-svg-icon";
|
||||||
@@ -35,29 +35,30 @@ import { hassioStyle } from "../resources/hassio-style";
|
|||||||
export class HassioUpdate extends LitElement {
|
export class HassioUpdate extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
@property({ attribute: false }) public hassInfo?: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
@property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo;
|
@property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo;
|
||||||
|
|
||||||
@property() public supervisorInfo: HassioSupervisorInfo;
|
@property({ attribute: false }) public supervisorInfo?: HassioSupervisorInfo;
|
||||||
|
|
||||||
@internalProperty() private _error?: string;
|
private _pendingUpdates = memoizeOne(
|
||||||
|
(
|
||||||
|
core?: HassioHomeAssistantInfo,
|
||||||
|
supervisor?: HassioSupervisorInfo,
|
||||||
|
os?: HassioHassOSInfo
|
||||||
|
): number => {
|
||||||
|
return [core, supervisor, os].filter(
|
||||||
|
(value) => !!value && value?.update_available
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const updatesAvailable: number = [
|
const updatesAvailable = this._pendingUpdates(
|
||||||
this.hassInfo,
|
this.hassInfo,
|
||||||
this.supervisorInfo,
|
this.supervisorInfo,
|
||||||
this.hassOsInfo,
|
this.hassOsInfo
|
||||||
].filter((value) => {
|
);
|
||||||
return (
|
|
||||||
!!value &&
|
|
||||||
(value.version_latest
|
|
||||||
? value.version !== value.version_latest
|
|
||||||
: value.version_latest
|
|
||||||
? value.version !== value.version_latest
|
|
||||||
: false)
|
|
||||||
);
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
if (!updatesAvailable) {
|
if (!updatesAvailable) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -65,9 +66,6 @@ export class HassioUpdate extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._error
|
|
||||||
? html` <div class="error">Error: ${this._error}</div> `
|
|
||||||
: ""}
|
|
||||||
<h1>
|
<h1>
|
||||||
${updatesAvailable > 1
|
${updatesAvailable > 1
|
||||||
? "Updates Available 🎉"
|
? "Updates Available 🎉"
|
||||||
@@ -76,26 +74,24 @@ export class HassioUpdate extends LitElement {
|
|||||||
<div class="card-group">
|
<div class="card-group">
|
||||||
${this._renderUpdateCard(
|
${this._renderUpdateCard(
|
||||||
"Home Assistant Core",
|
"Home Assistant Core",
|
||||||
this.hassInfo.version,
|
this.hassInfo!,
|
||||||
this.hassInfo.version_latest,
|
|
||||||
"hassio/homeassistant/update",
|
"hassio/homeassistant/update",
|
||||||
`https://${
|
`https://${
|
||||||
this.hassInfo.version_latest.includes("b") ? "rc" : "www"
|
this.hassInfo?.version_latest.includes("b") ? "rc" : "www"
|
||||||
}.home-assistant.io/latest-release-notes/`,
|
}.home-assistant.io/latest-release-notes/`
|
||||||
mdiHomeAssistant
|
|
||||||
)}
|
)}
|
||||||
${this._renderUpdateCard(
|
${this._renderUpdateCard(
|
||||||
"Supervisor",
|
"Supervisor",
|
||||||
this.supervisorInfo.version,
|
this.supervisorInfo!,
|
||||||
this.supervisorInfo.version_latest,
|
|
||||||
"hassio/supervisor/update",
|
"hassio/supervisor/update",
|
||||||
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisorInfo.version_latest}`
|
`https://github.com//home-assistant/hassio/releases/tag/${
|
||||||
|
this.supervisorInfo!.version_latest
|
||||||
|
}`
|
||||||
)}
|
)}
|
||||||
${this.hassOsInfo
|
${this.hassOsInfo
|
||||||
? this._renderUpdateCard(
|
? this._renderUpdateCard(
|
||||||
"Operating System",
|
"Operating System",
|
||||||
this.hassOsInfo.version,
|
this.hassOsInfo,
|
||||||
this.hassOsInfo.version_latest,
|
|
||||||
"hassio/os/update",
|
"hassio/os/update",
|
||||||
`https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
|
`https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
|
||||||
)
|
)
|
||||||
@@ -107,28 +103,22 @@ export class HassioUpdate extends LitElement {
|
|||||||
|
|
||||||
private _renderUpdateCard(
|
private _renderUpdateCard(
|
||||||
name: string,
|
name: string,
|
||||||
curVersion: string,
|
object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo,
|
||||||
lastVersion: string,
|
|
||||||
apiPath: string,
|
apiPath: string,
|
||||||
releaseNotesUrl: string,
|
releaseNotesUrl: string
|
||||||
icon?: string
|
|
||||||
): TemplateResult {
|
): TemplateResult {
|
||||||
if (!lastVersion || lastVersion === curVersion) {
|
if (!object.update_available) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
${icon
|
<div class="icon">
|
||||||
? html`
|
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||||
<div class="icon">
|
</div>
|
||||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
<div class="update-heading">${name} ${object.version_latest}</div>
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<div class="update-heading">${name} ${lastVersion}</div>
|
|
||||||
<div class="warning">
|
<div class="warning">
|
||||||
You are currently running version ${curVersion}
|
You are currently running version ${object.version}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@@ -138,7 +128,7 @@ export class HassioUpdate extends LitElement {
|
|||||||
<ha-progress-button
|
<ha-progress-button
|
||||||
.apiPath=${apiPath}
|
.apiPath=${apiPath}
|
||||||
.name=${name}
|
.name=${name}
|
||||||
.version=${lastVersion}
|
.version=${object.version_latest}
|
||||||
@click=${this._confirmUpdate}
|
@click=${this._confirmUpdate}
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
|
245
hassio/src/dialogs/registries/dialog-hassio-registries.ts
Normal file
245
hassio/src/dialogs/registries/dialog-hassio-registries.ts
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import { mdiDelete } from "@mdi/js";
|
||||||
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
internalProperty,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../../../../src/components/ha-circular-progress";
|
||||||
|
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||||
|
import "../../../../src/components/ha-svg-icon";
|
||||||
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
|
import {
|
||||||
|
addHassioDockerRegistry,
|
||||||
|
fetchHassioDockerRegistries,
|
||||||
|
removeHassioDockerRegistry,
|
||||||
|
} from "../../../../src/data/hassio/docker";
|
||||||
|
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
|
|
||||||
|
@customElement("dialog-hassio-registries")
|
||||||
|
class HassioRegistriesDialog extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) private _registries?: {
|
||||||
|
registry: string;
|
||||||
|
username: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
@internalProperty() private _registry?: string;
|
||||||
|
|
||||||
|
@internalProperty() private _username?: string;
|
||||||
|
|
||||||
|
@internalProperty() private _password?: string;
|
||||||
|
|
||||||
|
@internalProperty() private _opened = false;
|
||||||
|
|
||||||
|
@internalProperty() private _addingRegistry = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
.open=${this._opened}
|
||||||
|
@closing=${this.closeDialog}
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this._addingRegistry
|
||||||
|
? "Add New Docker Registry"
|
||||||
|
: "Manage Docker Registries"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="form">
|
||||||
|
${this._addingRegistry
|
||||||
|
? html`
|
||||||
|
<paper-input
|
||||||
|
@value-changed=${this._inputChanged}
|
||||||
|
class="flex-auto"
|
||||||
|
name="registry"
|
||||||
|
label="Registry"
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
></paper-input>
|
||||||
|
<paper-input
|
||||||
|
@value-changed=${this._inputChanged}
|
||||||
|
class="flex-auto"
|
||||||
|
name="username"
|
||||||
|
label="Username"
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
></paper-input>
|
||||||
|
<paper-input
|
||||||
|
@value-changed=${this._inputChanged}
|
||||||
|
class="flex-auto"
|
||||||
|
name="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<mwc-button
|
||||||
|
?disabled=${Boolean(
|
||||||
|
!this._registry || !this._username || !this._password
|
||||||
|
)}
|
||||||
|
@click=${this._addNewRegistry}
|
||||||
|
>
|
||||||
|
Add registry
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: html`${this._registries?.length
|
||||||
|
? this._registries.map((entry) => {
|
||||||
|
return html`
|
||||||
|
<mwc-list-item class="option" hasMeta twoline>
|
||||||
|
<span>${entry.registry}</span>
|
||||||
|
<span slot="secondary"
|
||||||
|
>Username: ${entry.username}</span
|
||||||
|
>
|
||||||
|
<mwc-icon-button
|
||||||
|
.entry=${entry}
|
||||||
|
title="Remove"
|
||||||
|
slot="meta"
|
||||||
|
@click=${this._removeRegistry}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</mwc-list-item>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
: html`
|
||||||
|
<mwc-list-item>
|
||||||
|
<span>No registries configured</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
`}
|
||||||
|
<mwc-button @click=${this._addRegistry}>
|
||||||
|
Add new registry
|
||||||
|
</mwc-button> `}
|
||||||
|
</div>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _inputChanged(ev: Event) {
|
||||||
|
const target = ev.currentTarget as PaperInputElement;
|
||||||
|
this[`_${target.name}`] = target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async showDialog(_dialogParams: any): Promise<void> {
|
||||||
|
this._opened = true;
|
||||||
|
await this._loadRegistries();
|
||||||
|
await this.updateComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._addingRegistry = false;
|
||||||
|
this._opened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public focus(): void {
|
||||||
|
this.updateComplete.then(() =>
|
||||||
|
(this.shadowRoot?.querySelector(
|
||||||
|
"[dialogInitialFocus]"
|
||||||
|
) as HTMLElement)?.focus()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadRegistries(): Promise<void> {
|
||||||
|
const registries = await fetchHassioDockerRegistries(this.hass);
|
||||||
|
this._registries = Object.keys(registries!.registries).map((key) => ({
|
||||||
|
registry: key,
|
||||||
|
username: registries.registries[key].username,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addRegistry(): void {
|
||||||
|
this._addingRegistry = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _addNewRegistry(): Promise<void> {
|
||||||
|
const data = {};
|
||||||
|
data[this._registry!] = {
|
||||||
|
username: this._username,
|
||||||
|
password: this._password,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await addHassioDockerRegistry(this.hass, data);
|
||||||
|
await this._loadRegistries();
|
||||||
|
this._addingRegistry = false;
|
||||||
|
} catch (err) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: "Failed to add registry",
|
||||||
|
text: extractApiErrorMessage(err),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeRegistry(ev: Event): Promise<void> {
|
||||||
|
const entry = (ev.currentTarget as any).entry;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeHassioDockerRegistry(this.hass, entry.registry);
|
||||||
|
await this._loadRegistries();
|
||||||
|
} catch (err) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: "Failed to remove registry",
|
||||||
|
text: extractApiErrorMessage(err),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog.button-left {
|
||||||
|
--justify-action-buttons: flex-start;
|
||||||
|
}
|
||||||
|
paper-icon-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.form {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
.option {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
mwc-button {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
mwc-icon-button {
|
||||||
|
color: var(--error-color);
|
||||||
|
margin: -10px;
|
||||||
|
}
|
||||||
|
mwc-list-item {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
mwc-list-item span[slot="secondary"] {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
ha-paper-dropdown-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-hassio-registries": HassioRegistriesDialog;
|
||||||
|
}
|
||||||
|
}
|
13
hassio/src/dialogs/registries/show-dialog-registries.ts
Normal file
13
hassio/src/dialogs/registries/show-dialog-registries.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
|
import "./dialog-hassio-registries";
|
||||||
|
|
||||||
|
export const showRegistriesDialog = (element: HTMLElement): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-hassio-registries",
|
||||||
|
dialogImport: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "dialog-hassio-registries" */ "./dialog-hassio-registries"
|
||||||
|
),
|
||||||
|
dialogParams: {},
|
||||||
|
});
|
||||||
|
};
|
@@ -1,6 +1,7 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
|
import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
|
||||||
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
|
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
|
@@ -25,13 +25,13 @@ class HassioPanelRouter extends HassRouterPage {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
|
@property({ attribute: false }) public supervisorInfo?: HassioSupervisorInfo;
|
||||||
|
|
||||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||||
|
|
||||||
@property({ attribute: false }) public hostInfo: HassioHostInfo;
|
@property({ attribute: false }) public hostInfo?: HassioHostInfo;
|
||||||
|
|
||||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
@property({ attribute: false }) public hassInfo?: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||||
|
|
||||||
|
@@ -66,15 +66,15 @@ class HassioRouter extends HassRouterPage {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@internalProperty() private _supervisorInfo: HassioSupervisorInfo;
|
@internalProperty() private _supervisorInfo?: HassioSupervisorInfo;
|
||||||
|
|
||||||
@internalProperty() private _hostInfo: HassioHostInfo;
|
@internalProperty() private _hostInfo?: HassioHostInfo;
|
||||||
|
|
||||||
@internalProperty() private _hassioInfo?: HassioInfo;
|
@internalProperty() private _hassioInfo?: HassioInfo;
|
||||||
|
|
||||||
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
|
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
|
||||||
|
|
||||||
@internalProperty() private _hassInfo: HassioHomeAssistantInfo;
|
@internalProperty() private _hassInfo?: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
@@ -13,7 +13,10 @@ import {
|
|||||||
fetchHassioAddonInfo,
|
fetchHassioAddonInfo,
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
} from "../../../src/data/hassio/addon";
|
} from "../../../src/data/hassio/addon";
|
||||||
import { createHassioSession } from "../../../src/data/hassio/supervisor";
|
import {
|
||||||
|
createHassioSession,
|
||||||
|
validateHassioSession,
|
||||||
|
} from "../../../src/data/hassio/ingress";
|
||||||
import "../../../src/layouts/hass-loading-screen";
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
import "../../../src/layouts/hass-subpage";
|
import "../../../src/layouts/hass-subpage";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
@@ -35,6 +38,17 @@ class HassioIngressView extends LitElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public narrow = false;
|
public narrow = false;
|
||||||
|
|
||||||
|
private _sessionKeepAlive?: number;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
if (this._sessionKeepAlive) {
|
||||||
|
clearInterval(this._sessionKeepAlive);
|
||||||
|
this._sessionKeepAlive = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._addon) {
|
if (!this._addon) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
@@ -44,6 +58,7 @@ class HassioIngressView extends LitElement {
|
|||||||
|
|
||||||
if (!this.ingressPanel) {
|
if (!this.ingressPanel) {
|
||||||
return html`<hass-subpage
|
return html`<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
.header=${this._addon.name}
|
.header=${this._addon.name}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
>
|
>
|
||||||
@@ -83,10 +98,7 @@ class HassioIngressView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchData(addonSlug: string) {
|
private async _fetchData(addonSlug: string) {
|
||||||
const createSessionPromise = createHassioSession(this.hass).then(
|
const createSessionPromise = createHassioSession(this.hass);
|
||||||
() => true,
|
|
||||||
() => false
|
|
||||||
);
|
|
||||||
|
|
||||||
let addon;
|
let addon;
|
||||||
|
|
||||||
@@ -119,7 +131,11 @@ class HassioIngressView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await createSessionPromise)) {
|
let session;
|
||||||
|
|
||||||
|
try {
|
||||||
|
session = await createSessionPromise;
|
||||||
|
} catch (err) {
|
||||||
await showAlertDialog(this, {
|
await showAlertDialog(this, {
|
||||||
text: "Unable to create an Ingress session",
|
text: "Unable to create an Ingress session",
|
||||||
title: addon.name,
|
title: addon.name,
|
||||||
@@ -128,6 +144,17 @@ class HassioIngressView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._sessionKeepAlive) {
|
||||||
|
clearInterval(this._sessionKeepAlive);
|
||||||
|
}
|
||||||
|
this._sessionKeepAlive = window.setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await validateHassioSession(this.hass, session);
|
||||||
|
} catch (err) {
|
||||||
|
session = await createHassioSession(this.hass);
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
this._addon = addon;
|
this._addon = addon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -108,8 +108,8 @@ class HassioHostInfo extends LitElement {
|
|||||||
<span slot="description">
|
<span slot="description">
|
||||||
${this.hostInfo.operating_system}
|
${this.hostInfo.operating_system}
|
||||||
</span>
|
</span>
|
||||||
${this.hostInfo.version !== this.hostInfo.version_latest &&
|
${this.hostInfo.features.includes("hassos") &&
|
||||||
this.hostInfo.features.includes("hassos")
|
this.hassOsInfo.update_available
|
||||||
? html`
|
? html`
|
||||||
<ha-progress-button
|
<ha-progress-button
|
||||||
title="Update the host OS"
|
title="Update the host OS"
|
||||||
|
@@ -7,18 +7,21 @@ import {
|
|||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/components/ha-settings-row";
|
import "../../../src/components/ha-settings-row";
|
||||||
import "../../../src/components/ha-switch";
|
import "../../../src/components/ha-switch";
|
||||||
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
|
import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
|
||||||
|
import { fetchHassioResolution } from "../../../src/data/hassio/resolution";
|
||||||
import {
|
import {
|
||||||
|
fetchHassioSupervisorInfo,
|
||||||
HassioSupervisorInfo as HassioSupervisorInfoType,
|
HassioSupervisorInfo as HassioSupervisorInfoType,
|
||||||
reloadSupervisor,
|
reloadSupervisor,
|
||||||
setSupervisorOption,
|
setSupervisorOption,
|
||||||
SupervisorOptions,
|
SupervisorOptions,
|
||||||
updateSupervisor,
|
updateSupervisor,
|
||||||
fetchHassioSupervisorInfo,
|
|
||||||
} from "../../../src/data/hassio/supervisor";
|
} from "../../../src/data/hassio/supervisor";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
@@ -26,14 +29,42 @@ import {
|
|||||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import { documentationUrl } from "../../../src/util/documentation-url";
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
|
||||||
|
const ISSUES = {
|
||||||
|
container: {
|
||||||
|
title: "Containers known to cause issues",
|
||||||
|
url: "/more-info/unsupported/container",
|
||||||
|
},
|
||||||
|
dbus: { title: "DBUS", url: "/more-info/unsupported/dbus" },
|
||||||
|
docker_configuration: {
|
||||||
|
title: "Docker Configuration",
|
||||||
|
url: "/more-info/unsupported/docker_configuration",
|
||||||
|
},
|
||||||
|
docker_version: {
|
||||||
|
title: "Docker Version",
|
||||||
|
url: "/more-info/unsupported/docker_version",
|
||||||
|
},
|
||||||
|
lxc: { title: "LXC", url: "/more-info/unsupported/lxc" },
|
||||||
|
network_manager: {
|
||||||
|
title: "Network Manager",
|
||||||
|
url: "/more-info/unsupported/network_manager",
|
||||||
|
},
|
||||||
|
os: { title: "Operating System", url: "/more-info/unsupported/os" },
|
||||||
|
privileged: {
|
||||||
|
title: "Supervisor is not privileged",
|
||||||
|
url: "/more-info/unsupported/privileged",
|
||||||
|
},
|
||||||
|
systemd: { title: "Systemd", url: "/more-info/unsupported/systemd" },
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hassio-supervisor-info")
|
@customElement("hassio-supervisor-info")
|
||||||
class HassioSupervisorInfo extends LitElement {
|
class HassioSupervisorInfo extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public supervisorInfo!: HassioSupervisorInfoType;
|
@property({ attribute: false })
|
||||||
|
public supervisorInfo!: HassioSupervisorInfoType;
|
||||||
|
|
||||||
@property() public hostInfo!: HassioHostInfoType;
|
@property() public hostInfo!: HassioHostInfoType;
|
||||||
|
|
||||||
@@ -56,7 +87,7 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
<span slot="description">
|
<span slot="description">
|
||||||
${this.supervisorInfo.version_latest}
|
${this.supervisorInfo.version_latest}
|
||||||
</span>
|
</span>
|
||||||
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
|
${this.supervisorInfo.update_available
|
||||||
? html`
|
? html`
|
||||||
<ha-progress-button
|
<ha-progress-button
|
||||||
title="Update the supervisor"
|
title="Update the supervisor"
|
||||||
@@ -118,18 +149,13 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
</ha-settings-row>`
|
</ha-settings-row>`
|
||||||
: html`<div class="error">
|
: html`<div class="error">
|
||||||
You are running an unsupported installation.
|
You are running an unsupported installation.
|
||||||
<a
|
<button
|
||||||
href="https://github.com/home-assistant/architecture/blob/master/adr/${this.hostInfo.features.includes(
|
class="link"
|
||||||
"hassos"
|
|
||||||
)
|
|
||||||
? "0015-home-assistant-os.md"
|
|
||||||
: "0014-home-assistant-supervised.md"}"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
title="Learn more about how you can make your system compliant"
|
title="Learn more about how you can make your system compliant"
|
||||||
|
@click=${this._unsupportedDialog}
|
||||||
>
|
>
|
||||||
Learn More
|
Learn more
|
||||||
</a>
|
</button>
|
||||||
</div>`}
|
</div>`}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@@ -181,7 +207,7 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
};
|
};
|
||||||
await setSupervisorOption(this.hass, data);
|
await setSupervisorOption(this.hass, data);
|
||||||
await reloadSupervisor(this.hass);
|
await reloadSupervisor(this.hass);
|
||||||
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
fireEvent(this, "hass-api-called", { success: true, response: null });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: "Failed to set supervisor option",
|
title: "Failed to set supervisor option",
|
||||||
@@ -249,6 +275,32 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _unsupportedDialog(): Promise<void> {
|
||||||
|
const resolution = await fetchHassioResolution(this.hass);
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
title: "You are running an unsupported installation",
|
||||||
|
text: html`Below is a list of issues found with your installation, click
|
||||||
|
on the links to learn how you can resolve the issues. <br /><br />
|
||||||
|
<ul>
|
||||||
|
${resolution.unsupported.map(
|
||||||
|
(issue) => html`
|
||||||
|
<li>
|
||||||
|
${ISSUES[issue]
|
||||||
|
? html`<a
|
||||||
|
href="${documentationUrl(this.hass, ISSUES[issue].url)}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
${ISSUES[issue].title}
|
||||||
|
</a>`
|
||||||
|
: issue}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ul>`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async _toggleDiagnostics(): Promise<void> {
|
private async _toggleDiagnostics(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const data: SupervisorOptions = {
|
const data: SupervisorOptions = {
|
||||||
|
@@ -21,11 +21,11 @@ import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common";
|
|||||||
import { HassioHostInfo } from "../../../src/data/hassio/host";
|
import { HassioHostInfo } from "../../../src/data/hassio/host";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||||
import {
|
import {
|
||||||
getValueInPercentage,
|
getValueInPercentage,
|
||||||
roundWithOneDecimal,
|
roundWithOneDecimal,
|
||||||
} from "../../../src/util/calculate";
|
} from "../../../src/util/calculate";
|
||||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
@customElement("hassio-system-metrics")
|
@customElement("hassio-system-metrics")
|
||||||
@@ -65,9 +65,7 @@ class HassioSystemMetrics extends LitElement {
|
|||||||
{
|
{
|
||||||
description: "Used Space",
|
description: "Used Space",
|
||||||
value: this._getUsedSpace(this.hostInfo),
|
value: this._getUsedSpace(this.hostInfo),
|
||||||
tooltip: `${
|
tooltip: `${this.hostInfo.disk_used} GB/${this.hostInfo.disk_total} GB`,
|
||||||
this.hostInfo.disk_used
|
|
||||||
} GB/${this.hostInfo.disk_total} GB`,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
19
package.json
19
package.json
@@ -22,7 +22,8 @@
|
|||||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-pluralrules": "^1.5.8",
|
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||||
|
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||||
"@fullcalendar/common": "5.1.0",
|
"@fullcalendar/common": "5.1.0",
|
||||||
"@fullcalendar/core": "5.1.0",
|
"@fullcalendar/core": "5.1.0",
|
||||||
"@fullcalendar/daygrid": "5.1.0",
|
"@fullcalendar/daygrid": "5.1.0",
|
||||||
@@ -77,7 +78,7 @@
|
|||||||
"@polymer/paper-toast": "^3.0.1",
|
"@polymer/paper-toast": "^3.0.1",
|
||||||
"@polymer/paper-tooltip": "^3.0.1",
|
"@polymer/paper-tooltip": "^3.0.1",
|
||||||
"@polymer/polymer": "3.1.0",
|
"@polymer/polymer": "3.1.0",
|
||||||
"@thomasloven/round-slider": "0.5.0",
|
"@thomasloven/round-slider": "0.5.2",
|
||||||
"@types/chromecast-caf-sender": "^1.0.3",
|
"@types/chromecast-caf-sender": "^1.0.3",
|
||||||
"@types/sortablejs": "^1.10.6",
|
"@types/sortablejs": "^1.10.6",
|
||||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||||
@@ -89,7 +90,6 @@
|
|||||||
"codemirror": "^5.49.0",
|
"codemirror": "^5.49.0",
|
||||||
"comlink": "^4.3.0",
|
"comlink": "^4.3.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"cpx": "^1.5.0",
|
|
||||||
"cropperjs": "^1.5.7",
|
"cropperjs": "^1.5.7",
|
||||||
"deep-clone-simple": "^1.1.1",
|
"deep-clone-simple": "^1.1.1",
|
||||||
"deep-freeze": "^0.0.1",
|
"deep-freeze": "^0.0.1",
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
"marked": "^1.1.1",
|
"marked": "^1.1.1",
|
||||||
"mdn-polyfills": "^5.16.0",
|
"mdn-polyfills": "^5.16.0",
|
||||||
"memoize-one": "^5.0.2",
|
"memoize-one": "^5.0.2",
|
||||||
"node-vibrant": "^3.1.5",
|
"node-vibrant": "^3.1.6",
|
||||||
"proxy-polyfill": "^0.3.1",
|
"proxy-polyfill": "^0.3.1",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
@@ -118,6 +118,7 @@
|
|||||||
"roboto-fontface": "^0.10.0",
|
"roboto-fontface": "^0.10.0",
|
||||||
"sortablejs": "^1.10.2",
|
"sortablejs": "^1.10.2",
|
||||||
"superstruct": "^0.10.12",
|
"superstruct": "^0.10.12",
|
||||||
|
"tinykeys": "^1.1.1",
|
||||||
"unfetch": "^4.1.0",
|
"unfetch": "^4.1.0",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue2-daterange-picker": "^0.5.1",
|
"vue2-daterange-picker": "^0.5.1",
|
||||||
@@ -160,6 +161,7 @@
|
|||||||
"@typescript-eslint/parser": "^4.4.0",
|
"@typescript-eslint/parser": "^4.4.0",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
|
"cpx": "^1.5.0",
|
||||||
"del": "^4.0.0",
|
"del": "^4.0.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||||
@@ -175,7 +177,6 @@
|
|||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-foreach": "^0.1.0",
|
"gulp-foreach": "^0.1.0",
|
||||||
"gulp-json-transform": "^0.4.6",
|
"gulp-json-transform": "^0.4.6",
|
||||||
"gulp-jsonminify": "^1.1.0",
|
|
||||||
"gulp-merge-json": "^1.3.1",
|
"gulp-merge-json": "^1.3.1",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"gulp-zopfli-green": "^3.0.1",
|
"gulp-zopfli-green": "^3.0.1",
|
||||||
@@ -202,15 +203,15 @@
|
|||||||
"sinon": "^7.3.1",
|
"sinon": "^7.3.1",
|
||||||
"source-map-url": "^0.4.0",
|
"source-map-url": "^0.4.0",
|
||||||
"systemjs": "^6.3.2",
|
"systemjs": "^6.3.2",
|
||||||
"terser-webpack-plugin": "^3.0.6",
|
"terser-webpack-plugin": "^5.0.0",
|
||||||
"ts-lit-plugin": "^1.2.1",
|
"ts-lit-plugin": "^1.2.1",
|
||||||
"ts-mocha": "^7.0.0",
|
"ts-mocha": "^7.0.0",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.0.3",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"vinyl-source-stream": "^2.0.0",
|
"vinyl-source-stream": "^2.0.0",
|
||||||
"webpack": "5.0.0-rc.3",
|
"webpack": "5.1.3",
|
||||||
"webpack-cli": "4.0.0-rc.0",
|
"webpack-cli": "4.1.0",
|
||||||
"webpack-dev-server": "^3.10.3",
|
"webpack-dev-server": "^3.11.0",
|
||||||
"webpack-manifest-plugin": "3.0.0-rc.0",
|
"webpack-manifest-plugin": "3.0.0-rc.0",
|
||||||
"workbox-build": "^5.1.3"
|
"workbox-build": "^5.1.3"
|
||||||
},
|
},
|
||||||
|
@@ -13,7 +13,6 @@
|
|||||||
"src/panels/iframe/ha-panel-iframe.js",
|
"src/panels/iframe/ha-panel-iframe.js",
|
||||||
"src/panels/logbook/ha-panel-logbook.js",
|
"src/panels/logbook/ha-panel-logbook.js",
|
||||||
"src/panels/map/ha-panel-map.js",
|
"src/panels/map/ha-panel-map.js",
|
||||||
"src/panels/shopping-list/ha-panel-shopping-list.js",
|
|
||||||
"src/panels/mailbox/ha-panel-mailbox.js",
|
"src/panels/mailbox/ha-panel-mailbox.js",
|
||||||
"hassio/src/entrypoint.js"
|
"hassio/src/entrypoint.js"
|
||||||
],
|
],
|
||||||
|
BIN
public/static/images/conference.png
Normal file
BIN
public/static/images/conference.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
55
script/core
Executable file
55
script/core
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Helper to start Home Assistant Core inside the devcontainer
|
||||||
|
|
||||||
|
# Stop on errors
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "${DEVCONTAINER}" ]; then
|
||||||
|
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "${CODESPACES}" ]; then
|
||||||
|
WORKSPACE="/root/workspace/frontend"
|
||||||
|
else
|
||||||
|
WORKSPACE="/workspaces/frontend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z $(which hass) ]; then
|
||||||
|
echo "Installing Home Asstant core from dev."
|
||||||
|
python3 -m pip install --upgrade \
|
||||||
|
colorlog \
|
||||||
|
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "${WORKSPACE}/config" ]; then
|
||||||
|
echo "Creating default configuration."
|
||||||
|
mkdir -p "${WORKSPACE}/config";
|
||||||
|
hass --script ensure_config -c config
|
||||||
|
echo "demo:
|
||||||
|
|
||||||
|
logger:
|
||||||
|
default: info
|
||||||
|
logs:
|
||||||
|
homeassistant.components.frontend: debug
|
||||||
|
" >> "${WORKSPACE}/config/configuration.yaml"
|
||||||
|
|
||||||
|
if [ ! -z "${HASSIO}" ]; then
|
||||||
|
echo "
|
||||||
|
# frontend:
|
||||||
|
# development_repo: ${WORKSPACE}
|
||||||
|
|
||||||
|
hassio:
|
||||||
|
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||||
|
else
|
||||||
|
echo "
|
||||||
|
frontend:
|
||||||
|
development_repo: ${WORKSPACE}
|
||||||
|
|
||||||
|
# hassio:
|
||||||
|
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
hass -c "${WORKSPACE}/config"
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20201001.0",
|
version="20201111.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@@ -1,10 +1,4 @@
|
|||||||
const expand_hex = (hex: string): string => {
|
import { expandHex } from "./hex";
|
||||||
let result = "";
|
|
||||||
for (const val of hex) {
|
|
||||||
result += val + val;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const rgb_hex = (component: number): string => {
|
const rgb_hex = (component: number): string => {
|
||||||
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
|
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
|
||||||
@@ -14,10 +8,7 @@ const rgb_hex = (component: number): string => {
|
|||||||
// Conversion between HEX and RGB
|
// Conversion between HEX and RGB
|
||||||
|
|
||||||
export const hex2rgb = (hex: string): [number, number, number] => {
|
export const hex2rgb = (hex: string): [number, number, number] => {
|
||||||
hex = hex.replace("#", "");
|
hex = expandHex(hex);
|
||||||
if (hex.length === 3 || hex.length === 4) {
|
|
||||||
hex = expand_hex(hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
parseInt(hex.substring(0, 2), 16),
|
parseInt(hex.substring(0, 2), 16),
|
||||||
|
24
src/common/color/hex.ts
Normal file
24
src/common/color/hex.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export const expandHex = (hex: string): string => {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
if (hex.length === 6) return hex;
|
||||||
|
let result = "";
|
||||||
|
for (const val of hex) {
|
||||||
|
result += val + val;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Blend 2 hex colors: c1 is placed over c2, blend is c1's opacity.
|
||||||
|
export const hexBlend = (c1: string, c2: string, blend = 50): string => {
|
||||||
|
let color = "";
|
||||||
|
c1 = expandHex(c1);
|
||||||
|
c2 = expandHex(c2);
|
||||||
|
for (let i = 0; i <= 5; i += 2) {
|
||||||
|
const h1 = parseInt(c1.substr(i, 2), 16);
|
||||||
|
const h2 = parseInt(c2.substr(i, 2), 16);
|
||||||
|
let hex = Math.floor(h2 + (h1 - h2) * (blend / 100)).toString(16);
|
||||||
|
while (hex.length < 2) hex = "0" + hex;
|
||||||
|
color += hex;
|
||||||
|
}
|
||||||
|
return `#${color}`;
|
||||||
|
};
|
18
src/common/config/can_show_page.ts
Normal file
18
src/common/config/can_show_page.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { isComponentLoaded } from "./is_component_loaded";
|
||||||
|
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) => {
|
||||||
|
return (
|
||||||
|
(isCore(page) || isLoadedIntegration(hass, page)) &&
|
||||||
|
!hideAdvancedPage(hass, page)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
|
!page.component || isComponentLoaded(hass, page.component);
|
||||||
|
const isCore = (page: PageNavigation) => page.core;
|
||||||
|
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
||||||
|
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
|
||||||
|
const hideAdvancedPage = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
|
isAdvancedPage(page) && !userWantsAdvanced(hass);
|
@@ -7,6 +7,7 @@ import {
|
|||||||
rgb2hex,
|
rgb2hex,
|
||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../color/convert-color";
|
} from "../color/convert-color";
|
||||||
|
import { hexBlend } from "../color/hex";
|
||||||
import { labBrighten, labDarken } from "../color/lab";
|
import { labBrighten, labDarken } from "../color/lab";
|
||||||
import { rgbContrast } from "../color/rgb";
|
import { rgbContrast } from "../color/rgb";
|
||||||
|
|
||||||
@@ -37,6 +38,13 @@ export const applyThemesOnElement = (
|
|||||||
if (themeOptions.dark) {
|
if (themeOptions.dark) {
|
||||||
cacheKey = `${cacheKey}__dark`;
|
cacheKey = `${cacheKey}__dark`;
|
||||||
themeRules = darkStyles;
|
themeRules = darkStyles;
|
||||||
|
if (themeOptions.primaryColor) {
|
||||||
|
themeRules["app-header-background-color"] = hexBlend(
|
||||||
|
themeOptions.primaryColor,
|
||||||
|
"#121212",
|
||||||
|
8
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (themeOptions.primaryColor) {
|
if (themeOptions.primaryColor) {
|
||||||
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
|
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
|
||||||
|
@@ -10,10 +10,7 @@ export const dynamicElement = directive(
|
|||||||
|
|
||||||
let element = part.value as HTMLElement | undefined;
|
let element = part.value as HTMLElement | undefined;
|
||||||
|
|
||||||
if (
|
if (tag === element?.localName) {
|
||||||
element !== undefined &&
|
|
||||||
tag.toUpperCase() === (element as HTMLElement).tagName
|
|
||||||
) {
|
|
||||||
if (properties) {
|
if (properties) {
|
||||||
Object.entries(properties).forEach(([key, value]) => {
|
Object.entries(properties).forEach(([key, value]) => {
|
||||||
element![key] = value;
|
element![key] = value;
|
||||||
|
@@ -23,7 +23,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
|||||||
case "problem":
|
case "problem":
|
||||||
case "safety":
|
case "safety":
|
||||||
case "smoke":
|
case "smoke":
|
||||||
return is_off ? "hass:shield-check" : "hass:alert";
|
return is_off ? "hass:check-circle" : "hass:alert-circle";
|
||||||
case "heat":
|
case "heat":
|
||||||
return is_off ? "hass:thermometer" : "hass:fire";
|
return is_off ? "hass:thermometer" : "hass:fire";
|
||||||
case "light":
|
case "light":
|
||||||
|
@@ -5,6 +5,7 @@ import { formatDateTime } from "../datetime/format_date_time";
|
|||||||
import { formatTime } from "../datetime/format_time";
|
import { formatTime } from "../datetime/format_time";
|
||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
|
import { numberFormat } from "../string/number-format";
|
||||||
|
|
||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
@@ -19,7 +20,9 @@ export const computeStateDisplay = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stateObj.attributes.unit_of_measurement) {
|
if (stateObj.attributes.unit_of_measurement) {
|
||||||
return `${compareState} ${stateObj.attributes.unit_of_measurement}`;
|
return `${numberFormat(compareState, language)} ${
|
||||||
|
stateObj.attributes.unit_of_measurement
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
@@ -43,6 +43,7 @@ export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
|||||||
}
|
}
|
||||||
case "blind":
|
case "blind":
|
||||||
case "curtain":
|
case "curtain":
|
||||||
|
case "shade":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
return "hass:arrow-up-box";
|
return "hass:arrow-up-box";
|
||||||
@@ -77,3 +78,25 @@ export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
|||||||
return "hass:window-open";
|
return "hass:window-open";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const computeOpenIcon = (stateObj: HassEntity): string => {
|
||||||
|
switch (stateObj.attributes.device_class) {
|
||||||
|
case "awning":
|
||||||
|
case "door":
|
||||||
|
case "gate":
|
||||||
|
return "hass:arrow-expand-horizontal";
|
||||||
|
default:
|
||||||
|
return "hass:arrow-up";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const computeCloseIcon = (stateObj: HassEntity): string => {
|
||||||
|
switch (stateObj.attributes.device_class) {
|
||||||
|
case "awning":
|
||||||
|
case "door":
|
||||||
|
case "gate":
|
||||||
|
return "hass:arrow-collapse-horizontal";
|
||||||
|
default:
|
||||||
|
return "hass:arrow-down";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -77,6 +77,11 @@ export const domainIcon = (
|
|||||||
return "hass:calendar";
|
return "hass:calendar";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "sun":
|
||||||
|
return stateObj?.state === "above_horizon"
|
||||||
|
? FIXED_DOMAIN_ICONS[domain]
|
||||||
|
: "hass:weather-night";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain in FIXED_DOMAIN_ICONS) {
|
if (domain in FIXED_DOMAIN_ICONS) {
|
||||||
|
@@ -51,18 +51,14 @@ class SearchInput extends LitElement {
|
|||||||
@value-changed=${this._filterInputChanged}
|
@value-changed=${this._filterInputChanged}
|
||||||
.noLabelFloat=${this.noLabelFloat}
|
.noLabelFloat=${this.noLabelFloat}
|
||||||
>
|
>
|
||||||
<ha-svg-icon
|
<slot name="prefix" slot="prefix">
|
||||||
.path=${mdiMagnify}
|
<ha-svg-icon class="prefix" .path=${mdiMagnify}></ha-svg-icon>
|
||||||
slot="prefix"
|
</slot>
|
||||||
class="prefix"
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.filter &&
|
${this.filter &&
|
||||||
html`
|
html`
|
||||||
<mwc-icon-button
|
<mwc-icon-button
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
class="suffix"
|
|
||||||
@click=${this._clearSearch}
|
@click=${this._clearSearch}
|
||||||
alt="Clear"
|
|
||||||
title="Clear"
|
title="Clear"
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
|
244
src/common/string/filter/char-code.ts
Normal file
244
src/common/string/filter/char-code.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// MIT License
|
||||||
|
|
||||||
|
// Copyright (c) 2015 - present Microsoft Corporation
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
|
||||||
|
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
|
||||||
|
*/
|
||||||
|
export enum CharCode {
|
||||||
|
Null = 0,
|
||||||
|
/**
|
||||||
|
* The `\b` character.
|
||||||
|
*/
|
||||||
|
Backspace = 8,
|
||||||
|
/**
|
||||||
|
* The `\t` character.
|
||||||
|
*/
|
||||||
|
Tab = 9,
|
||||||
|
/**
|
||||||
|
* The `\n` character.
|
||||||
|
*/
|
||||||
|
LineFeed = 10,
|
||||||
|
/**
|
||||||
|
* The `\r` character.
|
||||||
|
*/
|
||||||
|
CarriageReturn = 13,
|
||||||
|
Space = 32,
|
||||||
|
/**
|
||||||
|
* The `!` character.
|
||||||
|
*/
|
||||||
|
ExclamationMark = 33,
|
||||||
|
/**
|
||||||
|
* The `"` character.
|
||||||
|
*/
|
||||||
|
DoubleQuote = 34,
|
||||||
|
/**
|
||||||
|
* The `#` character.
|
||||||
|
*/
|
||||||
|
Hash = 35,
|
||||||
|
/**
|
||||||
|
* The `$` character.
|
||||||
|
*/
|
||||||
|
DollarSign = 36,
|
||||||
|
/**
|
||||||
|
* The `%` character.
|
||||||
|
*/
|
||||||
|
PercentSign = 37,
|
||||||
|
/**
|
||||||
|
* The `&` character.
|
||||||
|
*/
|
||||||
|
Ampersand = 38,
|
||||||
|
/**
|
||||||
|
* The `'` character.
|
||||||
|
*/
|
||||||
|
SingleQuote = 39,
|
||||||
|
/**
|
||||||
|
* The `(` character.
|
||||||
|
*/
|
||||||
|
OpenParen = 40,
|
||||||
|
/**
|
||||||
|
* The `)` character.
|
||||||
|
*/
|
||||||
|
CloseParen = 41,
|
||||||
|
/**
|
||||||
|
* The `*` character.
|
||||||
|
*/
|
||||||
|
Asterisk = 42,
|
||||||
|
/**
|
||||||
|
* The `+` character.
|
||||||
|
*/
|
||||||
|
Plus = 43,
|
||||||
|
/**
|
||||||
|
* The `,` character.
|
||||||
|
*/
|
||||||
|
Comma = 44,
|
||||||
|
/**
|
||||||
|
* The `-` character.
|
||||||
|
*/
|
||||||
|
Dash = 45,
|
||||||
|
/**
|
||||||
|
* The `.` character.
|
||||||
|
*/
|
||||||
|
Period = 46,
|
||||||
|
/**
|
||||||
|
* The `/` character.
|
||||||
|
*/
|
||||||
|
Slash = 47,
|
||||||
|
|
||||||
|
Digit0 = 48,
|
||||||
|
Digit1 = 49,
|
||||||
|
Digit2 = 50,
|
||||||
|
Digit3 = 51,
|
||||||
|
Digit4 = 52,
|
||||||
|
Digit5 = 53,
|
||||||
|
Digit6 = 54,
|
||||||
|
Digit7 = 55,
|
||||||
|
Digit8 = 56,
|
||||||
|
Digit9 = 57,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `:` character.
|
||||||
|
*/
|
||||||
|
Colon = 58,
|
||||||
|
/**
|
||||||
|
* The `;` character.
|
||||||
|
*/
|
||||||
|
Semicolon = 59,
|
||||||
|
/**
|
||||||
|
* The `<` character.
|
||||||
|
*/
|
||||||
|
LessThan = 60,
|
||||||
|
/**
|
||||||
|
* The `=` character.
|
||||||
|
*/
|
||||||
|
Equals = 61,
|
||||||
|
/**
|
||||||
|
* The `>` character.
|
||||||
|
*/
|
||||||
|
GreaterThan = 62,
|
||||||
|
/**
|
||||||
|
* The `?` character.
|
||||||
|
*/
|
||||||
|
QuestionMark = 63,
|
||||||
|
/**
|
||||||
|
* The `@` character.
|
||||||
|
*/
|
||||||
|
AtSign = 64,
|
||||||
|
|
||||||
|
A = 65,
|
||||||
|
B = 66,
|
||||||
|
C = 67,
|
||||||
|
D = 68,
|
||||||
|
E = 69,
|
||||||
|
F = 70,
|
||||||
|
G = 71,
|
||||||
|
H = 72,
|
||||||
|
I = 73,
|
||||||
|
J = 74,
|
||||||
|
K = 75,
|
||||||
|
L = 76,
|
||||||
|
M = 77,
|
||||||
|
N = 78,
|
||||||
|
O = 79,
|
||||||
|
P = 80,
|
||||||
|
Q = 81,
|
||||||
|
R = 82,
|
||||||
|
S = 83,
|
||||||
|
T = 84,
|
||||||
|
U = 85,
|
||||||
|
V = 86,
|
||||||
|
W = 87,
|
||||||
|
X = 88,
|
||||||
|
Y = 89,
|
||||||
|
Z = 90,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `[` character.
|
||||||
|
*/
|
||||||
|
OpenSquareBracket = 91,
|
||||||
|
/**
|
||||||
|
* The `\` character.
|
||||||
|
*/
|
||||||
|
Backslash = 92,
|
||||||
|
/**
|
||||||
|
* The `]` character.
|
||||||
|
*/
|
||||||
|
CloseSquareBracket = 93,
|
||||||
|
/**
|
||||||
|
* The `^` character.
|
||||||
|
*/
|
||||||
|
Caret = 94,
|
||||||
|
/**
|
||||||
|
* The `_` character.
|
||||||
|
*/
|
||||||
|
Underline = 95,
|
||||||
|
/**
|
||||||
|
* The ``(`)`` character.
|
||||||
|
*/
|
||||||
|
BackTick = 96,
|
||||||
|
|
||||||
|
a = 97,
|
||||||
|
b = 98,
|
||||||
|
c = 99,
|
||||||
|
d = 100,
|
||||||
|
e = 101,
|
||||||
|
f = 102,
|
||||||
|
g = 103,
|
||||||
|
h = 104,
|
||||||
|
i = 105,
|
||||||
|
j = 106,
|
||||||
|
k = 107,
|
||||||
|
l = 108,
|
||||||
|
m = 109,
|
||||||
|
n = 110,
|
||||||
|
o = 111,
|
||||||
|
p = 112,
|
||||||
|
q = 113,
|
||||||
|
r = 114,
|
||||||
|
s = 115,
|
||||||
|
t = 116,
|
||||||
|
u = 117,
|
||||||
|
v = 118,
|
||||||
|
w = 119,
|
||||||
|
x = 120,
|
||||||
|
y = 121,
|
||||||
|
z = 122,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `{` character.
|
||||||
|
*/
|
||||||
|
OpenCurlyBrace = 123,
|
||||||
|
/**
|
||||||
|
* The `|` character.
|
||||||
|
*/
|
||||||
|
Pipe = 124,
|
||||||
|
/**
|
||||||
|
* The `}` character.
|
||||||
|
*/
|
||||||
|
CloseCurlyBrace = 125,
|
||||||
|
/**
|
||||||
|
* The `~` character.
|
||||||
|
*/
|
||||||
|
Tilde = 126,
|
||||||
|
}
|
463
src/common/string/filter/filter.ts
Normal file
463
src/common/string/filter/filter.ts
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
// Copyright (c) 2015 - present Microsoft Corporation
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { CharCode } from "./char-code";
|
||||||
|
|
||||||
|
const _debug = false;
|
||||||
|
|
||||||
|
export interface Match {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _maxLen = 128;
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
|
const table: number[][] = [];
|
||||||
|
const row: number[] = [0];
|
||||||
|
for (let i = 1; i <= _maxLen; i++) {
|
||||||
|
row.push(-i);
|
||||||
|
}
|
||||||
|
for (let i = 0; i <= _maxLen; i++) {
|
||||||
|
const thisRow = row.slice(0);
|
||||||
|
thisRow[0] = -i;
|
||||||
|
table.push(thisRow);
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSeparatorAtPos(value: string, index: number): boolean {
|
||||||
|
if (index < 0 || index >= value.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const code = value.charCodeAt(index);
|
||||||
|
switch (code) {
|
||||||
|
case CharCode.Underline:
|
||||||
|
case CharCode.Dash:
|
||||||
|
case CharCode.Period:
|
||||||
|
case CharCode.Space:
|
||||||
|
case CharCode.Slash:
|
||||||
|
case CharCode.Backslash:
|
||||||
|
case CharCode.SingleQuote:
|
||||||
|
case CharCode.DoubleQuote:
|
||||||
|
case CharCode.Colon:
|
||||||
|
case CharCode.DollarSign:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWhitespaceAtPos(value: string, index: number): boolean {
|
||||||
|
if (index < 0 || index >= value.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const code = value.charCodeAt(index);
|
||||||
|
switch (code) {
|
||||||
|
case CharCode.Space:
|
||||||
|
case CharCode.Tab:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
|
||||||
|
return word[pos] !== wordLow[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPatternInWord(
|
||||||
|
patternLow: string,
|
||||||
|
patternPos: number,
|
||||||
|
patternLen: number,
|
||||||
|
wordLow: string,
|
||||||
|
wordPos: number,
|
||||||
|
wordLen: number
|
||||||
|
): boolean {
|
||||||
|
while (patternPos < patternLen && wordPos < wordLen) {
|
||||||
|
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||||
|
patternPos += 1;
|
||||||
|
}
|
||||||
|
wordPos += 1;
|
||||||
|
}
|
||||||
|
return patternPos === patternLen; // pattern must be exhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Arrow {
|
||||||
|
Top = 0b1,
|
||||||
|
Diag = 0b10,
|
||||||
|
Left = 0b100,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tuple of three values.
|
||||||
|
* 0. the score
|
||||||
|
* 1. the matches encoded as bitmask (2^53)
|
||||||
|
* 2. the offset at which matching started
|
||||||
|
*/
|
||||||
|
export type FuzzyScore = [number, number, number];
|
||||||
|
|
||||||
|
interface FilterGlobals {
|
||||||
|
_matchesCount: number;
|
||||||
|
_topMatch2: number;
|
||||||
|
_topScore: number;
|
||||||
|
_wordStart: number;
|
||||||
|
_firstMatchCanBeWeak: boolean;
|
||||||
|
_table: number[][];
|
||||||
|
_scores: number[][];
|
||||||
|
_arrows: Arrow[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
function initGlobals(): FilterGlobals {
|
||||||
|
return {
|
||||||
|
_matchesCount: 0,
|
||||||
|
_topMatch2: 0,
|
||||||
|
_topScore: 0,
|
||||||
|
_wordStart: 0,
|
||||||
|
_firstMatchCanBeWeak: false,
|
||||||
|
_table: initTable(),
|
||||||
|
_scores: initTable(),
|
||||||
|
_arrows: <Arrow[][]>initTable(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fuzzyScore(
|
||||||
|
pattern: string,
|
||||||
|
patternLow: string,
|
||||||
|
patternStart: number,
|
||||||
|
word: string,
|
||||||
|
wordLow: string,
|
||||||
|
wordStart: number,
|
||||||
|
firstMatchCanBeWeak: boolean
|
||||||
|
): FuzzyScore | undefined {
|
||||||
|
const globals = initGlobals();
|
||||||
|
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
|
||||||
|
const wordLen = word.length > _maxLen ? _maxLen : word.length;
|
||||||
|
|
||||||
|
if (
|
||||||
|
patternStart >= patternLen ||
|
||||||
|
wordStart >= wordLen ||
|
||||||
|
patternLen - patternStart > wordLen - wordStart
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a simple check if the characters of pattern occur
|
||||||
|
// (in order) at all in word. If that isn't the case we
|
||||||
|
// stop because no match will be possible
|
||||||
|
if (
|
||||||
|
!isPatternInWord(
|
||||||
|
patternLow,
|
||||||
|
patternStart,
|
||||||
|
patternLen,
|
||||||
|
wordLow,
|
||||||
|
wordStart,
|
||||||
|
wordLen
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = 1;
|
||||||
|
let column = 1;
|
||||||
|
let patternPos = patternStart;
|
||||||
|
let wordPos = wordStart;
|
||||||
|
|
||||||
|
let hasStrongFirstMatch = false;
|
||||||
|
|
||||||
|
// There will be a match, fill in tables
|
||||||
|
for (
|
||||||
|
row = 1, patternPos = patternStart;
|
||||||
|
patternPos < patternLen;
|
||||||
|
row++, patternPos++
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
column = 1, wordPos = wordStart;
|
||||||
|
wordPos < wordLen;
|
||||||
|
column++, wordPos++
|
||||||
|
) {
|
||||||
|
const score = _doScore(
|
||||||
|
pattern,
|
||||||
|
patternLow,
|
||||||
|
patternPos,
|
||||||
|
patternStart,
|
||||||
|
word,
|
||||||
|
wordLow,
|
||||||
|
wordPos
|
||||||
|
);
|
||||||
|
|
||||||
|
if (patternPos === patternStart && score > 1) {
|
||||||
|
hasStrongFirstMatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
globals._scores[row][column] = score;
|
||||||
|
|
||||||
|
const diag =
|
||||||
|
globals._table[row - 1][column - 1] + (score > 1 ? 1 : score);
|
||||||
|
const top = globals._table[row - 1][column] + -1;
|
||||||
|
const left = globals._table[row][column - 1] + -1;
|
||||||
|
|
||||||
|
if (left >= top) {
|
||||||
|
// left or diag
|
||||||
|
if (left > diag) {
|
||||||
|
globals._table[row][column] = left;
|
||||||
|
globals._arrows[row][column] = Arrow.Left;
|
||||||
|
} else if (left === diag) {
|
||||||
|
globals._table[row][column] = left;
|
||||||
|
globals._arrows[row][column] = Arrow.Left || Arrow.Diag;
|
||||||
|
} else {
|
||||||
|
globals._table[row][column] = diag;
|
||||||
|
globals._arrows[row][column] = Arrow.Diag;
|
||||||
|
}
|
||||||
|
} else if (top > diag) {
|
||||||
|
globals._table[row][column] = top;
|
||||||
|
globals._arrows[row][column] = Arrow.Top;
|
||||||
|
} else if (top === diag) {
|
||||||
|
globals._table[row][column] = top;
|
||||||
|
globals._arrows[row][column] = Arrow.Top || Arrow.Diag;
|
||||||
|
} else {
|
||||||
|
globals._table[row][column] = diag;
|
||||||
|
globals._arrows[row][column] = Arrow.Diag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_debug) {
|
||||||
|
printTables(pattern, patternStart, word, wordStart, globals);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStrongFirstMatch && !firstMatchCanBeWeak) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
globals._matchesCount = 0;
|
||||||
|
globals._topScore = -100;
|
||||||
|
globals._wordStart = wordStart;
|
||||||
|
globals._firstMatchCanBeWeak = firstMatchCanBeWeak;
|
||||||
|
|
||||||
|
_findAllMatches2(
|
||||||
|
row - 1,
|
||||||
|
column - 1,
|
||||||
|
patternLen === wordLen ? 1 : 0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
globals
|
||||||
|
);
|
||||||
|
if (globals._matchesCount === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [globals._topScore, globals._topMatch2, wordStart];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _doScore(
|
||||||
|
pattern: string,
|
||||||
|
patternLow: string,
|
||||||
|
patternPos: number,
|
||||||
|
patternStart: number,
|
||||||
|
word: string,
|
||||||
|
wordLow: string,
|
||||||
|
wordPos: number
|
||||||
|
) {
|
||||||
|
if (patternLow[patternPos] !== wordLow[wordPos]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (wordPos === patternPos - patternStart) {
|
||||||
|
// common prefix: `foobar <-> foobaz`
|
||||||
|
// ^^^^^
|
||||||
|
if (pattern[patternPos] === word[wordPos]) {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isUpperCaseAtPos(wordPos, word, wordLow) &&
|
||||||
|
(wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))
|
||||||
|
) {
|
||||||
|
// hitting upper-case: `foo <-> forOthers`
|
||||||
|
// ^^ ^
|
||||||
|
if (pattern[patternPos] === word[wordPos]) {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isSeparatorAtPos(wordLow, wordPos) &&
|
||||||
|
(wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))
|
||||||
|
) {
|
||||||
|
// hitting a separator: `. <-> foo.bar`
|
||||||
|
// ^
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isSeparatorAtPos(wordLow, wordPos - 1) ||
|
||||||
|
isWhitespaceAtPos(wordLow, wordPos - 1)
|
||||||
|
) {
|
||||||
|
// post separator: `foo <-> bar_foo`
|
||||||
|
// ^^^
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTable(
|
||||||
|
table: number[][],
|
||||||
|
pattern: string,
|
||||||
|
patternLen: number,
|
||||||
|
word: string,
|
||||||
|
wordLen: number
|
||||||
|
): string {
|
||||||
|
function pad(s: string, n: number, _pad = " ") {
|
||||||
|
while (s.length < n) {
|
||||||
|
s = _pad + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
let ret = ` | |${word
|
||||||
|
.split("")
|
||||||
|
.map((c) => pad(c, 3))
|
||||||
|
.join("|")}\n`;
|
||||||
|
|
||||||
|
for (let i = 0; i <= patternLen; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
ret += " |";
|
||||||
|
} else {
|
||||||
|
ret += `${pattern[i - 1]}|`;
|
||||||
|
}
|
||||||
|
ret +=
|
||||||
|
table[i]
|
||||||
|
.slice(0, wordLen + 1)
|
||||||
|
.map((n) => pad(n.toString(), 3))
|
||||||
|
.join("|") + "\n";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTables(
|
||||||
|
pattern: string,
|
||||||
|
patternStart: number,
|
||||||
|
word: string,
|
||||||
|
wordStart: number,
|
||||||
|
globals: FilterGlobals
|
||||||
|
): void {
|
||||||
|
pattern = pattern.substr(patternStart);
|
||||||
|
word = word.substr(wordStart);
|
||||||
|
console.log(
|
||||||
|
printTable(globals._table, pattern, pattern.length, word, word.length)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
printTable(globals._arrows, pattern, pattern.length, word, word.length)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
printTable(globals._scores, pattern, pattern.length, word, word.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _findAllMatches2(
|
||||||
|
row: number,
|
||||||
|
column: number,
|
||||||
|
total: number,
|
||||||
|
matches: number,
|
||||||
|
lastMatched: boolean,
|
||||||
|
globals: FilterGlobals
|
||||||
|
): void {
|
||||||
|
if (globals._matchesCount >= 10 || total < -25) {
|
||||||
|
// stop when having already 10 results, or
|
||||||
|
// when a potential alignment as already 5 gaps
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let simpleMatchCount = 0;
|
||||||
|
|
||||||
|
while (row > 0 && column > 0) {
|
||||||
|
const score = globals._scores[row][column];
|
||||||
|
const arrow = globals._arrows[row][column];
|
||||||
|
|
||||||
|
if (arrow === Arrow.Left) {
|
||||||
|
// left -> no match, skip a word character
|
||||||
|
column -= 1;
|
||||||
|
if (lastMatched) {
|
||||||
|
total -= 5; // new gap penalty
|
||||||
|
} else if (matches !== 0) {
|
||||||
|
total -= 1; // gap penalty after first match
|
||||||
|
}
|
||||||
|
lastMatched = false;
|
||||||
|
simpleMatchCount = 0;
|
||||||
|
} else if (arrow && Arrow.Diag) {
|
||||||
|
if (arrow && Arrow.Left) {
|
||||||
|
// left
|
||||||
|
_findAllMatches2(
|
||||||
|
row,
|
||||||
|
column - 1,
|
||||||
|
matches !== 0 ? total - 1 : total, // gap penalty after first match
|
||||||
|
matches,
|
||||||
|
lastMatched,
|
||||||
|
globals
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// diag
|
||||||
|
total += score;
|
||||||
|
row -= 1;
|
||||||
|
column -= 1;
|
||||||
|
lastMatched = true;
|
||||||
|
|
||||||
|
// match -> set a 1 at the word pos
|
||||||
|
matches += 2 ** (column + globals._wordStart);
|
||||||
|
|
||||||
|
// count simple matches and boost a row of
|
||||||
|
// simple matches when they yield in a
|
||||||
|
// strong match.
|
||||||
|
if (score === 1) {
|
||||||
|
simpleMatchCount += 1;
|
||||||
|
|
||||||
|
if (row === 0 && !globals._firstMatchCanBeWeak) {
|
||||||
|
// when the first match is a weak
|
||||||
|
// match we discard it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// boost
|
||||||
|
total += 1 + simpleMatchCount * (score - 1);
|
||||||
|
simpleMatchCount = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total -= column >= 3 ? 9 : column * 3; // late start penalty
|
||||||
|
|
||||||
|
// dynamically keep track of the current top score
|
||||||
|
// and insert the current best score at head, the rest at tail
|
||||||
|
globals._matchesCount += 1;
|
||||||
|
if (total > globals._topScore) {
|
||||||
|
globals._topScore = total;
|
||||||
|
globals._topMatch2 = matches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion
|
66
src/common/string/filter/sequence-matching.ts
Normal file
66
src/common/string/filter/sequence-matching.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { fuzzyScore } from "./filter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a sequence of letters exists in another string,
|
||||||
|
* in that order, allowing for skipping. Ex: "chdr" exists in "chandelier")
|
||||||
|
*
|
||||||
|
* @param {string} filter - Sequence of letters to check for
|
||||||
|
* @param {string} word - Word to check for sequence
|
||||||
|
*
|
||||||
|
* @return {number} Score representing how well the word matches the filter. Return of 0 means no match.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const fuzzySequentialMatch = (filter: string, ...words: string[]) => {
|
||||||
|
let topScore = 0;
|
||||||
|
|
||||||
|
for (const word of words) {
|
||||||
|
const scores = fuzzyScore(
|
||||||
|
filter,
|
||||||
|
filter.toLowerCase(),
|
||||||
|
0,
|
||||||
|
word,
|
||||||
|
word.toLowerCase(),
|
||||||
|
0,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!scores) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The VS Code implementation of filter treats a score of "0" as just barely a match
|
||||||
|
// But we will typically use this matcher in a .filter(), which interprets 0 as a failure.
|
||||||
|
// By shifting all scores up by 1, we allow "0" matches, while retaining score precedence
|
||||||
|
const score = scores[0] + 1;
|
||||||
|
|
||||||
|
if (score > topScore) {
|
||||||
|
topScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return topScore;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ScorableTextItem {
|
||||||
|
score?: number;
|
||||||
|
text: string;
|
||||||
|
altText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FuzzyFilterSort = <T extends ScorableTextItem>(
|
||||||
|
filter: string,
|
||||||
|
items: T[]
|
||||||
|
) => T[];
|
||||||
|
|
||||||
|
export const fuzzyFilterSort: FuzzyFilterSort = (filter, items) => {
|
||||||
|
return items
|
||||||
|
.map((item) => {
|
||||||
|
item.score = item.altText
|
||||||
|
? fuzzySequentialMatch(filter, item.text, item.altText)
|
||||||
|
: fuzzySequentialMatch(filter, item.text);
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.filter((item) => item.score !== undefined && item.score > 0)
|
||||||
|
.sort(({ score: scoreA = 0 }, { score: scoreB = 0 }) =>
|
||||||
|
scoreA > scoreB ? -1 : scoreA < scoreB ? 1 : 0
|
||||||
|
);
|
||||||
|
};
|
22
src/common/string/number-format.ts
Normal file
22
src/common/string/number-format.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Formats a number based on the specified language with thousands separator(s) and decimal character for better legibility.
|
||||||
|
*
|
||||||
|
* @param num The number to format
|
||||||
|
* @param language The language to use when formatting the number
|
||||||
|
*/
|
||||||
|
export const numberFormat = (
|
||||||
|
num: string | number,
|
||||||
|
language: string
|
||||||
|
): string => {
|
||||||
|
// Polyfill for Number.isNaN, which is more reliable that the global isNaN()
|
||||||
|
Number.isNaN =
|
||||||
|
Number.isNaN ||
|
||||||
|
function isNaN(input) {
|
||||||
|
return typeof input === "number" && isNaN(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!Number.isNaN(Number(num)) && Intl) {
|
||||||
|
return new Intl.NumberFormat(language).format(Number(num));
|
||||||
|
}
|
||||||
|
return num.toString();
|
||||||
|
};
|
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Determine whether a sequence of letters exists in another string,
|
|
||||||
* in that order, allowing for skipping. Ex: "chdr" exists in "chandelier")
|
|
||||||
*
|
|
||||||
* filter => sequence of letters
|
|
||||||
* word => Word to check for sequence
|
|
||||||
*
|
|
||||||
* return true if word contains sequence. Otherwise false.
|
|
||||||
*/
|
|
||||||
export const fuzzySequentialMatch = (filter: string, word: string) => {
|
|
||||||
if (filter === "") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i <= filter.length; i++) {
|
|
||||||
const pos = word.indexOf(filter[0]);
|
|
||||||
|
|
||||||
if (pos < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newWord = word.substring(pos + 1);
|
|
||||||
const newFilter = filter.substring(1);
|
|
||||||
|
|
||||||
return fuzzySequentialMatch(newFilter, newWord);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
@@ -1,4 +1,5 @@
|
|||||||
import IntlMessageFormat from "intl-messageformat";
|
import IntlMessageFormat from "intl-messageformat";
|
||||||
|
import { shouldPolyfill } from "@formatjs/intl-pluralrules/should-polyfill";
|
||||||
import { Resources } from "../../types";
|
import { Resources } from "../../types";
|
||||||
|
|
||||||
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
||||||
@@ -12,9 +13,12 @@ export interface FormatsType {
|
|||||||
time: FormatType;
|
time: FormatType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Intl.PluralRules) {
|
let polyfillLoaded = !shouldPolyfill();
|
||||||
import("@formatjs/intl-pluralrules/polyfill-locales");
|
const polyfillProm = polyfillLoaded
|
||||||
}
|
? undefined
|
||||||
|
: import("@formatjs/intl-pluralrules/polyfill-locales").then(() => {
|
||||||
|
polyfillLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapted from Polymer app-localize-behavior.
|
* Adapted from Polymer app-localize-behavior.
|
||||||
@@ -37,12 +41,16 @@ if (!Intl.PluralRules) {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const computeLocalize = (
|
export const computeLocalize = async (
|
||||||
cache: any,
|
cache: any,
|
||||||
language: string,
|
language: string,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
formats?: FormatsType
|
formats?: FormatsType
|
||||||
): LocalizeFunc => {
|
): Promise<LocalizeFunc> => {
|
||||||
|
if (!polyfillLoaded) {
|
||||||
|
await polyfillProm;
|
||||||
|
}
|
||||||
|
|
||||||
// Everytime any of the parameters change, invalidate the strings cache.
|
// Everytime any of the parameters change, invalidate the strings cache.
|
||||||
cache._localizationCache = {};
|
cache._localizationCache = {};
|
||||||
|
|
||||||
|
8
src/common/util/copy-clipboard.ts
Normal file
8
src/common/util/copy-clipboard.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const copyToClipboard = (str) => {
|
||||||
|
const el = document.createElement("textarea");
|
||||||
|
el.value = str;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(el);
|
||||||
|
};
|
@@ -94,6 +94,8 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public selectable = false;
|
@property({ type: Boolean }) public selectable = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public clickable = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public hasFab = false;
|
@property({ type: Boolean }) public hasFab = false;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "auto-height" })
|
@property({ type: Boolean, attribute: "auto-height" })
|
||||||
@@ -327,12 +329,13 @@ export class HaDataTable extends LitElement {
|
|||||||
<div
|
<div
|
||||||
aria-rowindex=${index}
|
aria-rowindex=${index}
|
||||||
role="row"
|
role="row"
|
||||||
.rowId="${row[this.id]}"
|
.rowId=${row[this.id]}
|
||||||
@click=${this._handleRowClick}
|
@click=${this._handleRowClick}
|
||||||
class="mdc-data-table__row ${classMap({
|
class="mdc-data-table__row ${classMap({
|
||||||
"mdc-data-table__row--selected": this._checkedRows.includes(
|
"mdc-data-table__row--selected": this._checkedRows.includes(
|
||||||
String(row[this.id])
|
String(row[this.id])
|
||||||
),
|
),
|
||||||
|
clickable: this.clickable,
|
||||||
})}"
|
})}"
|
||||||
aria-selected=${ifDefined(
|
aria-selected=${ifDefined(
|
||||||
this._checkedRows.includes(String(row[this.id]))
|
this._checkedRows.includes(String(row[this.id]))
|
||||||
@@ -350,6 +353,7 @@ export class HaDataTable extends LitElement {
|
|||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
class="mdc-data-table__row-checkbox"
|
class="mdc-data-table__row-checkbox"
|
||||||
@change=${this._handleRowCheckboxClick}
|
@change=${this._handleRowCheckboxClick}
|
||||||
|
.rowId=${row[this.id]}
|
||||||
.disabled=${row.selectable === false}
|
.disabled=${row.selectable === false}
|
||||||
.checked=${this._checkedRows.includes(
|
.checked=${this._checkedRows.includes(
|
||||||
String(row[this.id])
|
String(row[this.id])
|
||||||
@@ -457,9 +461,7 @@ export class HaDataTable extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private _handleHeaderClick(ev: Event) {
|
private _handleHeaderClick(ev: Event) {
|
||||||
const columnId = ((ev.target as HTMLElement).closest(
|
const columnId = (ev.currentTarget as any).columnId;
|
||||||
".mdc-data-table__header-cell"
|
|
||||||
) as any).columnId;
|
|
||||||
if (!this.columns[columnId].sortable) {
|
if (!this.columns[columnId].sortable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -493,8 +495,8 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleRowCheckboxClick(ev: Event) {
|
private _handleRowCheckboxClick(ev: Event) {
|
||||||
const checkbox = ev.target as HaCheckbox;
|
const checkbox = ev.currentTarget as HaCheckbox;
|
||||||
const rowId = (checkbox.closest(".mdc-data-table__row") as any).rowId;
|
const rowId = (checkbox as any).rowId;
|
||||||
|
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
if (this._checkedRows.includes(rowId)) {
|
if (this._checkedRows.includes(rowId)) {
|
||||||
@@ -512,7 +514,7 @@ export class HaDataTable extends LitElement {
|
|||||||
if (target.tagName === "HA-CHECKBOX") {
|
if (target.tagName === "HA-CHECKBOX") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rowId = (target.closest(".mdc-data-table__row") as any).rowId;
|
const rowId = (ev.currentTarget as any).rowId;
|
||||||
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,6 +888,9 @@ export class HaDataTable extends LitElement {
|
|||||||
.forceLTR {
|
.forceLTR {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "../ha-icon-button";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
@@ -38,6 +38,8 @@ import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
|||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "./ha-devices-picker";
|
import "./ha-devices-picker";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
|
|
||||||
interface DevicesByArea {
|
interface DevicesByArea {
|
||||||
[areaId: string]: AreaDevices;
|
[areaId: string]: AreaDevices;
|
||||||
@@ -62,7 +64,7 @@ const rowRenderer = (
|
|||||||
margin: -10px 0;
|
margin: -10px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
ha-icon-button {
|
mwc-icon-button {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
.devices {
|
.devices {
|
||||||
@@ -324,36 +326,34 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
${this.value
|
<div class="suffix" slot="suffix">
|
||||||
? html`
|
${this.value
|
||||||
<ha-icon-button
|
? html`<mwc-icon-button
|
||||||
aria-label=${this.hass.localize(
|
class="clear-button"
|
||||||
|
.label=${this.hass.localize(
|
||||||
"ui.components.device-picker.clear"
|
"ui.components.device-picker.clear"
|
||||||
)}
|
)}
|
||||||
slot="suffix"
|
|
||||||
class="clear-button"
|
|
||||||
icon="hass:close"
|
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
no-ripple
|
no-ripple
|
||||||
>
|
>
|
||||||
Clear
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
</ha-icon-button>
|
</mwc-icon-button> `
|
||||||
`
|
: ""}
|
||||||
: ""}
|
${areas.length > 0
|
||||||
${areas.length > 0
|
? html`
|
||||||
? html`
|
<mwc-icon-button
|
||||||
<ha-icon-button
|
.label=${this.hass.localize(
|
||||||
aria-label=${this.hass.localize(
|
"ui.components.device-picker.show_devices"
|
||||||
"ui.components.device-picker.show_devices"
|
)}
|
||||||
)}
|
class="toggle-button"
|
||||||
slot="suffix"
|
>
|
||||||
class="toggle-button"
|
<ha-svg-icon
|
||||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
>
|
></ha-svg-icon>
|
||||||
Toggle
|
</mwc-icon-button>
|
||||||
</ha-icon-button>
|
`
|
||||||
`
|
: ""}
|
||||||
: ""}
|
</div>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
<mwc-button @click=${this._switchPicker}
|
<mwc-button @click=${this._switchPicker}
|
||||||
@@ -409,10 +409,12 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
paper-input > ha-icon-button {
|
.suffix {
|
||||||
width: 24px;
|
display: flex;
|
||||||
height: 24px;
|
}
|
||||||
padding: 2px;
|
mwc-icon-button {
|
||||||
|
--mdc-icon-button-size: 24px;
|
||||||
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
[hidden] {
|
[hidden] {
|
||||||
|
@@ -9,9 +9,17 @@ import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
|||||||
|
|
||||||
@customElement("ha-device-action-picker")
|
@customElement("ha-device-action-picker")
|
||||||
class HaDeviceActionPicker extends HaDeviceAutomationPicker<DeviceAction> {
|
class HaDeviceActionPicker extends HaDeviceAutomationPicker<DeviceAction> {
|
||||||
protected NO_AUTOMATION_TEXT = "No actions";
|
protected get NO_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.actions.no_actions"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected UNKNOWN_AUTOMATION_TEXT = "Unknown action";
|
protected get UNKNOWN_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.actions.unknown_action"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
|
@@ -33,16 +33,24 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
|
|
||||||
@property() public value?: T;
|
@property() public value?: T;
|
||||||
|
|
||||||
protected NO_AUTOMATION_TEXT = "No automations";
|
|
||||||
|
|
||||||
protected UNKNOWN_AUTOMATION_TEXT = "Unknown automation";
|
|
||||||
|
|
||||||
@internalProperty() private _automations: T[] = [];
|
@internalProperty() private _automations: T[] = [];
|
||||||
|
|
||||||
// Trigger an empty render so we start with a clean DOM.
|
// Trigger an empty render so we start with a clean DOM.
|
||||||
// paper-listbox does not like changing things around.
|
// paper-listbox does not like changing things around.
|
||||||
@internalProperty() private _renderEmpty = false;
|
@internalProperty() private _renderEmpty = false;
|
||||||
|
|
||||||
|
protected get NO_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.actions.no_actions"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get UNKNOWN_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.actions.unknown_action"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _localizeDeviceAutomation: (
|
private _localizeDeviceAutomation: (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
automation: T
|
automation: T
|
||||||
|
@@ -11,9 +11,17 @@ import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
|||||||
class HaDeviceConditionPicker extends HaDeviceAutomationPicker<
|
class HaDeviceConditionPicker extends HaDeviceAutomationPicker<
|
||||||
DeviceCondition
|
DeviceCondition
|
||||||
> {
|
> {
|
||||||
protected NO_AUTOMATION_TEXT = "No conditions";
|
protected get NO_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.conditions.no_conditions"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected UNKNOWN_AUTOMATION_TEXT = "Unknown condition";
|
protected get UNKNOWN_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.conditions.unknown_condition"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
|
@@ -9,9 +9,17 @@ import { HaDeviceAutomationPicker } from "./ha-device-automation-picker";
|
|||||||
|
|
||||||
@customElement("ha-device-trigger-picker")
|
@customElement("ha-device-trigger-picker")
|
||||||
class HaDeviceTriggerPicker extends HaDeviceAutomationPicker<DeviceTrigger> {
|
class HaDeviceTriggerPicker extends HaDeviceAutomationPicker<DeviceTrigger> {
|
||||||
protected NO_AUTOMATION_TEXT = "No triggers";
|
protected get NO_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.triggers.no_triggers"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected UNKNOWN_AUTOMATION_TEXT = "Unknown trigger";
|
protected get UNKNOWN_AUTOMATION_TEXT() {
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.devices.automation.triggers.unknown_trigger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
|
@@ -88,6 +88,7 @@ class HaChartBase extends mixinBehaviors(
|
|||||||
.chartTooltip .beforeBody {
|
.chartTooltip .beforeBody {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.chartLegend li {
|
.chartLegend li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -278,7 +279,7 @@ class HaChartBase extends mixinBehaviors(
|
|||||||
this.set(["tooltip", "title"], title);
|
this.set(["tooltip", "title"], title);
|
||||||
|
|
||||||
if (tooltip.beforeBody) {
|
if (tooltip.beforeBody) {
|
||||||
this.set(["tooltip", "beforeBody"], tooltip.beforeBody.join("\n"));
|
this.set(["tooltip", "beforeBody"], tooltip.beforeBody.join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const bodyLines = tooltip.body.map((n) => n.lines);
|
const bodyLines = tooltip.body.map((n) => n.lines);
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||||
@@ -16,8 +17,10 @@ import {
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-icon-button";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
|
import { formatAttributeName } from "../../util/hass-attributes-util";
|
||||||
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
|
|
||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
|
|
||||||
@@ -33,7 +36,9 @@ const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => {
|
|||||||
<paper-item></paper-item>
|
<paper-item></paper-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
root.querySelector("paper-item")!.textContent = model.item;
|
root.querySelector("paper-item")!.textContent = formatAttributeName(
|
||||||
|
model.item
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-entity-attribute-picker")
|
@customElement("ha-entity-attribute-picker")
|
||||||
@@ -80,6 +85,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
|
attr-for-value="bind-value"
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
>
|
>
|
||||||
@@ -89,7 +95,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
"ui.components.entity.entity-attribute-picker.attribute"
|
"ui.components.entity.entity-attribute-picker.attribute"
|
||||||
)}
|
)}
|
||||||
.value=${this._value}
|
.value=${this._value ? formatAttributeName(this._value) : ""}
|
||||||
.disabled=${this.disabled || !this.entityId}
|
.disabled=${this.disabled || !this.entityId}
|
||||||
class="input"
|
class="input"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
@@ -97,33 +103,35 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
${this.value
|
<div class="suffix" slot="suffix">
|
||||||
? html`
|
${this.value
|
||||||
<ha-icon-button
|
? html`
|
||||||
aria-label=${this.hass.localize(
|
<mwc-icon-button
|
||||||
"ui.components.entity.entity-picker.clear"
|
.label=${this.hass.localize(
|
||||||
)}
|
"ui.components.entity.entity-picker.clear"
|
||||||
slot="suffix"
|
)}
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
icon="hass:close"
|
tabindex="-1"
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
no-ripple
|
no-ripple
|
||||||
>
|
>
|
||||||
Clear
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
</ha-icon-button>
|
</mwc-icon-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<ha-icon-button
|
<mwc-icon-button
|
||||||
aria-label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.entity.entity-attribute-picker.show_attributes"
|
"ui.components.entity.entity-attribute-picker.show_attributes"
|
||||||
)}
|
)}
|
||||||
slot="suffix"
|
class="toggle-button"
|
||||||
class="toggle-button"
|
tabindex="-1"
|
||||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
>
|
||||||
>
|
<ha-svg-icon
|
||||||
Toggle
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
</ha-icon-button>
|
></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</div>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
`;
|
`;
|
||||||
@@ -135,7 +143,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || "";
|
return this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
@@ -159,7 +167,10 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
paper-input > ha-icon-button {
|
.suffix {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
mwc-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
|
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
@@ -20,7 +22,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
|||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-icon-button";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
|
|
||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
@@ -101,6 +103,8 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
private _initedStates = false;
|
private _initedStates = false;
|
||||||
|
|
||||||
|
private _states: HassEntity[] = [];
|
||||||
|
|
||||||
private _getStates = memoizeOne(
|
private _getStates = memoizeOne(
|
||||||
(
|
(
|
||||||
_opened: boolean,
|
_opened: boolean,
|
||||||
@@ -166,7 +170,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
|
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
|
||||||
const states = this._getStates(
|
this._states = this._getStates(
|
||||||
this._opened,
|
this._opened,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.includeDomains,
|
this.includeDomains,
|
||||||
@@ -174,7 +178,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
this.entityFilter,
|
this.entityFilter,
|
||||||
this.includeDeviceClasses
|
this.includeDeviceClasses
|
||||||
);
|
);
|
||||||
(this._comboBox as any).items = states;
|
(this._comboBox as any).filteredItems = this._states;
|
||||||
this._initedStates = true;
|
this._initedStates = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,6 +196,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
@filter-changed=${this._filterChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
<paper-input
|
||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
@@ -206,35 +211,35 @@ export class HaEntityPicker extends LitElement {
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
${this.value && !this.hideClearIcon
|
<div class="suffix" slot="suffix">
|
||||||
? html`
|
${this.value && !this.hideClearIcon
|
||||||
<ha-icon-button
|
? html`
|
||||||
aria-label=${this.hass.localize(
|
<mwc-icon-button
|
||||||
"ui.components.entity.entity-picker.clear"
|
.label=${this.hass.localize(
|
||||||
)}
|
"ui.components.entity.entity-picker.clear"
|
||||||
slot="suffix"
|
)}
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
icon="hass:close"
|
tabindex="-1"
|
||||||
tabindex="-1"
|
@click=${this._clearValue}
|
||||||
@click=${this._clearValue}
|
no-ripple
|
||||||
no-ripple
|
>
|
||||||
>
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
Clear
|
</mwc-icon-button>
|
||||||
</ha-icon-button>
|
`
|
||||||
`
|
: ""}
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
<mwc-icon-button
|
||||||
aria-label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.entity.entity-picker.show_entities"
|
"ui.components.entity.entity-picker.show_entities"
|
||||||
)}
|
)}
|
||||||
slot="suffix"
|
class="toggle-button"
|
||||||
class="toggle-button"
|
tabindex="-1"
|
||||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
>
|
||||||
tabindex="-1"
|
<ha-svg-icon
|
||||||
>
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
Toggle
|
></ha-svg-icon>
|
||||||
</ha-icon-button>
|
</mwc-icon-button>
|
||||||
|
</div>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
`;
|
`;
|
||||||
@@ -260,6 +265,15 @@ export class HaEntityPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
|
const filterString = ev.detail.value.toLowerCase();
|
||||||
|
(this._comboBox as any).filteredItems = this._states.filter(
|
||||||
|
(state) =>
|
||||||
|
state.entity_id.toLowerCase().includes(filterString) ||
|
||||||
|
computeStateName(state).toLowerCase().includes(filterString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _setValue(value: string) {
|
private _setValue(value: string) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -270,7 +284,10 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
paper-input > ha-icon-button {
|
.suffix {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
mwc-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@@ -1,25 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { stateIcon } from "../../common/entity/state_icon";
|
|
||||||
import "../ha-icon";
|
|
||||||
|
|
||||||
class HaStateIcon extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html` <ha-icon icon="[[computeIcon(stateObj)]]"></ha-icon> `;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeIcon(stateObj) {
|
|
||||||
return stateIcon(stateObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-state-icon", HaStateIcon);
|
|
@@ -154,11 +154,8 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
case "device_tracker":
|
case "device_tracker":
|
||||||
case "updater":
|
case "updater":
|
||||||
case "person":
|
case "person":
|
||||||
return stateIcon(state);
|
|
||||||
case "sun":
|
case "sun":
|
||||||
return state.state === "above_horizon"
|
return stateIcon(state);
|
||||||
? domainIcon(domain)
|
|
||||||
: "hass:brightness-3";
|
|
||||||
case "timer":
|
case "timer":
|
||||||
return state.state === "active"
|
return state.state === "active"
|
||||||
? "hass:timer-outline"
|
? "hass:timer-outline"
|
||||||
|
@@ -11,11 +11,14 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { ifDefined } from "lit-html/directives/if-defined";
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
|
|
||||||
import { computeActiveState } from "../../common/entity/compute_active_state";
|
import { computeActiveState } from "../../common/entity/compute_active_state";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { stateIcon } from "../../common/entity/state_icon";
|
import { stateIcon } from "../../common/entity/state_icon";
|
||||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||||
|
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
import "../ha-icon";
|
import "../ha-icon";
|
||||||
|
|
||||||
export class StateBadge extends LitElement {
|
export class StateBadge extends LitElement {
|
||||||
@@ -37,7 +40,13 @@ export class StateBadge extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const stateObj = this.stateObj;
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
if (!stateObj || !this._showIcon) {
|
if (!stateObj) {
|
||||||
|
return html`<div class="missing">
|
||||||
|
<ha-icon icon="hass:alert"></ha-icon>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._showIcon) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +149,9 @@ export class StateBadge extends LitElement {
|
|||||||
ha-icon {
|
ha-icon {
|
||||||
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
.missing {
|
||||||
|
color: #fce588;
|
||||||
|
}
|
||||||
|
|
||||||
${iconColorCSS}
|
${iconColorCSS}
|
||||||
`;
|
`;
|
||||||
|
@@ -1,124 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
|
||||||
import "../ha-relative-time";
|
|
||||||
import "./state-badge";
|
|
||||||
|
|
||||||
class StateInfo extends LocalizeMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
${this.styleTemplate} ${this.stateBadgeTemplate} ${this.infoTemplate}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styleTemplate() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
@apply --paper-font-body1;
|
|
||||||
min-width: 120px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
state-badge {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) state-badge {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
margin-left: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .info {
|
|
||||||
margin-right: 56px;
|
|
||||||
margin-left: 0;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
@apply --paper-font-common-nowrap;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name[in-dialog],
|
|
||||||
:host([secondary-line]) .name {
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-ago,
|
|
||||||
.extra-info,
|
|
||||||
.extra-info > * {
|
|
||||||
@apply --paper-font-common-nowrap;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get stateBadgeTemplate() {
|
|
||||||
return html` <state-badge state-obj="[[stateObj]]"></state-badge> `;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get infoTemplate() {
|
|
||||||
return html`
|
|
||||||
<div class="info">
|
|
||||||
<div class="name" in-dialog$="[[inDialog]]">
|
|
||||||
[[computeStateName(stateObj)]]
|
|
||||||
</div>
|
|
||||||
<template is="dom-if" if="[[inDialog]]">
|
|
||||||
<div class="time-ago">
|
|
||||||
<ha-relative-time
|
|
||||||
id="last_changed"
|
|
||||||
hass="[[hass]]"
|
|
||||||
datetime="[[stateObj.last_changed]]"
|
|
||||||
></ha-relative-time>
|
|
||||||
<paper-tooltip animation-delay="0" for="last_changed">
|
|
||||||
[[localize('ui.dialogs.more_info_control.last_updated')]]:
|
|
||||||
<ha-relative-time
|
|
||||||
hass="[[hass]]"
|
|
||||||
datetime="[[stateObj.last_updated]]"
|
|
||||||
></ha-relative-time>
|
|
||||||
</paper-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[!inDialog]]">
|
|
||||||
<div class="extra-info"><slot> </slot></div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
stateObj: Object,
|
|
||||||
inDialog: {
|
|
||||||
type: Boolean,
|
|
||||||
value: () => false,
|
|
||||||
},
|
|
||||||
rtl: {
|
|
||||||
type: Boolean,
|
|
||||||
reflectToAttribute: true,
|
|
||||||
computed: "computeRTL(hass)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeStateName(stateObj) {
|
|
||||||
return computeStateName(stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeRTL(hass) {
|
|
||||||
return computeRTL(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("state-info", StateInfo);
|
|
158
src/components/entity/state-info.ts
Normal file
158
src/components/entity/state-info.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
import "../ha-relative-time";
|
||||||
|
import "./state-badge";
|
||||||
|
|
||||||
|
@customElement("state-info")
|
||||||
|
class StateInfo extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public inDialog = false;
|
||||||
|
|
||||||
|
// property used only in css
|
||||||
|
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.hass || !this.stateObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<state-badge
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
.stateColor=${true}
|
||||||
|
></state-badge>
|
||||||
|
<div class="info">
|
||||||
|
<div class="name" .inDialog=${this.inDialog}>
|
||||||
|
${computeStateName(this.stateObj)}
|
||||||
|
</div>
|
||||||
|
${this.inDialog
|
||||||
|
? html`<div class="time-ago">
|
||||||
|
<ha-relative-time
|
||||||
|
id="last_changed"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
></ha-relative-time>
|
||||||
|
<paper-tooltip animation-delay="0" for="last_changed">
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="column-name">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.last_changed"
|
||||||
|
)}:
|
||||||
|
</span>
|
||||||
|
<ha-relative-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
></ha-relative-time>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.last_updated"
|
||||||
|
)}:
|
||||||
|
</span>
|
||||||
|
<ha-relative-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${this.stateObj.last_updated}
|
||||||
|
></ha-relative-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</paper-tooltip>
|
||||||
|
</div>`
|
||||||
|
: html`<div class="extra-info"><slot> </slot></div>`}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
if (!changedProps.has("hass")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||||
|
this.rtl = computeRTL(this.hass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
@apply --paper-font-body1;
|
||||||
|
min-width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
state-badge {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([rtl]) state-badge {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin-left: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([rtl]) .info {
|
||||||
|
margin-right: 56px;
|
||||||
|
margin-left: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
@apply --paper-font-common-nowrap;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name[in-dialog],
|
||||||
|
:host([secondary-line]) .name {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-ago,
|
||||||
|
.extra-info,
|
||||||
|
.extra-info > * {
|
||||||
|
@apply --paper-font-common-nowrap;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: no-wrap;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0 2px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:last-child {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"state-info": StateInfo;
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,9 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { until } from "lit-html/directives/until";
|
import { until } from "lit-html/directives/until";
|
||||||
import hassAttributeUtil from "../util/hass-attributes-util";
|
import hassAttributeUtil, {
|
||||||
|
formatAttributeName,
|
||||||
|
} from "../util/hass-attributes-util";
|
||||||
|
|
||||||
let jsYamlPromise: Promise<typeof import("js-yaml")>;
|
let jsYamlPromise: Promise<typeof import("js-yaml")>;
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ class HaAttributes extends LitElement {
|
|||||||
(attribute) => html`
|
(attribute) => html`
|
||||||
<div class="data-entry">
|
<div class="data-entry">
|
||||||
<div class="key">
|
<div class="key">
|
||||||
${attribute.replace(/_/g, " ").replace("id", "ID")}
|
${formatAttributeName(attribute)}
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
${this.formatAttribute(attribute)}
|
${this.formatAttribute(attribute)}
|
||||||
@@ -61,15 +63,16 @@ class HaAttributes extends LitElement {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.data-entry .value {
|
.data-entry .value {
|
||||||
max-width: 200px;
|
max-width: 50%;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
.key:first-letter {
|
.key {
|
||||||
text-transform: capitalize;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
.attribution {
|
.attribution {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
text-align: right;
|
text-align: center;
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
@@ -50,9 +50,12 @@ export class HaCard extends LitElement {
|
|||||||
font-family: var(--ha-card-header-font-family, inherit);
|
font-family: var(--ha-card-header-font-family, inherit);
|
||||||
font-size: var(--ha-card-header-font-size, 24px);
|
font-size: var(--ha-card-header-font-size, 24px);
|
||||||
letter-spacing: -0.012em;
|
letter-spacing: -0.012em;
|
||||||
line-height: 32px;
|
line-height: 48px;
|
||||||
padding: 24px 16px 16px;
|
padding: 12px 16px 16px;
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-block-start: 0px;
|
||||||
|
margin-block-end: 0px;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host ::slotted(.card-content:not(:first-child)),
|
:host ::slotted(.card-content:not(:first-child)),
|
||||||
@@ -75,7 +78,7 @@ export class HaCard extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.header
|
${this.header
|
||||||
? html` <div class="card-header">${this.header}</div> `
|
? html`<h1 class="card-header">${this.header}</h1>`
|
||||||
: html``}
|
: html``}
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
`;
|
`;
|
||||||
|
@@ -11,7 +11,7 @@ export class HaCircularProgress extends CircularProgress {
|
|||||||
public alt = "Loading";
|
public alt = "Loading";
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public size: "small" | "medium" | "large" = "medium";
|
public size: "tiny" | "small" | "medium" | "large" = "medium";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public set density(_) {
|
public set density(_) {
|
||||||
@@ -20,6 +20,8 @@ export class HaCircularProgress extends CircularProgress {
|
|||||||
|
|
||||||
public get density() {
|
public get density() {
|
||||||
switch (this.size) {
|
switch (this.size) {
|
||||||
|
case "tiny":
|
||||||
|
return -8;
|
||||||
case "small":
|
case "small":
|
||||||
return -5;
|
return -5;
|
||||||
case "medium":
|
case "medium":
|
||||||
|
@@ -1,131 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
|
||||||
import LocalizeMixin from "../mixins/localize-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class HaClimateState extends LocalizeMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.current {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-label {
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unit {
|
|
||||||
display: inline-block;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="target">
|
|
||||||
<template is="dom-if" if="[[_hasKnownState(stateObj.state)]]">
|
|
||||||
<span class="state-label">
|
|
||||||
[[_localizeState(localize, stateObj)]]
|
|
||||||
<template is="dom-if" if="[[_renderPreset(stateObj.attributes)]]">
|
|
||||||
- [[_localizePreset(localize, stateObj.attributes.preset_mode)]]
|
|
||||||
</template>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<div class="unit">[[computeTarget(hass, stateObj)]]</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[currentStatus]]">
|
|
||||||
<div class="current">
|
|
||||||
[[localize('ui.card.climate.currently')]]:
|
|
||||||
<div class="unit">[[currentStatus]]</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
stateObj: Object,
|
|
||||||
currentStatus: {
|
|
||||||
type: String,
|
|
||||||
computed: "computeCurrentStatus(hass, stateObj)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeCurrentStatus(hass, stateObj) {
|
|
||||||
if (!hass || !stateObj) return null;
|
|
||||||
if (stateObj.attributes.current_temperature != null) {
|
|
||||||
return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`;
|
|
||||||
}
|
|
||||||
if (stateObj.attributes.current_humidity != null) {
|
|
||||||
return `${stateObj.attributes.current_humidity} %`;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeTarget(hass, stateObj) {
|
|
||||||
if (!hass || !stateObj) return null;
|
|
||||||
// We're using "!= null" on purpose so that we match both null and undefined.
|
|
||||||
if (
|
|
||||||
stateObj.attributes.target_temp_low != null &&
|
|
||||||
stateObj.attributes.target_temp_high != null
|
|
||||||
) {
|
|
||||||
return `${stateObj.attributes.target_temp_low}-${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
|
|
||||||
}
|
|
||||||
if (stateObj.attributes.temperature != null) {
|
|
||||||
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
stateObj.attributes.target_humidity_low != null &&
|
|
||||||
stateObj.attributes.target_humidity_high != null
|
|
||||||
) {
|
|
||||||
return `${stateObj.attributes.target_humidity_low}-${stateObj.attributes.target_humidity_high}%`;
|
|
||||||
}
|
|
||||||
if (stateObj.attributes.humidity != null) {
|
|
||||||
return `${stateObj.attributes.humidity} %`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasKnownState(state) {
|
|
||||||
return state !== "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
_localizeState(localize, stateObj) {
|
|
||||||
const stateString = localize(`component.climate.state._.${stateObj.state}`);
|
|
||||||
return stateObj.attributes.hvac_action
|
|
||||||
? `${localize(
|
|
||||||
`state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
|
|
||||||
)} (${stateString})`
|
|
||||||
: stateString;
|
|
||||||
}
|
|
||||||
|
|
||||||
_localizePreset(localize, preset) {
|
|
||||||
return localize(`state_attributes.climate.preset_mode.${preset}`) || preset;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderPreset(attributes) {
|
|
||||||
return (
|
|
||||||
attributes.preset_mode && attributes.preset_mode !== CLIMATE_PRESET_NONE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("ha-climate-state", HaClimateState);
|
|
139
src/components/ha-climate-state.ts
Normal file
139
src/components/ha-climate-state.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
@customElement("ha-climate-state")
|
||||||
|
class HaClimateState extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj!: HassEntity;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const currentStatus = this._computeCurrentStatus();
|
||||||
|
|
||||||
|
return html`<div class="target">
|
||||||
|
${this.stateObj.state !== "unknown"
|
||||||
|
? html`<span class="state-label">
|
||||||
|
${this._localizeState()}
|
||||||
|
${this.stateObj.attributes.preset_mode &&
|
||||||
|
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||||
|
? html`-
|
||||||
|
${this.hass.localize(
|
||||||
|
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||||
|
) || this.stateObj.attributes.preset_mode}`
|
||||||
|
: ""}
|
||||||
|
</span>`
|
||||||
|
: ""}
|
||||||
|
<div class="unit">${this._computeTarget()}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${currentStatus
|
||||||
|
? html`<div class="current">
|
||||||
|
${this.hass.localize("ui.card.climate.currently")}:
|
||||||
|
<div class="unit">${currentStatus}</div>
|
||||||
|
</div>`
|
||||||
|
: ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeCurrentStatus(): string | undefined {
|
||||||
|
if (!this.hass || !this.stateObj) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stateObj.attributes.current_temperature != null) {
|
||||||
|
return `${this.stateObj.attributes.current_temperature} ${this.hass.config.unit_system.temperature}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stateObj.attributes.current_humidity != null) {
|
||||||
|
return `${this.stateObj.attributes.current_humidity} %`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeTarget(): string {
|
||||||
|
if (!this.hass || !this.stateObj) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.stateObj.attributes.target_temp_low != null &&
|
||||||
|
this.stateObj.attributes.target_temp_high != null
|
||||||
|
) {
|
||||||
|
return `${this.stateObj.attributes.target_temp_low}-${this.stateObj.attributes.target_temp_high} ${this.hass.config.unit_system.temperature}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stateObj.attributes.temperature != null) {
|
||||||
|
return `${this.stateObj.attributes.temperature} ${this.hass.config.unit_system.temperature}`;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.stateObj.attributes.target_humidity_low != null &&
|
||||||
|
this.stateObj.attributes.target_humidity_high != null
|
||||||
|
) {
|
||||||
|
return `${this.stateObj.attributes.target_humidity_low}-${this.stateObj.attributes.target_humidity_high}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stateObj.attributes.humidity != null) {
|
||||||
|
return `${this.stateObj.attributes.humidity} %`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _localizeState(): string {
|
||||||
|
const stateString = this.hass.localize(
|
||||||
|
`component.climate.state._.${this.stateObj.state}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.stateObj.attributes.hvac_action
|
||||||
|
? `${this.hass.localize(
|
||||||
|
`state_attributes.climate.hvac_action.${this.stateObj.attributes.hvac_action}`
|
||||||
|
)} (${stateString})`
|
||||||
|
: stateString;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-label {
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
display: inline-block;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-climate-state": HaClimateState;
|
||||||
|
}
|
||||||
|
}
|
@@ -81,6 +81,7 @@ export class HaCodeEditor extends UpdatingElement {
|
|||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues): void {
|
protected firstUpdated(changedProps: PropertyValues): void {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
this._blockKeyboardShortcuts();
|
||||||
this._load();
|
this._load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +233,10 @@ export class HaCodeEditor extends UpdatingElement {
|
|||||||
this.codemirror!.on("changes", () => this._onChange());
|
this.codemirror!.on("changes", () => this._onChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _blockKeyboardShortcuts() {
|
||||||
|
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
||||||
|
}
|
||||||
|
|
||||||
private _onChange(): void {
|
private _onChange(): void {
|
||||||
const newValue = this.value;
|
const newValue = this.value;
|
||||||
if (newValue === this._value) {
|
if (newValue === this._value) {
|
||||||
|
@@ -1,126 +0,0 @@
|
|||||||
import "./ha-icon-button";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
|
||||||
import CoverEntity from "../util/cover-model";
|
|
||||||
|
|
||||||
class HaCoverControls extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
.state {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
[invisible] {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="state">
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Open cover"
|
|
||||||
icon="[[computeOpenIcon(stateObj)]]"
|
|
||||||
on-click="onOpenTap"
|
|
||||||
invisible$="[[!entityObj.supportsOpen]]"
|
|
||||||
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Stop the cover from moving"
|
|
||||||
icon="hass:stop"
|
|
||||||
on-click="onStopTap"
|
|
||||||
invisible$="[[!entityObj.supportsStop]]"
|
|
||||||
disabled="[[computeStopDisabled(stateObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Close cover"
|
|
||||||
icon="[[computeCloseIcon(stateObj)]]"
|
|
||||||
on-click="onCloseTap"
|
|
||||||
invisible$="[[!entityObj.supportsClose]]"
|
|
||||||
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
entityObj: {
|
|
||||||
type: Object,
|
|
||||||
computed: "computeEntityObj(hass, stateObj)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeEntityObj(hass, stateObj) {
|
|
||||||
return new CoverEntity(hass, stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeOpenIcon(stateObj) {
|
|
||||||
switch (stateObj.attributes.device_class) {
|
|
||||||
case "awning":
|
|
||||||
case "door":
|
|
||||||
case "gate":
|
|
||||||
return "hass:arrow-expand-horizontal";
|
|
||||||
default:
|
|
||||||
return "hass:arrow-up";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
computeCloseIcon(stateObj) {
|
|
||||||
switch (stateObj.attributes.device_class) {
|
|
||||||
case "awning":
|
|
||||||
case "door":
|
|
||||||
case "gate":
|
|
||||||
return "hass:arrow-collapse-horizontal";
|
|
||||||
default:
|
|
||||||
return "hass:arrow-down";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
computeStopDisabled(stateObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeOpenDisabled(stateObj, entityObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
return (entityObj.isFullyOpen || entityObj.isOpening) && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeClosedDisabled(stateObj, entityObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
return (entityObj.isFullyClosed || entityObj.isClosing) && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpenTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.openCover();
|
|
||||||
}
|
|
||||||
|
|
||||||
onCloseTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.closeCover();
|
|
||||||
}
|
|
||||||
|
|
||||||
onStopTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.stopCover();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-cover-controls", HaCoverControls);
|
|
135
src/components/ha-cover-controls.ts
Normal file
135
src/components/ha-cover-controls.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
internalProperty,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { UNAVAILABLE } from "../data/entity";
|
||||||
|
import CoverEntity from "../util/cover-model";
|
||||||
|
|
||||||
|
import "./ha-icon-button";
|
||||||
|
import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
|
||||||
|
|
||||||
|
@customElement("ha-cover-controls")
|
||||||
|
class HaCoverControls extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj!: HassEntity;
|
||||||
|
|
||||||
|
@internalProperty() private _entityObj?: CoverEntity;
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has("stateObj")) {
|
||||||
|
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._entityObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="state">
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !this._entityObj.supportsOpen,
|
||||||
|
})}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.open_cover"
|
||||||
|
)}
|
||||||
|
.icon=${computeOpenIcon(this.stateObj)}
|
||||||
|
@click=${this._onOpenTap}
|
||||||
|
.disabled=${this._computeOpenDisabled()}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.stop_cover"
|
||||||
|
)}
|
||||||
|
icon="hass:stop"
|
||||||
|
@click=${this._onStopTap}
|
||||||
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !this._entityObj.supportsClose,
|
||||||
|
})}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.close_cover"
|
||||||
|
)}
|
||||||
|
.icon=${computeCloseIcon(this.stateObj)}
|
||||||
|
@click=${this._onCloseTap}
|
||||||
|
.disabled=${this._computeClosedDisabled()}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeOpenDisabled(): boolean {
|
||||||
|
if (this.stateObj.state === UNAVAILABLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
|
return (
|
||||||
|
(this._entityObj.isFullyOpen || this._entityObj.isOpening) &&
|
||||||
|
!assumedState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeClosedDisabled(): boolean {
|
||||||
|
if (this.stateObj.state === UNAVAILABLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
|
return (
|
||||||
|
(this._entityObj.isFullyClosed || this._entityObj.isClosing) &&
|
||||||
|
!assumedState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onOpenTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.openCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCloseTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.closeCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStopTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.stopCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.state {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-cover-controls": HaCoverControls;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,106 +0,0 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import "./ha-icon-button";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
|
||||||
import CoverEntity from "../util/cover-model";
|
|
||||||
|
|
||||||
class HaCoverTiltControls extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex"></style>
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
[invisible] {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Open cover tilt"
|
|
||||||
icon="hass:arrow-top-right"
|
|
||||||
on-click="onOpenTiltTap"
|
|
||||||
title="Open tilt"
|
|
||||||
invisible$="[[!entityObj.supportsOpenTilt]]"
|
|
||||||
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Stop cover from moving"
|
|
||||||
icon="hass:stop"
|
|
||||||
on-click="onStopTiltTap"
|
|
||||||
invisible$="[[!entityObj.supportsStopTilt]]"
|
|
||||||
disabled="[[computeStopDisabled(stateObj)]]"
|
|
||||||
title="Stop tilt"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Close cover tilt"
|
|
||||||
icon="hass:arrow-bottom-left"
|
|
||||||
on-click="onCloseTiltTap"
|
|
||||||
title="Close tilt"
|
|
||||||
invisible$="[[!entityObj.supportsCloseTilt]]"
|
|
||||||
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
entityObj: {
|
|
||||||
type: Object,
|
|
||||||
computed: "computeEntityObj(hass, stateObj)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeEntityObj(hass, stateObj) {
|
|
||||||
return new CoverEntity(hass, stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeStopDisabled(stateObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeOpenDisabled(stateObj, entityObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
return entityObj.isFullyOpenTilt && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeClosedDisabled(stateObj, entityObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
return entityObj.isFullyClosedTilt && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpenTiltTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.openCoverTilt();
|
|
||||||
}
|
|
||||||
|
|
||||||
onCloseTiltTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.closeCoverTilt();
|
|
||||||
}
|
|
||||||
|
|
||||||
onStopTiltTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.stopCoverTilt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-cover-tilt-controls", HaCoverTiltControls);
|
|
122
src/components/ha-cover-tilt-controls.ts
Normal file
122
src/components/ha-cover-tilt-controls.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
internalProperty,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
|
import { UNAVAILABLE } from "../data/entity";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import CoverEntity from "../util/cover-model";
|
||||||
|
|
||||||
|
import "./ha-icon-button";
|
||||||
|
|
||||||
|
@customElement("ha-cover-tilt-controls")
|
||||||
|
class HaCoverTiltControls extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) stateObj!: HassEntity;
|
||||||
|
|
||||||
|
@internalProperty() private _entityObj?: CoverEntity;
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has("stateObj")) {
|
||||||
|
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._entityObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html` <ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
invisible: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.open_tilt_cover"
|
||||||
|
)}
|
||||||
|
icon="hass:arrow-top-right"
|
||||||
|
@click=${this._onOpenTiltTap}
|
||||||
|
.disabled=${this._computeOpenDisabled()}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
invisible: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
||||||
|
icon="hass:stop"
|
||||||
|
@click=${this._onStopTiltTap}
|
||||||
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
invisible: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.open_tilt_cover"
|
||||||
|
)}
|
||||||
|
icon="hass:arrow-bottom-left"
|
||||||
|
@click=${this._onCloseTiltTap}
|
||||||
|
.disabled=${this._computeClosedDisabled()}
|
||||||
|
></ha-icon-button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeOpenDisabled(): boolean {
|
||||||
|
if (this.stateObj.state === UNAVAILABLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
|
return this._entityObj.isFullyOpenTilt && !assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeClosedDisabled(): boolean {
|
||||||
|
if (this.stateObj.state === UNAVAILABLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
|
return this._entityObj.isFullyClosedTilt && !assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onOpenTiltTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.openCoverTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCloseTiltTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.closeCoverTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStopTiltTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.stopCoverTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-cover-tilt-controls": HaCoverTiltControls;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,127 +1,86 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit-element";
|
|
||||||
|
|
||||||
@customElement("ha-date-input")
|
const VaadinDatePicker = customElements.get("vaadin-date-picker");
|
||||||
export class HaDateInput extends LitElement {
|
|
||||||
@property() public year?: string;
|
|
||||||
|
|
||||||
@property() public month?: string;
|
const documentContainer = document.createElement("template");
|
||||||
|
documentContainer.setAttribute("style", "display: none;");
|
||||||
|
documentContainer.innerHTML = `
|
||||||
|
<dom-module id="ha-date-input-styles" theme-for="vaadin-text-field">
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
[part="input-field"] {
|
||||||
|
top: 2px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
`;
|
||||||
|
document.head.appendChild(documentContainer.content);
|
||||||
|
|
||||||
@property() public day?: string;
|
export class HaDateInput extends VaadinDatePicker {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
this.i18n.formatDate = this._formatISODate;
|
||||||
|
this.i18n.parseDate = this._parseISODate;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles() {
|
ready() {
|
||||||
return css`
|
super.ready();
|
||||||
|
const styleEl = document.createElement("style");
|
||||||
|
styleEl.innerHTML = `
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
width: 12ex;
|
||||||
font-family: var(--paper-font-common-base_-_font-family);
|
margin-top: -6px;
|
||||||
-webkit-font-smoothing: var(
|
--material-body-font-size: 16px;
|
||||||
--paper-font-common-base_-_-webkit-font-smoothing
|
--_material-text-field-input-line-background-color: var(--primary-text-color);
|
||||||
);
|
--_material-text-field-input-line-opacity: 1;
|
||||||
}
|
--material-primary-color: var(--primary-text-color);
|
||||||
|
|
||||||
paper-input {
|
|
||||||
width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
--paper-input-container-shared-input-style_-_-webkit-appearance: textfield;
|
|
||||||
--paper-input-container-input_-_-moz-appearance: textfield;
|
|
||||||
--paper-input-container-shared-input-style_-_appearance: textfield;
|
|
||||||
--paper-input-container-input-webkit-spinner_-_-webkit-appearance: none;
|
|
||||||
--paper-input-container-input-webkit-spinner_-_margin: 0;
|
|
||||||
--paper-input-container-input-webkit-spinner_-_display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-input#year {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-input-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
this.shadowRoot.appendChild(styleEl);
|
||||||
|
this._inputElement.querySelector("[part='toggle-button']").style.display =
|
||||||
|
"none";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
private _formatISODate(d) {
|
||||||
return html`
|
return [
|
||||||
<div class="date-input-wrap">
|
("0000" + String(d.year)).slice(-4),
|
||||||
<paper-input
|
("0" + String(d.month + 1)).slice(-2),
|
||||||
id="year"
|
("0" + String(d.day)).slice(-2),
|
||||||
type="number"
|
].join("-");
|
||||||
.value=${this.year}
|
|
||||||
@change=${this._formatYear}
|
|
||||||
maxlength="4"
|
|
||||||
max="9999"
|
|
||||||
min="0"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
<span suffix="" slot="suffix">-</span>
|
|
||||||
</paper-input>
|
|
||||||
<paper-input
|
|
||||||
id="month"
|
|
||||||
type="number"
|
|
||||||
.value=${this.month}
|
|
||||||
@change=${this._formatMonth}
|
|
||||||
maxlength="2"
|
|
||||||
max="12"
|
|
||||||
min="1"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
<span suffix="" slot="suffix">-</span>
|
|
||||||
</paper-input>
|
|
||||||
<paper-input
|
|
||||||
id="day"
|
|
||||||
type="number"
|
|
||||||
.value=${this.day}
|
|
||||||
@change=${this._formatDay}
|
|
||||||
maxlength="2"
|
|
||||||
max="31"
|
|
||||||
min="1"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
</paper-input>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _formatYear() {
|
private _parseISODate(text) {
|
||||||
const yearElement = this.shadowRoot!.getElementById(
|
const parts = text.split("-");
|
||||||
"year"
|
const today = new Date();
|
||||||
) as PaperInputElement;
|
let date;
|
||||||
this.year = yearElement.value!;
|
let month = today.getMonth();
|
||||||
}
|
let year = today.getFullYear();
|
||||||
|
if (parts.length === 3) {
|
||||||
|
year = parseInt(parts[0]);
|
||||||
|
if (parts[0].length < 3 && year >= 0) {
|
||||||
|
year += year < 50 ? 2000 : 1900;
|
||||||
|
}
|
||||||
|
month = parseInt(parts[1]) - 1;
|
||||||
|
date = parseInt(parts[2]);
|
||||||
|
} else if (parts.length === 2) {
|
||||||
|
month = parseInt(parts[0]) - 1;
|
||||||
|
date = parseInt(parts[1]);
|
||||||
|
} else if (parts.length === 1) {
|
||||||
|
date = parseInt(parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private _formatMonth() {
|
if (date !== undefined) {
|
||||||
const monthElement = this.shadowRoot!.getElementById(
|
return { day: date, month, year };
|
||||||
"month"
|
}
|
||||||
) as PaperInputElement;
|
return undefined;
|
||||||
this.month = ("0" + monthElement.value!).slice(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _formatDay() {
|
|
||||||
const dayElement = this.shadowRoot!.getElementById(
|
|
||||||
"day"
|
|
||||||
) as PaperInputElement;
|
|
||||||
this.day = ("0" + dayElement.value!).slice(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get value() {
|
|
||||||
return `${this.year}-${this.month}-${this.day}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customElements.define("ha-date-input", HaDateInput as any);
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-date-input": HaDateInput;
|
"ha-date-input": HaDateInput;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user