Compare commits

..

291 Commits

Author SHA1 Message Date
Donnie
72bf0c918a Update src/dialogs/template-editor/ha-template-editor.ts
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-17 16:07:54 -08:00
Donnie
419f5d13bf Update src/dialogs/template-editor/ha-template-editor.ts
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-17 16:07:48 -08:00
Donnie
16549b3404 Add ability to launch small version of template editor in a dialog using shortcut 2020-11-17 11:41:29 -08:00
Paulus Schoutsen
cbddebeaa8 Allow dismissing (#7712) 2020-11-17 16:14:37 +01:00
Philip Allgaier
bbe4c95109 Display entity ID for read-only entities (#7690) 2020-11-17 16:08:47 +01:00
Philip Allgaier
4c6f9f0dd8 Fix empty entity pickers for "Time" trigger and condition (#7689) 2020-11-17 15:51:18 +01:00
Philip Allgaier
90f7dba793 Use proper night time icon consistently for sun integration (#7681) 2020-11-17 15:48:54 +01:00
Philip Allgaier
7c492338a2 Improve gallery hui-gauge-card (#7682) 2020-11-17 15:46:33 +01:00
Philip Allgaier
530f494df8 Improve gallery hui-light-card (#7684) 2020-11-17 15:46:00 +01:00
Joakim Sørensen
8fd1f35c59 Move data_entry_flow_progressed event subscriber (#7700) 2020-11-17 15:12:45 +01:00
Joakim Sørensen
af1518e924 Use stepid title if present for progress flow (#7710) 2020-11-17 14:33:13 +01:00
Thomas Lovén
473e381d75 Fix date picker row alignment (#7704) 2020-11-17 09:43:25 +01:00
Paulus Schoutsen
7d3acc747d Guard passing invalid tag to customElements.whenDefined (#7696) 2020-11-17 09:40:35 +01:00
HomeAssistant Azure
bf7424a67c [ci skip] Translation update 2020-11-17 00:32:25 +00:00
Joakim Sørensen
a856337eae Devcontainer (#7697)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-11-16 16:53:47 +01:00
Bram Kragten
6cf47ba4eb Fix number format in state-card-display (#7703) 2020-11-16 12:45:52 +01:00
Donnie
3b7a189708 Quick Bar should only capitalize letters in Command Palette (#7671) 2020-11-16 12:45:20 +01:00
Philip Allgaier
79c542b76a Add missing 1px to prevent slider pin cutoff (#7657) 2020-11-16 12:44:58 +01:00
Paulus Schoutsen
e37b7bd73f Cleanup (#7702) 2020-11-16 11:43:25 +01:00
HomeAssistant Azure
d6f3c34b33 [ci skip] Translation update 2020-11-16 00:32:42 +00:00
Paulus Schoutsen
bc5cb46e7d Add missing outFiles 2020-11-15 15:37:43 +00:00
Paulus Schoutsen
c7b747c4fa Add debug launch conf for VS Code (#7683) 2020-11-15 16:36:48 +01:00
HomeAssistant Azure
d3c51d7acd [ci skip] Translation update 2020-11-15 00:32:20 +00:00
HomeAssistant Azure
b6881d797c [ci skip] Translation update 2020-11-14 00:32:35 +00:00
Joakim Sørensen
b9f802939c Add a github option when copying from system health (#7663)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-13 18:20:24 +01:00
HomeAssistant Azure
6558c2c065 [ci skip] Translation update 2020-11-13 00:32:24 +00:00
Ian Richardson
37a089c868 Convert ha-cover-controls to TypeScript/LitElement (#7521)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-11-12 15:29:16 +01:00
Ian Richardson
f68eff6bb3 Fix state-info icon color and convert to TypeScript/LitElement (#7664) 2020-11-12 15:06:45 +01:00
HomeAssistant Azure
88a525f1a7 [ci skip] Translation update 2020-11-12 00:32:16 +00:00
Bram Kragten
34fddd5940 Merge error 2020-11-11 15:27:33 +01:00
Bram Kragten
0e5d6fe8d8 Merge branch 'master' into dev 2020-11-11 15:26:26 +01:00
Bram Kragten
e1342a0d9d Bumped version to 20201111.0 2020-11-11 15:17:21 +01:00
Joakim Sørensen
0cc2d3aaa7 Check for integration before loading logo (#7653) 2020-11-11 15:16:52 +01:00
Bram Kragten
67814505b3 Fix areas devices picker (#7652) 2020-11-11 14:59:26 +01:00
Bram Kragten
bae29c6d62 Move last changed / last updated out of state table (#7649) 2020-11-11 14:42:27 +01:00
Bram Kragten
a0e67d4c03 Fix fabs (#7650) 2020-11-11 14:13:40 +01:00
Bram Kragten
131bc5fbf7 Fix pin of labeled slider cutoff (#7651) 2020-11-11 14:12:49 +01:00
Bram Kragten
051218e29b Fix view height in edit mode (#7646) 2020-11-11 14:08:53 +01:00
Philip Allgaier
6ace8307d8 Always show "off" button if supported by player (#7389) 2020-11-11 14:00:53 +01:00
Bram Kragten
e84bef44b7 Guard for undefined hass (#7647) 2020-11-11 13:55:45 +01:00
Bram Kragten
3186d762f2 Fix height of tabs subpage (#7648) 2020-11-11 13:10:07 +01:00
Bram Kragten
c97a3b0a56 Fixes for logbook card (#7645) 2020-11-11 13:08:03 +01:00
Zack Barett
78f1bb3b91 Logbook Card (#6976) 2020-11-11 11:49:56 +01:00
Joakim Sørensen
67707fbc90 Don't block system_health if one value is null (#7644) 2020-11-11 11:49:10 +01:00
Donnie
2a57ffa615 Add navigation commands to quick bar commands (#7380)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-11 11:46:53 +01:00
Ryan Meek
216fce74f8 Header/sidebar sizing (#7470)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-11 10:47:06 +01:00
HomeAssistant Azure
6cd3e6652a [ci skip] Translation update 2020-11-11 00:33:03 +00:00
Paulus Schoutsen
fe7d79cee6 Load system health translations from backend (#7643) 2020-11-10 23:44:59 +01:00
Nico Hirsch
2f4e7b388b Add dialog backdrop blur theme variable (#7635)
* Add dialog backdrop blur theme var

* Update src/components/ha-dialog.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Add backdrop-filter to iron-overlay-backdrop

* Revert change from opacity to rgba

* Add comment "for paper-dialog"

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-10 18:01:33 +01:00
kg333
2e289cd152 Add fix to more-info for media players without play/pause state (#7608)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-10 18:00:44 +01:00
Sören Beye
21a3dcf06c Further brands-url cleanup (#7640) 2020-11-10 17:59:48 +01:00
Philip Allgaier
7f56add914 Show user friendly attribute names in picker (#7337)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-10 15:25:53 +01:00
Sören Beye
88701c6167 Refactor brands url to single location (#7613) 2020-11-10 15:25:06 +01:00
uvjustin
e4ce6117a1 Get regular playlist url properly in ha-hls-player (#7417) 2020-11-10 14:41:14 +01:00
Josh McCarty
cec2a61bdf Use ha-dialog for device-registry-details-dialog (#7500) 2020-11-10 12:22:32 +01:00
Philip Allgaier
8275ac5853 Ensure "next" and "prev" buttons always have ARIA label (#7588)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-10 11:39:09 +01:00
Philip Allgaier
b7bcf97365 Minor visual QB command tweaks (#7590) 2020-11-10 11:38:44 +01:00
Nathan Orick
fa28b480f1 Add button to duplicate script (#7511)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-10 10:16:42 +01:00
HomeAssistant Azure
4bb95b7396 [ci skip] Translation update 2020-11-10 00:32:22 +00:00
Philip Allgaier
5a9bd73e8b Use clearer dialog button texts + various translation improvements (#7584) 2020-11-09 23:41:22 +01:00
Thomas Lovén
4fe0276914 Replace date picker for entities card (#6899) 2020-11-09 23:27:21 +01:00
Kendell R
5e8bda55b4 Make PR template more discussion-friendly (#7622) 2020-11-09 23:05:44 +01:00
Joakim Sørensen
d09c4898c1 Remove logs and fix dark theme during onboarding restore (#7579) 2020-11-09 22:50:16 +01:00
Joakim Sørensen
6ae67ed299 Add flow for "progress" step (#7592) 2020-11-09 22:45:37 +01:00
Zack Barett
32ff166a74 Entities Card: Add Header & Footer Editor (#6751) 2020-11-09 22:41:59 +01:00
Paulus Schoutsen
8feae04281 Add link to conference (#7636) 2020-11-09 22:41:05 +01:00
Erik Montnemery
129f9c147b Improve user experience when enabling a disabled entity (#7580) 2020-11-09 19:48:24 +01:00
Ian Richardson
6e336dd207 convert ha-cover-tilt-controls to TypeScript/LitElement (#7542) 2020-11-09 18:26:05 +01:00
Paulus Schoutsen
161561c48a Support system health streaming (#7593) 2020-11-09 16:11:01 +01:00
Franck Nijhof
c162e84383 Replace lock bot with GitHub Action (#7633) 2020-11-09 12:49:42 +01:00
Franck Nijhof
dc8d80a6e5 Replace stale bot with GitHub Action (#7634) 2020-11-09 12:46:19 +01:00
HomeAssistant Azure
293f67968c [ci skip] Translation update 2020-11-09 00:32:31 +00:00
HomeAssistant Azure
4dcf26236e [ci skip] Translation update 2020-11-08 00:32:28 +00:00
Philip Allgaier
a0e8d69243 Add "www" to generated tag QR code (#7577)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-07 17:27:57 +01:00
Philip Allgaier
33cd9bf516 Ensure all <mwc-fab> set a label for ARIA (#7587) 2020-11-07 17:27:34 +01:00
HomeAssistant Azure
0132797f2f [ci skip] Translation update 2020-11-07 00:32:20 +00:00
Paulus Schoutsen
7e2db0aa4e Add ingress session validation (#7610) 2020-11-06 23:01:29 +01:00
HomeAssistant Azure
cc1d50491b [ci skip] Translation update 2020-11-06 00:32:18 +00:00
Adam Ernst
461b86a04b Fix race condition in translation loading (#7597) 2020-11-05 18:47:09 +01:00
Ian Richardson
9a3a7c28f4 Use shopping list card in panel (#7519) 2020-11-05 16:17:42 +01:00
Philip Allgaier
1c9d0200ca Add "last_changed" and "last_updated" to dev tools state view (#7375) 2020-11-05 16:15:50 +01:00
Philip Allgaier
0037cd2e69 Make thingtalk dialogs translatable (#7574) 2020-11-05 16:15:16 +01:00
HomeAssistant Azure
028ae061da [ci skip] Translation update 2020-11-05 00:32:18 +00:00
HomeAssistant Azure
2e47763ecc [ci skip] Translation update 2020-11-04 00:32:29 +00:00
HomeAssistant Azure
924e4a45d0 [ci skip] Translation update 2020-11-03 00:32:27 +00:00
Bram Kragten
8361b9553b Fix polyfill check (#7575) 2020-11-02 22:06:49 +01:00
Zack Barett
e52be20fba Grid Card: Fix Card Picker (#7562)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-02 10:47:24 +01:00
Bram Kragten
da12233ade Add dark mode toggle to gallery cards (#7532) 2020-11-02 10:46:52 +01:00
HomeAssistant Azure
57500f6c97 [ci skip] Translation update 2020-11-02 00:32:22 +00:00
Ryan Meek
199e17d0b1 Use paper-tabs while in edit mode (#7563)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-01 22:04:52 +01:00
Ryan Meek
3b91343082 Dark mode header color (#7514)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-11-01 22:04:27 +01:00
HomeAssistant Azure
1753c9163c [ci skip] Translation update 2020-11-01 00:32:29 +00:00
HomeAssistant Azure
89e5953e89 [ci skip] Translation update 2020-10-31 00:33:15 +00:00
Ian Richardson
5bfd25c8c6 Convert ha-climate-state to TypeScript/LitElement (#7544)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-10-30 17:04:13 -05:00
Zack Barett
e555b24f50 Calendar: Adds an Update Size and Makes list view start today and adds in local for first day of week (#7541) 2020-10-30 17:03:02 -05:00
Josh McCarty
14db37459f Formats number state with selected language in compute_state_display (#7516) 2020-10-30 22:58:52 +01:00
Zack Barett
1d9779d47c Calendar Panel: Persist Calendars in Local Storage (#7540) 2020-10-30 11:28:19 -05:00
Rob McCann
3dedbc5457 Remove minification from translations to reduce build time by 1.5mins (#7552) 2020-10-30 15:36:52 +01:00
Zack Barett
facb3266c6 Media Browser: Fix error handling (#7538) 2020-10-30 14:55:30 +01:00
Thomas Lovén
01fe5dd2f7 Make sure rebuilt cards show up in panel views (#7535) 2020-10-30 14:48:39 +01:00
dklemm
9b22b1e499 Resolve #7533 Broken styling on stack card titles (#7534) 2020-10-30 14:47:09 +01:00
Ian Richardson
4bc8818145 Remove ha-state-icon (#7545) 2020-10-30 14:45:51 +01:00
Paulus Schoutsen
48ef8c86c2 Add unavailable entity to gauge demo (#7530) 2020-10-30 14:30:27 +01:00
Zack Barett
89f359a52f Calendar: Fix background to match ha-card (#7539) 2020-10-30 14:28:51 +01:00
HomeAssistant Azure
13b8160d74 [ci skip] Translation update 2020-10-30 00:32:19 +00:00
Paulus Schoutsen
f1c16d6674 Mock subscribe template (#7529) 2020-10-29 21:28:41 +01:00
Paulus Schoutsen
76a088e177 Only show header toggle for entities card if title (#7525) 2020-10-29 21:28:34 +01:00
Philip Allgaier
630d8c3bb6 Add last-updated to state info tooltip (#7445) 2020-10-29 20:08:44 +01:00
Bram Kragten
744efa30f2 Bumped version to 20201021.4 2020-10-29 18:33:22 +01:00
Erik Montnemery
bf4a94dc48 Add MQTT as ignorable discovery flow (#7527) 2020-10-29 18:32:19 +01:00
Bram Kragten
ce4ba2f6f1 Fix tooltip creating scrollbar history card (#7528) 2020-10-29 18:31:52 +01:00
Paulus Schoutsen
5b232b5d35 Fix glance card with header if parent does not set block (#7526) 2020-10-29 18:31:36 +01:00
Donnie
35151bbac7 Fix issue with toggles blocking dialog and dialog launching on mobile (#7506)
* Fix issue with some inputs blocking, or incorrectly allowing, keyboard shortcut activation

* Explicitly declare all input types that we can allow alphanumeric overrides

* Do not launch dialog in codemirror targets on mobile devices
2020-10-29 18:31:21 +01:00
Erik Montnemery
f0e959319e Add MQTT as ignorable discovery flow (#7527) 2020-10-29 18:30:20 +01:00
Bram Kragten
d0c4475724 Fix tooltip creating scrollbar history card (#7528) 2020-10-29 18:29:54 +01:00
Paulus Schoutsen
99935f1e59 Fix glance card with header if parent does not set block (#7526) 2020-10-29 18:29:20 +01:00
Paulus Schoutsen
fbb43821ba Add grid card to the gallery (#7524) 2020-10-29 17:56:26 +01:00
Donnie
c7f5c6c1d1 Fix issue with toggles blocking dialog and dialog launching on mobile (#7506)
* Fix issue with some inputs blocking, or incorrectly allowing, keyboard shortcut activation

* Explicitly declare all input types that we can allow alphanumeric overrides

* Do not launch dialog in codemirror targets on mobile devices
2020-10-29 09:18:47 -07:00
Paulus Schoutsen
d26f1fa371 Fix grid card size when square (#7520) 2020-10-29 14:21:49 +01:00
Paulus Schoutsen
c3718ff7dd Add Grid card (#7476) 2020-10-29 10:31:14 +01:00
HomeAssistant Azure
d63493a859 [ci skip] Translation update 2020-10-29 00:32:17 +00:00
Josh McCarty
a72183851a Use ha-dialog for dialog-area-registry-detail (#7508) 2020-10-28 16:00:39 +01:00
Nathan Orick
40b2387667 Allow pressing return to submit on area dialog (#7509) 2020-10-28 15:57:01 +01:00
HomeAssistant Azure
d814aa36a7 [ci skip] Translation update 2020-10-28 00:32:29 +00:00
Philip Allgaier
e37eebe4ad Get rid of the unwanted tooltip copying (final) (#7459) 2020-10-27 20:22:06 +01:00
Philip Allgaier
0baaaefdf8 Get rid of the unwanted tooltip copying (#7408) 2020-10-27 20:22:04 +01:00
Philip Allgaier
58a58906e7 Get rid of the unwanted tooltip copying (final) (#7459) 2020-10-27 20:20:15 +01:00
Bram Kragten
bec0d9b00e Bumped version to 20201021.3 2020-10-27 20:18:52 +01:00
Donnie
e6a4ab789b Add server restart/stop to quick bar command list (#7488) 2020-10-27 20:18:41 +01:00
Donnie
36c1d3230c Change Quick Bar shortcuts to "e" and "c" (#7496)
* Add toggle for disabling quick bar shortcuts

* Change shortcut from Ctrl+P and Ctrl+Shift+P to qe and qc (Quick Entity, Quick Command)

* Remove accidentally included code

* Use tinykeys for handling shortcuts

* Change shortcuts to e and c. And fix small typo.

* Change copy for toggle

* Rename hass property to be for generic shortcuts

* Minor tweaks to address review comments
2020-10-27 20:18:18 +01:00
Donnie
30466ec3fe Add toggle for disabling quick bar shortcuts (#7495)
* Add toggle for disabling quick bar shortcuts

* Remove accidentally included code

* Change copy for toggle

* Rename hass property to be for generic shortcuts
2020-10-27 20:17:53 +01:00
Thomas Lovén
ce414a5ca9 Correctly replace rebuilt badges in view (#7487) 2020-10-27 20:17:34 +01:00
Ryan Meek
e4e6edd573 Fix lovelace background color (#7478) 2020-10-27 20:17:18 +01:00
Erik Montnemery
79927f4dc9 Update translation for MQTT reload (#7475) 2020-10-27 20:17:02 +01:00
Ryan Meek
603b833757 fix edit mode mwc-button size (#7472) 2020-10-27 20:16:42 +01:00
Donnie
ba99d1a10d Add server restart/stop to quick bar command list (#7488) 2020-10-27 20:14:25 +01:00
Donnie
efe97e8f51 Refactor sidebar renders into methods (prep for mwc-list conversion) (#7453) 2020-10-27 20:12:30 +01:00
Donnie
5ec23bb7ab Change Quick Bar shortcuts to "e" and "c" (#7496)
* Add toggle for disabling quick bar shortcuts

* Change shortcut from Ctrl+P and Ctrl+Shift+P to qe and qc (Quick Entity, Quick Command)

* Remove accidentally included code

* Use tinykeys for handling shortcuts

* Change shortcuts to e and c. And fix small typo.

* Change copy for toggle

* Rename hass property to be for generic shortcuts

* Minor tweaks to address review comments
2020-10-27 10:34:51 -07:00
Donnie
9b4d01ab75 Add toggle for disabling quick bar shortcuts (#7495)
* Add toggle for disabling quick bar shortcuts

* Remove accidentally included code

* Change copy for toggle

* Rename hass property to be for generic shortcuts
2020-10-27 10:00:46 -07:00
Nathan Orick
40191a88d4 Allow edit card dialog to be made wider (#7492) 2020-10-27 15:59:32 +01:00
Josh McCarty
a19477d179 Migrate ha-paper-dialog to ha-dialog in system options dialog (#7455) 2020-10-27 15:24:52 +01:00
Erik Montnemery
bf98a78f3d Add custom device action to remove a Tasmota device (#7469) 2020-10-27 14:04:28 +01:00
Florian Gareis
ba4c2fc1bd Replace prompt with showPromptDialog (#7460)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-27 14:04:01 +01:00
Charles Garwood
b56e9ef028 Add cancel command button to ozw config panel (#7471) 2020-10-27 14:00:21 +01:00
Philip Allgaier
dbbd34c520 Allow breaking of entity ID in history tooltip into multiple lines (#7482) 2020-10-27 11:14:47 +01:00
J. Nick Koston
ccb69dbdfa Add icons for shade device class (#7493)
The blinds material icon looks more like a shade
then a blind but since its already being used
blind its still better than having it look like
a window.
2020-10-26 20:43:22 -05:00
HomeAssistant Azure
11e555ef6f [ci skip] Translation update 2020-10-27 00:32:18 +00:00
Thomas Lovén
61e17395c9 Correctly replace rebuilt badges in view (#7487) 2020-10-26 20:55:47 +01:00
Ryan Meek
733ce3b6b8 Fix lovelace background color (#7478) 2020-10-26 09:42:57 +01:00
Erik Montnemery
375f143199 Update translation for MQTT reload (#7475) 2020-10-26 09:41:43 +01:00
HomeAssistant Azure
2419f35eb9 [ci skip] Translation update 2020-10-26 00:32:40 +00:00
HomeAssistant Azure
21867c3576 [ci skip] Translation update 2020-10-25 00:32:37 +00:00
J. Nick Koston
28853b28bc Show cover attributes on the more-info card (#7458) 2020-10-24 08:26:18 -05:00
HomeAssistant Azure
e2f27568a5 [ci skip] Translation update 2020-10-24 00:32:12 +00:00
Ryan Meek
98b2b796b0 fix edit mode mwc-button size (#7472) 2020-10-24 00:53:11 +02:00
Philip Allgaier
b8f3fcf00b Allow discovered integration titles to line wrap (#7468) 2020-10-23 16:43:20 +02:00
Philip Allgaier
d3fda9a821 Ensure attribute values are consistently right aligned (#7466) 2020-10-23 16:29:27 +02:00
HomeAssistant Azure
19e69dc13e [ci skip] Translation update 2020-10-23 00:32:22 +00:00
Bram Kragten
48543a2dad Bumped version to 20201021.2 2020-10-23 00:18:49 +02:00
Philip Allgaier
b22f5ae5c2 Add outline color for dark buttons (#7444) 2020-10-23 00:18:26 +02:00
J. Nick Koston
2acb6a28fe Update template time listener phrasing for core changes (#7450) 2020-10-23 00:16:40 +02:00
Donnie
1064cdb79d Fix quick bar dark mode contrast, filter returning all items, no primary text (#7430)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-23 00:16:18 +02:00
Bram Kragten
bd7cb1c877 Template dev tools: Print the type of the response and stringify objects (#7439) 2020-10-23 00:15:57 +02:00
Bram Kragten
6c314982dc Pass narrow to masonry view to calc columns (#7454) 2020-10-23 00:15:36 +02:00
Philip Allgaier
d54710f113 Fix capitalization of state attributes (#7448) 2020-10-23 00:15:20 +02:00
J. Nick Koston
1346156ecd Avoid fetching logbook data instead in addition to not displaying it (#7427)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-10-23 00:15:04 +02:00
Bram Kragten
a2d9f9b417 Fix ES5 build, fix virtualizer polyfill (#7451) 2020-10-23 00:14:48 +02:00
Bram Kragten
3de78cca2d Fix quickbar debounce (#7426)
* Fix quicbar debounce

* Clear search property when dialog is closed

Co-authored-by: Donnie <donniekarnsinsb@hotmail.com>
2020-10-23 00:14:28 +02:00
J. Nick Koston
5fa7cd9fa9 Update template time listener phrasing for core changes (#7450) 2020-10-23 00:10:51 +02:00
Donnie
a78c00fb41 Fix quick bar dark mode contrast, filter returning all items, no primary text (#7430)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-23 00:06:36 +02:00
Philip Allgaier
edc2a03d1c Get rid of the unwanted tooltip copying (#7408) 2020-10-22 23:57:52 +02:00
Bram Kragten
174f8f5823 Template dev tools: Print the type of the response and stringify objects (#7439) 2020-10-22 23:51:33 +02:00
Bram Kragten
9fbc94e8d8 Pass narrow to masonry view to calc columns (#7454) 2020-10-22 23:29:26 +02:00
Philip Allgaier
6aff35196d Fix capitalization of state attributes (#7448) 2020-10-22 23:13:36 +02:00
J. Nick Koston
eceed4ed74 Avoid fetching logbook data instead in addition to not displaying it (#7427)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-10-22 22:47:19 +02:00
Bram Kragten
7428731eac Fix ES5 build, fix virtualizer polyfill (#7451) 2020-10-22 22:43:15 +02:00
Philip Allgaier
89b07ea0ae Add outline color for dark buttons (#7444) 2020-10-22 16:24:12 +02:00
Ian Richardson
d16daf0fd9 Add last-updated as a secondaryinfo option to entity rows (#7433) 2020-10-22 12:37:51 +02:00
Bram Kragten
211ab4eea8 Fix quickbar debounce (#7426)
* Fix quicbar debounce

* Clear search property when dialog is closed

Co-authored-by: Donnie <donniekarnsinsb@hotmail.com>
2020-10-22 11:00:36 +02:00
HomeAssistant Azure
dbd53f8d14 [ci skip] Translation update 2020-10-22 00:32:19 +00:00
Bram Kragten
a27680b8c0 Merge pull request #7424 from home-assistant/dev 2020-10-21 23:43:52 +02:00
Bram Kragten
07fc9b98cc Bump webpack (#7423) 2020-10-21 23:39:05 +02:00
Bram Kragten
33582c0448 Bumped version to 20201021.1 2020-10-21 23:35:57 +02:00
Bram Kragten
73be0fef75 Merge pull request #7422 from home-assistant/dev
20201021.0
2020-10-21 19:21:45 +02:00
Bram Kragten
611202c905 Fix no focus on first item when switching mode (#7421) 2020-10-21 18:50:08 +02:00
Bram Kragten
e553f35a68 Merge branch 'master' into dev 2020-10-21 18:41:18 +02:00
Bram Kragten
673649a603 Bumped version to 20201021.0 2020-10-21 18:36:59 +02:00
Bram Kragten
c4ed743370 Fix mwc list items icon color (#7420) 2020-10-21 18:21:58 +02:00
Joakim Sørensen
682fa0d3eb Haos update button (#7419) 2020-10-21 10:30:41 -05:00
On Freund
30f34eee22 Add context to event trigger (#7182)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-21 17:26:45 +02:00
Bram Kragten
eab76bf85b Fix position edit card dialog (#7418) 2020-10-21 15:26:08 +02:00
Donnie
bcf405bf9d Fix Firefox quick bar issue by allowing Ctrl+P to toggle modes (#7413)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-21 14:19:16 +02:00
Ryan Meek
3c4b0d4a74 Compact header (#7369)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-21 14:18:33 +02:00
Zack Barett
fb9bd0eb7d Fix prettier that keeps messing with merging dev (#7412) 2020-10-21 13:14:10 +02:00
Ian Richardson
7e2dc04123 Fix icon for unavailable buttons (#7416) 2020-10-21 13:12:28 +02:00
Bram Kragten
54ec37994c Improve performance of quick bar (#7359)
Co-authored-by: Donnie <donniekarnsinsb@hotmail.com>
2020-10-21 13:07:44 +02:00
Gilson Marquato Júnior
4a5935ee36 Add onClick listener to dismiss toast notification. (#7268) 2020-10-21 13:03:31 +02:00
Joakim Sørensen
01b9a07320 Change logic for the new version handling (#7405) 2020-10-21 12:40:06 +02:00
Zack Barett
0fcf0dcd18 Fix Yarn lock (#7411) 2020-10-20 19:37:26 -05:00
HomeAssistant Azure
80481f142a [ci skip] Translation update 2020-10-21 00:32:23 +00:00
Philip Allgaier
2be08ce7ab Light hui & more-info card fixes (#7397) 2020-10-20 23:52:10 +02:00
Bram Kragten
37eb5af3d4 Pass updated cards and badges to view element (#7407) 2020-10-20 23:51:15 +02:00
Philip Allgaier
8c8151be92 Add entity filter to history panel (#7401) 2020-10-20 23:02:59 +02:00
Philip Allgaier
baf31d1c1e Fix alignments in integration card (#7404) 2020-10-20 22:59:20 +02:00
Donnie
af2250835a Only admins can launch quick bar (#7388)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-10-20 22:24:48 +02:00
Donnie
6f2a759ba3 Add scoring and sorting to sequence matcher (#7367)
* Replace sequence matcher with VS Code's score-based implementation

* Remove everything not related to fuzzyScore and matchSubstring

* Fix bug when filter length <= 3

* Add licensing and credit to Microsoft

* Remove unnecessary character codes

* Remove old sequence matcher, update tests, fix issue with not finding best score in list of words

* Remove unnecessary sequence precheck, refactor client api to remove array

* Fix issue with score sorting not implemented correctly and thus not actually sorting by score

* Update src/common/string/filter/sequence-matching.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Remove unnecessary string return from fuzzy matcher. Clean up code

* Remove globals from filter. Move sorting logic into matcher

* Update function description, make score property optional.

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-20 09:01:16 -07:00
Jaroslav Hanslík
5065901196 Modified icons of binary sensors: gas, problem, safety, smoke (#7403) 2020-10-20 17:43:01 +02:00
Philip Allgaier
41b59e6e11 Bump round-slider to 0.5.2 (#7399) 2020-10-20 08:58:35 -05:00
onagurna
43afdaadc6 Fix syntax error (#7402) 2020-10-20 15:09:27 +02:00
Tomasz
83c5151792 bump node-vibrant to 3.1.6 (#7400) 2020-10-20 12:42:51 +02:00
HomeAssistant Azure
0880ab67c6 [ci skip] Translation update 2020-10-20 00:32:22 +00:00
Donnie
c0b2143c7c Quick Bar dark mode, ignore leading white space (#7387) 2020-10-19 22:10:44 +02:00
Philip Allgaier
c1de162c99 Visual alignment between PR 7364 & 7220 (#7396) 2020-10-19 22:09:15 +02:00
Philip Allgaier
a7ef8aba68 Add dialog-box warning support (#7356) 2020-10-19 22:08:35 +02:00
Philip Allgaier
3ee4c11a99 Move error log <ha-card> + color in log entries (#7382) 2020-10-19 22:05:19 +02:00
Philip Allgaier
990ae10dc2 Detect Lovelace resource type based on file extension (#7354) 2020-10-19 22:03:59 +02:00
Philip Allgaier
52b2fd046b Improved automation & script menus + show errors in toast (#7371)
* Improved automation & script menus + show errors in toast

* Changes from review

* Re-added old error display

* Toast back to default duration + remove action
2020-10-19 19:55:36 +02:00
J. Nick Koston
9f41f80a91 Add support for displaying time listeners (#7220) 2020-10-19 19:43:04 +02:00
Joakim Sørensen
eec4a91ad8 Fixes for snapshot upload during onboarding (#7390)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-19 15:24:25 +02:00
Joakim Sørensen
7c51001c3c Move valid so we don't cache an empty element for add-on config (#7394) 2020-10-19 15:23:13 +02:00
Daniel Martin Gonzalez
a4ea4b1f5f Add Timer to Helpers (#7366) 2020-10-19 14:45:20 +02:00
Philip Allgaier
19fc37539e Fix alignment / padding of ha-switch (#7349) 2020-10-19 14:44:54 +02:00
Philip Allgaier
ce7acb0feb Clean up general config page code (#7383) 2020-10-19 14:40:01 +02:00
Philip Allgaier
105b7678b8 Make error texts of device automation picker translatable (#7370) 2020-10-19 14:30:40 +02:00
Philip Allgaier
b67575586e Add option to copy system info into clipboard (#7323) 2020-10-19 14:29:10 +02:00
Ing. Jaroslav Šafka
3dc6898673 Enable volume up/down for media player (#7376)
Always show volume up/down buttons in media player
if SUPPORT_VOLUME_BUTTONS is supported.
And slider if supported

Previous impl. shows slider or up/down buttons.
2020-10-19 11:16:20 +02:00
Philip Allgaier
a73754c1b5 Use ha-card for dev tool "Services" + visual tweaks (#7364) 2020-10-19 11:15:12 +02:00
Charles Garwood
1ebf1c00d6 Initial OZW Node Config Panel (#7377) 2020-10-19 11:13:55 +02:00
HomeAssistant Azure
7dac7d757e [ci skip] Translation update 2020-10-19 00:32:45 +00:00
HomeAssistant Azure
b1f3192b95 [ci skip] Translation update 2020-10-18 00:32:21 +00:00
Donnie
16984d18bb Refactor quick bar to use a common interface for future commands and easier sorting (#7368) 2020-10-17 15:48:48 -07:00
Philip Allgaier
e603893d77 Fix navigation links for "script/edit" (#7363) 2020-10-17 17:28:05 -05:00
Bram Kragten
a7998b30c6 Fix hls player (#7362) 2020-10-17 23:43:00 +02:00
Philip Allgaier
3277a4e8c3 Minor tweaks for when media player has no items (#7374) 2020-10-17 23:34:11 +02:00
Philip Allgaier
7e769d0e14 Make <ha-card> use <h1> for header (#7373)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-10-17 23:22:56 +02:00
Philip Allgaier
713e0579f8 Entity registry settings: Remove "Override" string + use domain icon as fallback (#7320) 2020-10-17 22:46:34 +02:00
Philip Allgaier
6e130cc020 Properly wrap integration title / device names (#7355) 2020-10-17 22:28:40 +02:00
Villhellm
eb036a12d9 add help button to tags config panel (#7278)
* add help button to tags config panel

* Update src/panels/config/tags/ha-config-tags.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-17 22:21:03 +02:00
Joakim Sørensen
534d1f5055 Add dialog and links for unsupported supervisor installation (#7332) 2020-10-17 22:18:17 +02:00
Daniel Martin Gonzalez
cbef909657 Add Counter to Helpers (#7346)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-17 22:15:37 +02:00
Philip Allgaier
874f3b32b3 Harmonize the font sizes on area and device page (#7357) 2020-10-17 22:10:52 +02:00
Bram Kragten
2fd017cf73 Fix more info group (#7345)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
2020-10-17 22:05:04 +02:00
Bram Kragten
5740b018a7 Prevent old more info control to be created (#7324) 2020-10-17 22:04:44 +02:00
Tomasz
288bf6805a Sort persistent notifications ascending (#7195)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-17 22:03:33 +02:00
HomeAssistant Azure
02d37a369a [ci skip] Translation update 2020-10-17 00:32:29 +00:00
Philip Allgaier
1d316c3258 Remove query caching to get YAML editor toggle working again (#7365) 2020-10-16 23:27:00 +02:00
Joakim Sørensen
a56ce62f1a Add docker registry management (#7269)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-16 17:25:23 +02:00
Zack Barett
c268f42851 Entity Picker: undo fuzzy algorithm (#7360) 2020-10-16 09:45:58 -05:00
HomeAssistant Azure
7251e802ab [ci skip] Translation update 2020-10-16 00:32:27 +00:00
Matt
5b1a2d10c2 Add button that dismisses all notifications (#7223) 2020-10-15 16:22:47 -05:00
Charles Garwood
2dd7f292b1 Add product picture to ozw node dashboard (#7203) 2020-10-15 16:20:54 -05:00
Santobert
213c53e307 Add the options dark_mode_filter and dark_mode_image to the picture elements card (#7304) 2020-10-15 16:16:15 -05:00
Bram Kragten
ce07dfd8ac Little clean up in data table (#7352) 2020-10-15 18:46:55 +02:00
Zack Barett
c1dba462e8 Lovelace Cards: Update size calcs and add height fixes for horizontal stacks (#7177) 2020-10-15 17:46:29 +02:00
Philip Allgaier
47f0d74812 New "clickable" property for <ha-data-table> (#7351) 2020-10-15 15:57:05 +02:00
Ryan Meek
ce80285f8d Header styling & paper-tabs improvements (#7238)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-15 15:08:53 +02:00
Philip Allgaier
d2dd1a43dd Fix attribute picker suffix (#7348) 2020-10-15 14:56:10 +02:00
Zack Barett
12d73fe90d Add Friendly Name to the Entity Picker + FuzzySeq Algo (#7291) 2020-10-14 20:20:21 -05:00
HomeAssistant Azure
c2741638b2 [ci skip] Translation update 2020-10-15 00:32:32 +00:00
Philip Allgaier
4a7fb3d509 Use attribute picker for sec. info in weather card editor (#7335) 2020-10-15 00:57:30 +02:00
Bram Kragten
f6ff652ca4 Fix es5 build (#7319) 2020-10-14 22:20:39 +02:00
Donnie
6165cb0f83 Make enter key execute first filtered item in Quick Bar (#7288)
* Make enter key execute first filtered item in quick open dialog

* Add support for arrow up and down moving in and out of list

* Add support for typing letters even when arrowing around the list

* Address review comments and clean up the single-character keyboard event logic

* Address comments. Move activated index along with arrow keys

* Add loading spinner and remove memoization
2020-10-14 19:01:44 +02:00
Bram Kragten
1f361b7b10 Update entity picker (#7343) 2020-10-14 17:01:23 +02:00
Tomasz
5269ff978b New component: ha-expansion-panel (#6789)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-14 16:28:46 +02:00
Bram Kragten
55595493a9 Update config.yml 2020-10-14 13:57:48 +02:00
Bram Kragten
ad3ff0aba7 Delete FEATURE_REQUEST.md 2020-10-14 12:04:24 +02:00
Bram Kragten
ce48546cef Move feature requests to discussions 2020-10-14 12:04:07 +02:00
HomeAssistant Azure
35b3bc995e [ci skip] Translation update 2020-10-14 00:32:30 +00:00
Donnie
63f60019d1 Add friendly name to quick bar list and filter (#7306)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-13 17:26:12 -05:00
Philip Allgaier
0d741b6275 Add warning to badge preview in "Panel Mode" (#7331) 2020-10-13 13:49:02 +02:00
uvjustin
0df9080bbb Check if video el still exists before exoplayer resize in ha-hls-player (#7317)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-13 12:15:30 +02:00
Donnie
ddcf89e6a2 Add tests for fuzzy sequence matcher (#7328) 2020-10-13 11:50:36 +02:00
Donnie
5de225d5d4 Fix: Quick Bar not launching on windows (#7293) 2020-10-13 11:23:18 +02:00
Donnie
5cddb482f1 Fix issue with enter key not executing selected item in quick bar list (#7283) 2020-10-13 11:16:46 +02:00
HomeAssistant Azure
c000d724de [ci skip] Translation update 2020-10-13 00:32:16 +00:00
Philip Allgaier
504055f331 Add documentation link to "customize" dialog (#7321) 2020-10-12 22:09:34 +02:00
Philip Allgaier
7f6880f40e Show disabled entities for a config_entry by default + number of hidden entities (#7300)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-12 22:00:21 +02:00
Zack Barett
02e4e3c892 Weather Card: add icons instead of text (#7305)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-10-12 11:52:17 -05:00
Bram Kragten
b5b1849ab3 Bumped version to 20201001.2 2020-10-08 15:12:18 +02:00
Bram Kragten
0e10c81025 Fix cloud webhooks (#7261) 2020-10-08 15:12:05 +02:00
Bram Kragten
cce7ad449a Set hass when creating card (#7187) 2020-10-05 16:04:25 +02:00
Bram Kragten
d437dd5919 Bumped version to 20201001.1 2020-10-05 16:00:40 +02:00
Bram Kragten
f1980730d2 Fix Apple not showing cards (#7232) 2020-10-05 16:00:27 +02:00
Zack Barett
47773e9cae Fix entities Card toggle (#7192) 2020-10-05 16:00:12 +02:00
Ryan Meek
60969b0916 Styling fixes for hui-masonry-view (#7226) 2020-10-05 15:59:53 +02:00
Zack Barett
ecc7925d03 Warning Element: Fix Overflow in Entity Row (#7193) 2020-10-05 15:59:38 +02:00
Zack Barett
6d3010dcc7 Logbook: Fix for no state obj (#7191) 2020-10-05 15:59:19 +02:00
J. Nick Koston
0164bafbf1 Fix reversal in power icon (#7188) 2020-10-05 15:58:57 +02:00
Bram Kragten
98a64e3114 Merge pull request #7185 from home-assistant/dev 2020-10-01 16:05:08 +02:00
Bram Kragten
6ef3d091e1 Merge pull request #7174 from home-assistant/dev 2020-09-30 17:28:42 +02:00
Bram Kragten
b612c0e0d6 Merge pull request #7168 from home-assistant/dev 2020-09-30 11:45:31 +02:00
409 changed files with 19586 additions and 7586 deletions

13
.devcontainer/Dockerfile Normal file
View 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"

View 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
}
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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.

View File

@@ -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
View File

@@ -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
View File

@@ -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
View 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
View 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
View File

@@ -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
View 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
View 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"
}
]
}

View File

@@ -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.

View File

@@ -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",
[ [

View File

@@ -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") {

View File

@@ -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();
} }

View File

@@ -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, ".."),

View File

@@ -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"

View File

@@ -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>

View File

@@ -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",
}, },
], ],
}, },

View File

@@ -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 () => {};
});
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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,
}), }),
]; ];

View File

@@ -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);
} }
} }

View File

@@ -98,4 +98,4 @@ class DemoButtonEntity extends PolymerElement {
} }
} }
customElements.define("demo-hui-button-card", DemoButtonEntity); customElements.define("demo-hui-entity-button-card", DemoButtonEntity);

View File

@@ -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 {

View File

@@ -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
`, `,
}, },
{ {

View File

@@ -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);

View File

@@ -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

View File

@@ -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)
`, `,
}, },
]; ];

View File

@@ -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);
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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

View File

@@ -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);

View File

@@ -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> {

View File

@@ -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 &&

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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

View 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;
}
}

View 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: {},
});
};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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"

View File

@@ -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 = {

View File

@@ -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`,
}, },
]; ];

View File

@@ -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"
}, },

View File

@@ -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"
], ],

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

55
script/core Executable file
View 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"

View File

@@ -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",

View File

@@ -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
View 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}`;
};

View 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);

View File

@@ -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}`;

View File

@@ -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;

View File

@@ -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":

View File

@@ -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);

View File

@@ -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";
}
};

View File

@@ -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) {

View File

@@ -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>

View 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,
}

View 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

View 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
);
};

View 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();
};

View File

@@ -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;
};

View File

@@ -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 = {};

View 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);
};

View File

@@ -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;
}
`; `;
} }
} }

View File

@@ -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] {

View File

@@ -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(

View File

@@ -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

View File

@@ -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(

View File

@@ -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(

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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"

View File

@@ -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}
`; `;

View File

@@ -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);

View 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;
}
}

View File

@@ -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;

View File

@@ -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>
`; `;

View File

@@ -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":

View File

@@ -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);

View 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;
}
}

View File

@@ -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) {

View File

@@ -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);

View 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;
}
}

View File

@@ -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);

View 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;
}
}

View File

@@ -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