Compare commits

..

375 Commits

Author SHA1 Message Date
Ludeeus d3522d33b6 Use 6A1B9A 2021-12-06 09:48:52 +00:00
Ludeeus 680e416453 Move companion entry 2021-12-06 09:34:14 +00:00
Philip Allgaier 8f5751d5bb Use correct label in area card editor (#10799) 2021-12-05 12:09:06 -06:00
Philip Allgaier 4095450476 Add switch to input row domains to fix mobile focus issue (#10792) 2021-12-04 11:43:14 +01:00
Bram Kragten e61f587c51 Bumped version to 20211203.0 2021-12-03 18:07:07 +01:00
Bram Kragten d43d19190e Fix entity marker (#10787) 2021-12-03 17:04:50 +00:00
Bram Kragten a283acaabf safari doesnt support overflow-wrap: anywhere 2021-12-03 18:04:03 +01:00
Philip Allgaier ea18fc0078 Ensure we always have an active theme name (fixes dark theme issues) (#10780)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-12-03 17:02:54 +00:00
Bram Kragten 1df11e9bf1 Use groupBy (#10786) 2021-12-03 08:42:23 -08:00
Bram Kragten c71b2e6b9d Add provisioned device overview to zwave js (#10785) 2021-12-03 08:34:26 -08:00
Bram Kragten db4aa05bf4 Differentiate between assigned and targeting scene/automations/script (#10781) 2021-12-03 08:21:26 -08:00
Bram Kragten a54a2a54f8 Add support for local only users (#10784)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-12-03 16:34:34 +01:00
Philip Allgaier 0bcb4d0e09 Restore flex alignment for select and input-select rows (#10783) 2021-12-03 16:19:00 +01:00
Bram Kragten 95dbc811d3 Allow overriding device class (#10777) 2021-12-03 16:07:49 +01:00
Philip Allgaier e28a11964e Use correct styling for cloud certificate dialog (#10782) 2021-12-03 15:08:49 +01:00
Bram Kragten 46a9e36516 Guard for non numeric states (#10775)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-12-03 12:53:50 +01:00
Paulus Schoutsen e99f20c4f3 Tweak ZJS dashboard (#10772) 2021-12-03 10:51:50 +01:00
Joakim Sørensen 2100603cdc Remove handling of the supervisor panel from the sidebar (#10773) 2021-12-03 10:49:42 +01:00
Joakim Sørensen da4942aca3 Add default icons for button entities (#10774) 2021-12-03 09:11:29 +01:00
Paulus Schoutsen 7c78fb314e Show add devices fab on devices page for ZJS (#10771) 2021-12-03 08:42:39 +01:00
Paulus Schoutsen 5bc2468cbc Bumped version to 20211202.0 2021-12-02 14:31:09 -08:00
Bram Kragten a580904c52 Use chips for button rows (#10770) 2021-12-02 23:29:52 +01:00
Bram Kragten 48d12ceafe Group entities in area card by domain (#10767)
* Group entities in area card by domain

* Update hui-area-card.ts

* Update

* Add background color when no image

* Add camera support

* exclude unavailable states

* Update hui-area-card.ts
2021-12-02 23:15:18 +01:00
Carlos Garcia Saura 60ce805b3b Update hui-graph-header-footer.ts (#10476) 2021-12-02 13:32:38 -08:00
Paulus Schoutsen 251416b51d Add missing translation (#10769) 2021-12-02 13:01:19 -08:00
Bram Kragten c41c6eedd8 Remove thingtalk cleanup create new automation dialog (#10748)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-12-02 11:26:41 -08:00
Joakim Sørensen 6877fd9e00 Hide updates for dev as well (#10761) 2021-12-02 17:32:18 +01:00
Joakim Sørensen 4cc104a99f Use add-ons for mobile header (#10760) 2021-12-02 17:31:41 +01:00
Joakim Sørensen 6494177821 Fix SU sidebar issues (#10757) 2021-12-02 17:31:09 +01:00
Joakim Sørensen cea1a62867 handle ha-radio and ha-checkbox in ha-formfield (#10759) 2021-12-02 17:30:10 +01:00
rianadon a6b5262d02 Use unit system definitions for weather units (#10657) 2021-12-02 17:27:23 +01:00
Joakim Sørensen 2a5fc5181e Fix create backup checkbox (#10756) 2021-12-02 11:54:05 +01:00
Joakim Sørensen 2fe8f5ff27 Use puzzle for addons and blur entries on click (#10755) 2021-12-02 11:05:14 +01:00
Philip Allgaier 0c75d5afc9 Make graph colors themable (#10698) 2021-12-02 10:49:46 +01:00
Philip Allgaier cf062bf0f4 Fix pointer/more-info inconsistencies for entity rows (#10025)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-12-02 10:48:30 +01:00
Paulus Schoutsen acf4d59fde Bumped version to 20211201.0 2021-12-01 14:47:17 -08:00
Paulus Schoutsen 05333ac2d9 Show disabled entity names on the device page (#10743)
* Show disabled entity names on the device page

* Update src/panels/config/devices/device-detail/ha-device-entities-card.ts

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-12-01 14:46:40 -08:00
Bram Kragten 4b49da58b1 Add SmartStart/QR scan support for Z-Wave JS (#10726) 2021-12-01 14:12:52 -08:00
Joakim Sørensen 68373e6372 Focus Add-ons & Backups in config panel when clicking Supervisor in sidebar (#10745)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-12-01 08:46:38 -08:00
Matthias de Baat 01049e8eb8 Updated text (#10747)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-12-01 14:10:32 +00:00
Joakim Sørensen 87f7981144 Fix faded element in change log (#10737) 2021-12-01 13:55:01 +01:00
Paulus Schoutsen ceac9834b9 Change the area of scenes in editor (#10731) 2021-12-01 13:54:28 +01:00
Joakim Sørensen ac8f748656 Hide ha-icon-next if narrow (#10746) 2021-12-01 09:23:13 +01:00
Joakim Sørensen 1d97d8dca9 Handle 0 updates and show back on supervisor panels (#10744) 2021-11-30 23:30:38 -08:00
Bram Kragten fd6785b593 Use backend for day month stats in energy dashboard (#10728) 2021-11-30 09:22:06 -08:00
Joakim Sørensen d5fc751da6 Revert 10711 (#10736) 2021-11-30 18:02:02 +01:00
Joakim Sørensen 933fd72629 Fix typo (#10734) 2021-11-30 11:41:59 -05:00
Joakim Sørensen 0611133065 Move companion app config from sidebar to configuration dashboard (#10733)
* Move companion app config from sidebar to configuration dashboard

* Remove translation refrence
2021-11-30 08:03:10 -08:00
Allen Porter 02644b923f Improve hls stream view error handling (#10714)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-30 08:48:24 +01:00
Paulus Schoutsen 67f06112c6 Bumped version to 20211130.0 2021-11-29 16:57:58 -08:00
Paulus Schoutsen 49e39644f3 Tweak how scenes behave in generated lovelace (#10730) 2021-11-29 16:56:08 -08:00
Joakim Sørensen 990ad1bb67 Dashboard tweaks (#10729) 2021-11-29 23:56:59 +01:00
Philip Allgaier dbbf246060 Installation type property during onboarding was misspelled (#10721) 2021-11-29 14:41:21 -08:00
amitfin d2c20837a5 Fixed invalid hour handling in AMPM mode (#10717)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-29 18:49:33 +00:00
Philip Allgaier e91d1777d0 Ensure conditional rows getting state_color value (#10708)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-29 12:30:08 +01:00
Joakim Sørensen a5be143c3b Fix chip text color variable overrides (#10722) 2021-11-29 11:31:49 +01:00
Philip Allgaier 0ef07e4835 Ensure markdown card input is a string (#10705) 2021-11-29 10:50:08 +01:00
Philip Allgaier 9361e4cf9c Ensure required translations are loaded in safe-mode (#10709) 2021-11-29 10:34:25 +01:00
Luca Cavalli e7fd75703f Fixed ellipsis usage on graph legend entries. (#10707) 2021-11-29 10:30:27 +01:00
Philip Allgaier 2c0b2f4bc5 Convert cover UI to Lit + ensure proper tilt rendering (#10671) 2021-11-29 10:30:14 +01:00
Philip Allgaier faec09f0d1 Filter out disabled entities in the statistics dev tools (#10677) 2021-11-29 10:19:33 +01:00
Nathan Orick b79c06ad71 Default to yaml editing when there are multiple states in condition (#10481) 2021-11-29 10:14:09 +01:00
Philip Allgaier 5614e0d29c Make "Energy distribution today" translatable (#10696) 2021-11-29 10:09:54 +01:00
Philip Allgaier 0b7fc177f9 Prevent errors in more-info-climate if no modes are provided despite support flags (#10694) 2021-11-29 10:03:30 +01:00
Philip Allgaier 367322415e Use ha-icon-button in ha-icon-overflow-menu (#10692) 2021-11-29 09:58:34 +01:00
Joakim Sørensen 117b50f3ea Add ha-faded (#10651) 2021-11-28 22:27:53 -08:00
Philip Allgaier 366aa8aed1 Fix typo on config page + adjust icon color (#10713) 2021-11-28 17:52:39 +01:00
Joakim Sørensen 43011179eb Finish up config changes (#10710)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-26 17:24:30 +01:00
Joakim Sørensen 6177d2b416 Use app-header-text-color (#10711) 2021-11-26 17:11:06 +01:00
Joakim Sørensen f70485bc49 Don't make button disabled on error (#10699) 2021-11-25 16:56:57 +01:00
Erik Montnemery 921763b5f1 Improve device information when via device is unknown (#10685) 2021-11-24 09:09:21 +01:00
Joakim Sørensen 5fd4315789 Fix addon slug (#10693) 2021-11-23 08:53:17 -08:00
Joakim Sørensen ed291b57d0 Render update card on add-on page (#10681) 2021-11-23 08:18:40 -08:00
Joakim Sørensen f833701e7c Update background colors of navigation icons (#10691) 2021-11-23 14:36:11 +01:00
Paulus Schoutsen 8533b90957 Bumped version to 20211123.0 2021-11-22 17:28:13 -08:00
Laszlo Magyar c95a54c6f3 Fixing typo in #10626 (#10686) 2021-11-22 18:59:35 +01:00
Joakim Sørensen a991640f52 Remove first part of the update description (#10669) 2021-11-22 09:09:23 -08:00
Joakim Sørensen 3d99b92c07 Limit setting up supervisor subscriptions to the supervisor panel (#10680) 2021-11-22 08:59:28 -08:00
Philip Allgaier d28ad17135 Use component to ensure relative-time in Glance card gets updated (#10666) 2021-11-22 11:12:04 +01:00
Philip Allgaier 3c67fc96b1 Make "Show more" show everything starting from yesterday (#10533) 2021-11-22 10:56:40 +01:00
Joakim Sørensen 4719636176 Fix dark main-content and split gallery demo (#10675) 2021-11-21 21:01:51 -08:00
Paulus Schoutsen 45efee28b8 Add scenes and scripts as buttons in footer of area cards (#10673)
* Add scenes and scripts as chips in footer of area cards

* Remove unused chips config type

* Update src/panels/lovelace/common/generate-lovelace-config.ts

Co-authored-by: Zack Barett <arnett.zackary@gmail.com>

* Fix typing

Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
2021-11-21 20:59:56 -08:00
Joakim Sørensen 3bcf225380 Fix color overlay in ha-alert content (#10674) 2021-11-21 20:16:19 +01:00
Joakim Sørensen 2e81f843ce Use white for icons with backgound (#10672) 2021-11-21 18:07:55 +00:00
Joakim Sørensen a430142296 Add iconColor to ha-config-navigation entries (#10658) 2021-11-21 09:52:58 -08:00
Joakim Sørensen 6335b13c5e Remove core note on update page (#10661) 2021-11-21 09:16:06 -08:00
Joakim Sørensen 6c4e987a24 Make ha-chip-set slot-able (#10647) 2021-11-21 09:15:38 -08:00
Joakim Sørensen 1a5c43d72a Fix color over slotted image in ha-alert (#10652) 2021-11-21 09:13:48 -08:00
epenet 91dbfca899 Add frequency device class for sensor (#10621) 2021-11-21 05:05:32 +01:00
Bram Kragten 96f103644a Send error message to sender (#10660) 2021-11-19 13:22:49 -08:00
Paulus Schoutsen 5304e5a670 Always render groups/areas in a single column (#10655) 2021-11-19 13:16:43 -08:00
Lasse Rosenow 390e5b3881 Simplify launch screen svg (#10643) 2021-11-18 16:20:45 -08:00
Joakim Sørensen 9f5756c9fa Use ha-formfield around backup checkbox (#10653) 2021-11-18 16:09:39 -08:00
Joakim Sørensen 0ca35d7012 Remove ha-alert actionText (#10646) 2021-11-18 16:09:13 -08:00
Joakim Sørensen 0d19f4792f Fix active tab (#10654) 2021-11-18 19:21:19 +00:00
Joakim Sørensen 91b009af79 Fix back button color (#10650) 2021-11-18 18:57:15 +01:00
Paulus Schoutsen 1ebd2fb9f1 Bumped version to 20211117.0 2021-11-17 10:54:08 -08:00
Zack Barett 4684979ae7 Area Card (#10141)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-17 19:43:41 +01:00
Joakim Sørensen a567312bdb Show updates on dashboard for dev (#10637) 2021-11-17 18:39:16 +00:00
Joakim Sørensen 1e851e0e8c Remove customize UI (#10632) 2021-11-17 10:34:20 -08:00
Bram Kragten 7d94615f47 Cast fixes (#10598) 2021-11-17 10:33:15 -08:00
Bram Kragten 582fab7ea1 Update Lovelace Cast app ID (#10592) 2021-11-17 10:32:15 -08:00
Philip Allgaier 822590ec8a Add correct button label to "no_state" statistics fix dialog (#10628) 2021-11-17 10:22:34 -08:00
Joakim Sørensen e9f0967578 Move updates (#10626) 2021-11-17 10:21:27 -08:00
Joakim Sørensen 481da19c74 Fix datatable checkbox width (#10631) 2021-11-16 19:46:41 +01:00
Joakim Sørensen b969db0c0f Use ha-form for onboarding-create-user (#10604) 2021-11-15 14:21:29 -08:00
Joakim Sørensen a6b98fc3c3 Add markers-updated to ha-locations-editor (#10601) 2021-11-15 14:11:42 -08:00
Joakim Sørensen 87c2046ab5 Remove add-on store tab (#10624) 2021-11-15 09:15:20 -08:00
David F. Mulcahey 4b992fb0c4 Correct ZHA LQI sort in device children dialog (#10616) 2021-11-15 09:11:31 -08:00
Lasse Rosenow 3154011c65 Improve startup experience by removing AppBar skeleton (#10569) 2021-11-15 07:54:59 -08:00
Bram Kragten 4e68383cf7 Remove deprecated icons (#10622) 2021-11-15 11:54:59 +01:00
Michael Irigoyen db6ef22ebb Update MDI to v6.5.95 (#10618) 2021-11-15 09:49:53 +01:00
Allen Porter c238c7dbbc WebRTC fix for Safari (#10602) 2021-11-11 10:48:56 +01:00
Bram Kragten d04823b4c5 Update image-cropper-dialog.ts 2021-11-10 22:55:05 +01:00
Bram Kragten 4cb45d6313 Add picture uploader to area (#10544) 2021-11-10 21:42:43 +01:00
Bram Kragten 6623e5f017 Fix thingktalk dialog (#10600) 2021-11-10 19:36:18 +00:00
Bram Kragten 6518aefb7f Prevent cast timeout after 10 mins, show current shown Lovelace view (#10586) 2021-11-09 21:53:40 +01:00
Bram Kragten d5600b7c08 Bumped version to 20211109.0 2021-11-09 21:42:04 +01:00
Philip Allgaier 4789295d32 Add CSS var for ha-dialog border radius (#10424) 2021-11-09 17:24:39 +01:00
Philip Allgaier 70d54aa855 Ensure theme picker row uses correct theme name (#10589) 2021-11-09 17:22:48 +01:00
Bram Kragten 77549efc47 Bump codemirror (#10588) 2021-11-09 16:10:42 +01:00
Bram Kragten 00299bc74d Fix multi select ha-form (#10585) 2021-11-09 16:10:26 +01:00
Philip Allgaier b74fc5578d Consistently show a close button for config dialogs (#10587) 2021-11-09 13:52:56 +00:00
Bram Kragten 9018d4cc18 Update translations 2021-11-08 19:58:29 +01:00
Bram Kragten fcdceba09d Bumped version to 20211108.0 2021-11-08 18:30:59 +01:00
Bram Kragten 06d4ccf344 Allow create zone without icon + add icon picker (#10447) 2021-11-08 09:29:10 -08:00
Bram Kragten a268040ae7 Fix datetime polyfill for latest build (#10572) 2021-11-08 09:28:27 -08:00
Bram Kragten 67d79d618a Allow to input decimal in ha-form-float (#10575) 2021-11-08 09:28:17 -08:00
Philip Allgaier 0e8a06e24d Use correct darkMode flag for image variant selection (#10574) 2021-11-08 11:41:17 +01:00
rianadon d7732ee850 Improve accessibility on login page (#9731) 2021-11-08 10:29:34 +01:00
H. Árkosi Róbert 729a928cfe Update connectivity icons (#10558) 2021-11-08 10:27:08 +01:00
Paulus Schoutsen fe5a582a74 Filter out entities when expanding device in target (#10570) 2021-11-08 10:22:52 +01:00
Bram Kragten c26a59d805 Format timestamp sensor states (#10525) 2021-11-05 12:01:14 +01:00
chriss158 ea331dbe0b Fix cut off slider value (#10250) 2021-11-05 11:55:03 +01:00
Bram Kragten b97d6d7059 Play dummy media to prevent app from closing (#10531) 2021-11-04 10:12:07 -07:00
Bram Kragten 9425b943dd Add separate cast media entrypoint (with ES5) (#10527) 2021-11-04 10:09:21 -07:00
Joakim Sørensen 3fd0becfd4 Update registry dialog (#10524) 2021-11-04 13:47:53 +01:00
Joakim Sørensen 12ef191a0f Handle missing hass with backup upload during onboarding (#10523) 2021-11-04 13:47:30 +01:00
Marius 2bbb4acf3d change switch icon to mdiToggleSwitch (#10475)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-04 13:37:38 +01:00
Marius 77d54df007 Add Bluetooth source_type icon (#10507)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-04 12:18:36 +01:00
Zack Barett 1c35571ef0 Fix Device Page (#10513) 2021-11-04 10:41:30 +01:00
Bram Kragten c8804160bf Add basic support for button entity (#10504) 2021-11-03 15:38:52 -07:00
Marius 0a6ffb6bc8 change device_tracker icon to reflect state (#10501)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-11-03 11:59:39 +01:00
Bram Kragten 6984f19aa0 Bumped version to 20211103.0 2021-11-03 10:35:17 +01:00
Paulus Schoutsen cb8de53d74 Dynamic align light effects dropdown (#10503) 2021-11-02 15:46:26 +00:00
Simone Chemelli 93680b9764 Improve icons for plant status card (#10493) 2021-11-02 13:34:46 +01:00
Bram Kragten 3cf9b745b5 Add more domains to sensor group, strip device name from disabled entities (#10490) 2021-11-01 15:55:55 +01:00
Nico Hirsch 5851fe26ff dialog-backdrop-filter fix for Safari (#10485) 2021-11-01 11:48:56 +01:00
Josh McCarty b188c4ec81 Measurement number format (#10459) 2021-11-01 09:32:22 +01:00
Bram Kragten 4624c3d75b Fix missing import (#10456) 2021-10-28 20:53:49 -05:00
Bram Kragten 7d196b4b95 Bumped version to 20211028.0 2021-10-28 20:06:14 +02:00
Bram Kragten 6347e44d94 Energy: Dont shrink today button (#10451) 2021-10-28 20:03:28 +02:00
Allen Porter 719d9386c5 Render Nest battery cam vertical video on screen correctly (#10431)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-28 18:02:06 +00:00
Bram Kragten bb734be4bc Remove keep logged in query string after login, dont show on select_mfa_module step (#10439) 2021-10-28 19:05:30 +02:00
Bram Kragten 7cadaf1dc3 Use min value instead of hard coded 0 (#10443) 2021-10-28 17:16:09 +02:00
Bram Kragten c30453a86f Fix title though close button in config/options flow (#10444) 2021-10-28 17:15:55 +02:00
Bram Kragten c2e3d0188e Update translations 2021-10-28 17:06:48 +02:00
Bram Kragten aabb8ea16f Fix camera more info pre load toggle (#10442) 2021-10-28 16:03:08 +02:00
Paul Bottein df572d59c5 Custom iconsets in Icon Picker (#10399)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-28 13:28:14 +00:00
Allen Porter 5ef7a37c20 Fix for Nest WebRTC cams to not require stream component (#10432) 2021-10-28 13:47:15 +02:00
Paulus Schoutsen 4b44e197ae ha-form-integer to only show slider if < 256 steps (#10430) 2021-10-28 13:44:25 +02:00
Joakim Sørensen 8b5b21ae69 Fix icon overrides in logbook (#10434) 2021-10-28 13:43:14 +02:00
Joakim Sørensen f5417fad6f Fix alignment in card editor elements (#10428) 2021-10-28 13:39:18 +02:00
Philip Allgaier 7fa6317f5c Invert "update" binary sensor device class color (#10427) 2021-10-28 11:50:52 +02:00
Paul Bottein 74533cebc6 Use tags and aliases when filtering icons in Icon Picker (#10425) 2021-10-27 20:12:12 +00:00
Bram Kragten 10986db7c6 Bumped version to 20211027.0 2021-10-27 21:03:31 +02:00
Joakim Sørensen 67648baca7 Fix Keep me logged in (#10422) 2021-10-27 15:40:24 +02:00
Joakim Sørensen dc9182e9ab Fix missing logbook icons (#10423) 2021-10-27 15:39:39 +02:00
Paulus Schoutsen 4a7a81ffdb Improve rendering person card (#10419) 2021-10-27 15:38:57 +02:00
Joakim Sørensen 09ef72647e Add running to not inverted (#10420) 2021-10-27 09:17:53 +02:00
Paulus Schoutsen da38e6f986 Hide script/sun from generated Lovelace (#10418) 2021-10-27 08:20:50 +02:00
Bram Kragten bd1a9f2cb0 Add support for external stats (#10411) 2021-10-26 23:15:57 -07:00
Philip Allgaier 171eddd779 Shrink new section titles in more-info dialog a bit (#10414) 2021-10-26 22:19:37 +02:00
Paulus Schoutsen 7acc2f9e08 Merge remote-tracking branch 'origin/master' into dev 2021-10-26 13:13:54 -07:00
Paulus Schoutsen 27a6341137 Bumped version to 20211026.0 2021-10-26 13:12:06 -07:00
Paulus Schoutsen 6c5e15e707 Move entities to center column on device page (#10412) 2021-10-26 12:48:05 -07:00
Philip Allgaier 06b1718ade Add navigation option from more-info to history (#9717) 2021-10-26 21:12:52 +02:00
Paulus Schoutsen e50d2e16a7 Improve device info add to Lovelace (#10413) 2021-10-26 21:03:19 +02:00
Philip Allgaier 0b2404a0f2 Make device classes in logbook translatable (#10376) 2021-10-26 21:00:28 +02:00
Bram Kragten 371804591d Add blueprint scripts (#9504) 2021-10-26 09:32:40 -07:00
Bram Kragten 54c64c15f3 Bump and patch material elements (#10406) 2021-10-26 16:39:35 +02:00
Bram Kragten 0e1124cd4f Bump codemirror (#10404) 2021-10-26 16:28:13 +02:00
Bram Kragten 70fd759e18 Bump format js (#10405) 2021-10-26 14:24:14 +02:00
Bram Kragten 8e383b2bec Bump Lit (#10409) 2021-10-26 14:23:18 +02:00
Joakim Sørensen 63cd576d56 Allow configuration_url to point to an internal panel (#10395) 2021-10-26 13:24:08 +02:00
Marc Hörsken 32ac04ea78 Add support for hiding current weather in forecast card (#10267) 2021-10-26 10:18:26 +00:00
Joakim Sørensen 5d6bacb0bd Use ha-alert to warn about logs from custom integrations (#10396) 2021-10-26 12:11:27 +02:00
Tobias Kündig 398d777681 Introduced ha-icon-overflow-menu component (#10352)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-26 12:10:53 +02:00
Paulus Schoutsen 549a360d98 Update delay label (#10284) 2021-10-26 12:09:35 +02:00
MartinT 1140e6026c Add "Keep me logged in" checkbox within login flow (#10226)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-26 12:05:13 +02:00
Philip Allgaier 29a1167782 Ensure consistent card look on device config page (#10386) 2021-10-26 11:12:27 +02:00
Joakim Sørensen d61a77f2d9 Add running device class to binary sensor (#10400) 2021-10-25 23:05:35 +02:00
Philip Allgaier b9bde1960b Ensure explicit false values from customize form get stored (#10381) 2021-10-25 20:33:26 +02:00
Nathan Orick a12c2eea5d Ensure Sortable is recreated when card editors are reopened (#10382) 2021-10-25 19:49:00 +02:00
MartinT b5c717a559 Do not close edit dialog when more info is escaped (#10249) 2021-10-25 19:48:17 +02:00
Joakim Sørensen 3adbc4cfaf Use ha-chip instead of ha-label-badge for add-on capabilities (#10398) 2021-10-25 18:25:37 +02:00
Paulus Schoutsen dd11fb1b99 Add automation editor to gallery (#10392) 2021-10-25 15:53:32 +00:00
Bram Kragten bf0d102c86 Fix timezone issues with date formatting for ES5 (#10370) 2021-10-25 08:33:15 -07:00
Joakim Sørensen dad2b92d2e Use ha-chip for alarm control panel card (#10393)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-25 15:28:29 +00:00
Nathan Orick d027ec0018 Add stopPropagation to move click handlers (#10379) 2021-10-25 17:08:30 +02:00
Philip Allgaier 0c038398aa Fix various slugify() issues + add tests (#10383) 2021-10-25 16:26:38 +02:00
Raman Gupta 5c3e0cc016 Add additional properties to zwave_js device info panel (#10132) 2021-10-25 16:13:59 +02:00
Zack Barett 9bcd26ce57 Fix Full Calendar Background color (#10373) 2021-10-25 15:23:55 +02:00
Rogério Ribeiro 3e8a6c418c Update markdown card to allow word to be broken (#10387) 2021-10-25 12:43:38 +00:00
Paulus Schoutsen 279f3e1183 Trim device name from entities on device page (#10285) 2021-10-25 12:56:33 +02:00
Paulus Schoutsen f77339ad85 Make all automation type pickers use natural width to be able to show… (#10391) 2021-10-25 12:55:26 +02:00
Bram Kragten da73b316ff Remove deprecated icons that where replaced (#10371) 2021-10-25 12:12:16 +02:00
Michael Irigoyen 82a49d2cbf Update MDI to v6.4.95 (#10389) 2021-10-25 11:00:32 +02:00
Bram Kragten 05711b4636 Catch error if input_datetime state is incorrect (#10237) 2021-10-22 09:46:58 -07:00
Kyle Niewiada 2c2809573f Add to do list support to markdown (#10129) 2021-10-22 08:49:00 -07:00
Philip Allgaier bbbeafcc92 Restore proper state badge image behavior (#10369) 2021-10-22 14:09:23 +02:00
Bram Kragten 95c6adc739 Convert cloud account config to Lit (#10350) 2021-10-21 09:49:55 -07:00
Philip Allgaier 7c2e0aea92 Correct automation editor event action translation (#10355) 2021-10-21 15:14:26 +02:00
Franck Nijhof d05c76356f Add auto slider/box mode to number entity (#10272)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-10-20 22:12:44 -07:00
Paulus Schoutsen f1a0623447 Bumped version to 20211020.0 2021-10-20 16:06:35 -07:00
Bram Kragten 41d02fdb72 Replace paper progress with mwc-linear-progess (#10339) 2021-10-20 15:56:20 -07:00
Bram Kragten 52d45d482c Change dark mode input fill color (#10341) 2021-10-20 15:55:40 -07:00
Bram Kragten a0fea94db2 Add support for no-state and entity-no-longer-available statistic… (#10345) 2021-10-20 15:55:09 -07:00
Bram Kragten c3975e48d9 Tweak icon picker a bit (#10319) 2021-10-20 21:03:18 +02:00
Bram Kragten f062e13921 Use svg icons for default panels (#10342) 2021-10-20 15:33:12 +02:00
Joakim Sørensen 08ca9c9064 Use secondary-text-color for trailing icon (#10340) 2021-10-20 12:51:41 +02:00
Bram Kragten 667fd39147 Convert default state icons (#10223)
* Convert default state icons

* update

* Update cast/src/launcher/layout/hc-cast.ts

Co-authored-by: Philip Allgaier <mail@spacegaier.de>

* Update ha-config-core.js

* Update

* Finish

* Add siren icon

* FIx

* Add curtain icons

Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2021-10-20 11:10:16 +02:00
Joakim Sørensen b760e543b0 Fix overflow icon color in backup dialog (#10331)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-20 08:41:56 +00:00
Bram Kragten 760ead4860 Set default value when enabling optional value (#10247) 2021-10-19 21:45:41 -07:00
Bram Kragten 9a4cce74f0 Stack gas and solar sources (#10244) 2021-10-19 21:44:41 -07:00
Bram Kragten 7488eb782d Migrate all paper dialogs to mwc (#10333) 2021-10-19 13:56:49 -07:00
Joakim Sørensen b1e6935df9 Fix select options for add-on config (#10330) 2021-10-19 22:54:07 +02:00
Will Adler df53364d16 Correct grid neutrality card tooltip, make consistent with new colors (#10326) 2021-10-19 22:53:06 +02:00
Bram Kragten 777e6c4c72 Migrate all paper-radio elements to mwc-radio (#10327) 2021-10-19 13:42:30 -07:00
Bram Kragten e47a5effe6 Migrate all paper checkbox elements to mwc (#10329) 2021-10-19 13:31:24 -07:00
Joakim Sørensen 62d3f74513 Change unsupported reason container to software (#10325) 2021-10-19 18:37:38 +02:00
Joakim Sørensen 21e1fef0fb Use error for protection mode alert (#10315) 2021-10-19 18:37:22 +02:00
Philip Allgaier b3f8daa758 Fix ha-icon-button in ha-file-upload (#10328) 2021-10-19 16:24:11 +00:00
Will Adler 04f586721f Revise grid neutrality energy dashboard card, modify energy dashboard presentation to match (#10054)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-19 13:48:59 +02:00
uvjustin 8e22e41605 Use maxLiveSyncPlaybackRate in ha-hls-player (#10323) 2021-10-19 10:38:57 +02:00
Paul Bottein 2770d1f36b Icon Picker (#10161) 2021-10-18 22:45:21 +02:00
MartinT 403c042235 Add views dropdown and footer actions to the "move to view" dialog (#10172)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-18 20:27:00 +00:00
Philip Allgaier bdb3c04037 Ensure current active dark modes gets used for manually set themes (#10307) 2021-10-18 22:09:21 +02:00
Philip Allgaier f1cb21e7fc Fix formatting of weather extrema temperatures (#10306) 2021-10-18 22:07:16 +02:00
Allen Porter a8486eda9f Improve WebRTC stream error handling and cleanup (#10302) 2021-10-18 22:06:42 +02:00
Allen Porter d5b98d306d Remove element resize hook (#10300) 2021-10-18 22:05:38 +02:00
Joakim Sørensen bb2fe650ac Prevent mwc-list-item from opening up quick-bar (#10317) 2021-10-18 22:04:50 +02:00
Bram Kragten b576c3de40 Fix translation key energy distribution solar (#10316) 2021-10-18 14:11:41 +02:00
Allen Porter 84533b8843 Rename stream_type to frontend_stream_type (#10298) 2021-10-18 12:42:34 +02:00
Michael Irigoyen a8ff98b808 Update MDI to v6.3.95 (#10313) 2021-10-18 12:41:31 +02:00
Philip Allgaier f0062b1e67 Only render badge value if there is no icon and no image (#10310) 2021-10-18 01:39:13 +02:00
Bram Kragten 93f64de875 Fix icon buttons in Safari (#10293) 2021-10-16 23:03:26 +02:00
MartinT ec47e320d2 Unify default dashboard name (#10254)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-16 15:30:48 +00:00
Kyle Niewiada 816d5ee594 Fix energy onboarding add_solar_production button (#10275) (#10286) 2021-10-16 17:09:00 +02:00
Philip Allgaier 588f5bd6b7 Add additional binary device classes to inversion list (#10152) 2021-10-16 14:49:57 +02:00
Philip Allgaier 825ea93dba Add "capitalize" option to hui-timestamp-display (#10280) 2021-10-16 14:43:03 +02:00
Paulus Schoutsen a690a1d7bf ABC automation types + use MWC (#10287) 2021-10-16 14:41:23 +02:00
Paulus Schoutsen 9fe4c79782 Convert all warning classes to ha-alert (#10289) 2021-10-16 14:38:58 +02:00
Paulus Schoutsen 42613d6519 Disable ha-form while submitting entry flow (#10290) 2021-10-16 14:37:48 +02:00
Paulus Schoutsen 4b77910e4f Warn if iframe won't be able to load the website (#10217) 2021-10-15 09:03:51 +02:00
Paulus Schoutsen 3f2cce936c Bumped version to 20211014.0 2021-10-14 13:06:18 -07:00
Bram Kragten 6e8e9824f9 Bump mdc/mwc to 0.25.2 (#10271) 2021-10-14 11:18:32 -07:00
Paulus Schoutsen 33e1d34cb1 Add support for device configuration URL (#10251)
* Add support for device configuration URL

* Lint

* Tweak text
2021-10-14 11:17:44 -07:00
Paulus Schoutsen 48948d5854 Initial support for entity category (#10266) 2021-10-14 09:56:51 -07:00
Philip Allgaier 7fc00ce1cb Fix sizing / positioning error for trace graph node with subsequent branches (#10049) 2021-10-14 17:00:31 +02:00
Philip Allgaier 0c940be5fb Consolidate all icon button logic into <ha-icon-button> + ensure tooltip (#9230) 2021-10-14 15:44:20 +02:00
chriss158 bddb505b7f Fix missing translatable energy texts (#10230) 2021-10-14 12:28:11 +02:00
Franck Nijhof a91d25b27d Add tamper device class for binary sensor (#10268) 2021-10-14 12:22:07 +02:00
Allen Porter 4ad005f0bf Add WebRTC stream player (#10193)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-10-13 10:14:33 +02:00
Erik Montnemery 7472545204 Update demo template (#10256) 2021-10-13 10:09:28 +02:00
Joakim Sørensen 164c9c8e73 Remove "Hass.io" from translation (#10257) 2021-10-13 10:08:56 +02:00
Joakim Sørensen e52118db93 Use correct build url (#10258) 2021-10-13 10:08:10 +02:00
Joakim Sørensen 9e7acacb06 Add ha-label-badge to gallery (#10248) 2021-10-13 10:05:44 +02:00
Joakim Sørensen 56deb15bca Add netlify build script for gallery (#10253) 2021-10-12 22:47:53 +02:00
Joakim Sørensen a3d4969d7b Add ha-chip to gallery (#10252) 2021-10-12 21:19:09 +02:00
Philip Allgaier 4a00957b71 Remove "battery" device class from fixed icon list (#10246) 2021-10-12 11:40:44 +00:00
Josh McCarty 56bd731361 Handle text overflow for tabs (#10239) 2021-10-12 11:51:52 +02:00
Joakim Sørensen b6c470edf1 Add ha-bar to gallery (#10242) 2021-10-12 11:38:43 +02:00
Jack Wilsdon 5bc0feacf0 Apply flat polyfill globally (#10222) 2021-10-10 14:33:42 +02:00
Bram Kragten dc8d837e88 Add missing validation text (#10225) 2021-10-09 19:17:02 +02:00
Bram Kragten cddf6ce1f4 Bumped version to 20211007.1 2021-10-09 17:39:08 +02:00
Bram Kragten 8abb212ae7 Fix alarm panel badge (#10221) 2021-10-09 17:38:46 +02:00
Joakim Sørensen 0056d75127 Fix icon overlay for person badges (#10201) 2021-10-09 17:38:29 +02:00
Bram Kragten 5be475ea17 Fix dirty check/leaving automation editor (#10211) 2021-10-09 17:38:09 +02:00
Philip Allgaier b157cf5294 Make zone names readable on map in dark mode (#10195) 2021-10-09 17:37:45 +02:00
Philip Allgaier 48c9c89e3d Add "gas" device_class to customize (and sort existing ones) (#10196) 2021-10-09 17:37:28 +02:00
Bram Kragten 83f405b695 Fix alarm panel badge (#10221) 2021-10-09 16:17:50 +02:00
Paulus Schoutsen 9bf41a37b4 Allow disabling an ha-form (#10218) 2021-10-09 12:41:36 +02:00
Paulus Schoutsen 774f22b7e7 Convert iframe panel to Lit (#10216) 2021-10-09 12:39:37 +02:00
Joakim Sørensen aaa3964bb3 Fix icon overlay for person badges (#10201) 2021-10-09 12:30:51 +02:00
Paulus Schoutsen 6f6fc759cc Add selector demo to gallery (#10213) 2021-10-08 20:56:32 +02:00
Bram Kragten 4358b7f924 Fix dirty check/leaving automation editor (#10211) 2021-10-08 20:32:13 +02:00
Paulus Schoutsen 2841369d3d Extract black/white row into component (#10212)
* Extract black/white row into component

* Remove unused import
2021-10-08 10:48:39 -07:00
Paulus Schoutsen ad031d4bda Tweak ha-form (#10194) 2021-10-08 17:19:02 +02:00
Philip Allgaier 588ee2c3b1 Make zone names readable on map in dark mode (#10195) 2021-10-08 17:17:41 +02:00
Philip Allgaier 038033cf27 Add "gas" device_class to customize (and sort existing ones) (#10196) 2021-10-08 17:16:44 +02:00
Joakim Sørensen 84c4bbd380 Fix import (#10206) 2021-10-08 07:41:21 -07:00
Bram Kragten 807ce468d6 Dont create icon for supervisor (#10191) 2021-10-07 23:27:35 +02:00
Paulus Schoutsen a839494a1e Use MWC components for ha-form (#10120) 2021-10-07 12:21:35 -07:00
Bram Kragten 80bbc9990a Merge pull request #10190 from home-assistant/dev 2021-10-07 21:20:25 +02:00
Bram Kragten fa52442c1c Bumped version to 20211007.0 2021-10-07 21:07:37 +02:00
Bram Kragten 919ce2afb1 Fix position of home circle in energy distribution on safari (#10186) 2021-10-07 12:06:59 -07:00
Bram Kragten db55be6d33 Add start - end time to energy graph tooltip (#10185) 2021-10-07 12:06:18 -07:00
Bram Kragten 2dc7c1afed Fix unsupported_unit_metadata text in stats dev tools (#10183)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2021-10-07 12:05:45 -07:00
Bram Kragten 85956dc7fd Fix error in reduceSumStatisticsByDay (#10170) 2021-10-07 15:26:41 +02:00
Joakim Sørensen 910cd98a38 Fix missing add-on rating (#10184) 2021-10-07 10:53:22 +00:00
Bram Kragten 8022bd2868 Guard icon db check on hassio (#10181) 2021-10-07 10:31:47 +00:00
Bram Kragten d5ca7e1719 Remove ha-icon from ha-label-badge (#10182) 2021-10-07 10:25:15 +00:00
Joakim Sørensen 066a0771b3 Move functions to common-translation (#10180) 2021-10-07 11:02:52 +02:00
Bram Kragten 9e35c1ab68 Make sure we have no ha-icon in supervisor (#10176) 2021-10-06 22:41:37 +00:00
Philip Allgaier fb1deb838c Add title property to elements showing entity names (#9264) 2021-10-06 17:41:37 +02:00
Bram Kragten 8e010618bb Show correct units for prices in energy gas settings (#10164) 2021-10-06 17:38:32 +02:00
Bram Kragten 365cf1f7ef Break lines in error card (#10169) 2021-10-06 07:53:40 -05:00
Philip Allgaier b226b20e3d Prevent computeDomain() call if no entity selected (#10166) 2021-10-06 14:07:18 +02:00
Bram Kragten ec21f4c2c6 Capitalize relative time strings (#10165) 2021-10-06 13:56:52 +02:00
Philip Allgaier a696d849b2 Add missing translations to statistics fixing (#10159) 2021-10-06 08:38:44 +00:00
Philip Allgaier ea3fae2ce4 Make add-on sorting case insensitive (#10061) 2021-10-06 10:33:15 +02:00
Bram Kragten 736e117eca Merge pull request #10162 from home-assistant/dev 2021-10-06 10:18:40 +02:00
Bram Kragten 2fb3ac74eb Add total and total increasing state class 2021-10-06 09:57:03 +02:00
Bram Kragten 2d5c8ec3e9 Bumped version to 20211006.0 2021-10-06 09:43:23 +02:00
Bram Kragten 25c1156c88 Some code improvements (#10156) 2021-10-05 21:21:05 -07:00
Bram Kragten c44624282c Fix lint warnings (#10157) 2021-10-05 18:11:02 +02:00
Bram Kragten 370f2eb9e4 Add no issues text to stats dev tools (#10158) 2021-10-05 17:58:56 +02:00
Paulus Schoutsen 1793c68aae Split price validation errors (#10155) 2021-10-04 21:04:45 -07:00
Bram Kragten 5e52bd905d Merge pull request #10154 from home-assistant/dev 2021-10-05 00:00:33 +02:00
Bram Kragten cba6bbdc74 Bumped version to 20211004.0 2021-10-04 23:43:13 +02:00
Bram Kragten 6f4593508b More statistics validation (#10146)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2021-10-04 14:21:21 -07:00
Bram Kragten dc3bad56f2 Improve padding/positioning of ha-alert (#10145)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-10-04 16:27:03 +00:00
Joakim Sørensen 784e5e6e39 Add more secret string fields (#10149) 2021-10-04 08:42:18 -07:00
Michael Irigoyen 13fe62975d Update MDI to v6.2.95 (#10142) 2021-10-04 10:33:25 +02:00
Tobias Kündig b97fd9918a Add decorator to translationFragment property (#10143) 2021-10-04 10:32:53 +02:00
uvjustin dc56c2de52 Bump hls.js to 1.0.11 (#10144) 2021-10-04 10:29:58 +02:00
Philip Allgaier 375a5323d5 Prevent wrong colors in history timeline for inverted unavailable states (#10137) 2021-10-03 11:35:53 -07:00
Bram Kragten 31b69147f4 Merge pull request #10136 from home-assistant/dev 2021-10-02 22:41:19 +02:00
Bram Kragten 8e3011807d Bumped version to 20211002.0 2021-10-02 22:29:56 +02:00
Bram Kragten ec7c6ab96c Add if node is secure to zwave js device page (#10135) 2021-10-02 10:20:16 -07:00
Bram Kragten 8a4097a366 Fix labels device energy graph (#10134)
* Fix labels device energy graph

* prettier
2021-10-02 08:06:05 -07:00
Bram Kragten 792a736e48 Fix fix stats callback (#10123)
* Fix `fix stats` callback

* memoize
2021-10-02 08:05:30 -07:00
Paulus Schoutsen cce0a02ebb Add My support for statistics (#10131) 2021-10-02 10:15:26 +02:00
Paulus Schoutsen 2ddab4eecc Fix webpack dev server (#10130) 2021-10-01 14:18:53 -07:00
Kyle Niewiada f66755cbf1 Fix inverted motion chart colors (#10128) 2021-10-01 20:12:05 +02:00
Bram Kragten 257e60a2b1 Don't bundle locale data, but add to static (#10119) 2021-10-01 07:58:02 -07:00
Philip Allgaier 75a3566760 Fixed typo in unsupported unit statistics dialog (#10118) 2021-10-01 00:00:20 +02:00
Joakim Sørensen 7a9f17e059 Use heading property for data disk dialog (#10115) 2021-09-30 19:08:51 +02:00
Joakim Sørensen abbfe7200a Fix supervisor dev translations (#10113) 2021-09-30 09:01:36 -07:00
Paulus Schoutsen 419942112b Fix Lit lint warnings (#10112) 2021-09-30 08:46:03 -07:00
Bram Kragten 597d4a0426 Use const enums where possible (#10110) 2021-09-30 07:44:28 -07:00
Bram Kragten e023d60be7 exclude a bunch of polyfill locales (#10111) 2021-09-30 07:43:46 -07:00
Bram Kragten bc5010a953 Merge pull request #10109 from home-assistant/dev
20210930.0
2021-09-30 12:57:53 +02:00
Bram Kragten 41a7b42037 Bumped version to 20210930.0 2021-09-30 12:41:35 +02:00
Bram Kragten 2936865c55 Bump typescript, lint, prettier (#10108) 2021-09-30 12:39:03 +02:00
smonesi ff2bf1f3c1 Local images flagged as already loaded to avoid flickering/slow-down (#10086) 2021-09-30 08:14:08 +00:00
Bram Kragten 1bccbd4173 Use browser default time and number formatting with polyfills if needed (#9481)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-09-29 23:34:52 +00:00
Bram Kragten d7f00df391 Bump yarn to v3 (#10104) 2021-09-29 15:57:50 -07:00
Bram Kragten 22f88c59c7 Bump lit and mwc (#10103) 2021-09-29 15:09:43 -07:00
Bram Kragten 8721776839 Add migration wizard for zwave -> zwave_js (#10097) 2021-09-29 08:55:20 -07:00
craiggenner a89da0dac0 add 'allow-download' to iframe sandbox for chrome (#9490) 2021-09-29 14:40:26 +02:00
Bram Kragten e4b4dc4ae9 Allow gas to be in kWh (#10075)
* Allow gas to be in kWh

* Extract some gas unit helpers

* Forgot to save a refactor

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-09-29 10:33:48 +02:00
Bram Kragten b26c44b2b9 Add S2 support to Z wave JS (#10090)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: kpine <keith.pine@gmail.com>
2021-09-28 15:37:32 -07:00
Bram Kragten 68095417b9 Fix tooltip and click action on device energy graph (#10094) 2021-09-28 15:21:47 -07:00
Joakim Sørensen b6344eb6e8 Fix link to os_agent (#10093) 2021-09-28 09:09:27 +02:00
Paulus Schoutsen 224302cfef Simplify remote connection preferences (#10071)
* Simplify remote connection preferences

* Remove unused CSS and strings
2021-09-27 22:28:43 -07:00
Bram Kragten abc4816888 Only show entities in energy with energy device class (#10076) 2021-09-27 17:56:56 -07:00
Bram Kragten 21e14bd644 Fix energy device graph when multiple entities have same name (#10092) 2021-09-27 17:55:46 -07:00
Bram Kragten a89caccd32 Statistics dev tools (#10074)
* Statistics dev tools

* Show all statistics

* Update src/data/history.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Update history.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-09-27 22:03:57 +02:00
Joakim Sørensen 03dc3e52b7 Add missing unsupported links (#10080) 2021-09-23 15:08:51 +02:00
Paulus Schoutsen f04be8efa6 Hide hassio integration during onboarding (#10079) 2021-09-23 08:12:04 +02:00
Paulus Schoutsen 2c32f6bcb3 Bumped version to 20210922.0 2021-09-22 14:20:23 -07:00
Joakim Sørensen a3a08ff5c7 Add dialog to trigger moving datadisk (#10048)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-22 13:37:21 -07:00
Jefferson Bledsoe ea51186767 Update delete dashboard dialog to be more descriptive (#10051)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-09-22 11:38:21 +02:00
Philip Allgaier 49494c572b Fix card deletion dialog buttons out of view (#9992)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-20 16:32:41 +02:00
Philip Allgaier fcac3fa164 Convert suggest-card dialog to ha-dialog (#10000)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-20 16:32:13 +02:00
Jaroslav Hanslík 9c1153ef37 Unified alarm panel icons everywhere (#10021) 2021-09-20 11:06:31 +00:00
Philip Allgaier 0adc4b33ef Align "option" wording and show user-friendly option index in trace timeline (#10059) 2021-09-20 12:59:30 +02:00
Will Adler c0f3215340 Clarify carbon-consumed energy dashboard card tooltip (#10053) 2021-09-20 12:52:20 +02:00
Will Adler bab1e6a95f Revise solar-consumed energy dashboard card tooltip, fix typos (#10052) 2021-09-20 12:51:47 +02:00
Erik Montnemery 53b26a43c0 Update translation strings for energy validator (#10037)
* Update translation strings for energy validator

* Update src/translations/en.json

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

* Update en.json

* Update src/translations/en.json

Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>
2021-09-20 12:38:16 +02:00
Bram Kragten 49947f3337 Merge pull request #9915 from home-assistant/dev 2021-08-30 22:35:41 +02:00
Bram Kragten d3ce4af541 Merge pull request #9761 from home-assistant/dev
20210809.0
2021-08-09 21:00:41 +02:00
Bram Kragten d45f47d908 Merge pull request #9715 from home-assistant/dev
20210804.0
2021-08-04 23:53:37 +02:00
812 changed files with 39306 additions and 19977 deletions
+5 -2
View File
@@ -1,9 +1,10 @@
{
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended",
"plugin:wc/recommended",
"plugin:lit/recommended",
"plugin:lit/all",
"prettier"
],
"parser": "@typescript-eslint/parser",
@@ -28,6 +29,7 @@
"__BUILD__": false,
"__VERSION__": false,
"__STATIC_PATH__": false,
"__SUPERVISOR__": false,
"Polymer": true
},
"env": {
@@ -109,7 +111,8 @@
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off"
"lit/attribute-value-entities": "off",
"lit/no-template-map": "off"
},
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable"
+6 -4
View File
@@ -12,7 +12,7 @@ on:
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
NODE_OPTIONS: --max_old_space_size=6144
jobs:
lint:
@@ -30,7 +30,7 @@ jobs:
env:
CI: true
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-demos
- name: Run eslint
run: yarn run lint:eslint
- name: Run tsc
@@ -53,8 +53,10 @@ jobs:
run: yarn install
env:
CI: true
- name: Run Mocha
run: yarn run mocha
- name: Build resources
run: ./node_modules/.bin/gulp build-translations build-locale-data
- name: Run Tests
run: yarn run test
build:
runs-on: ubuntu-latest
needs: [lint, test]
+1 -1
View File
@@ -7,7 +7,7 @@ on:
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy:
+1 -1
View File
@@ -8,7 +8,7 @@ on:
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
NODE_OPTIONS: --max_old_space_size=6144
jobs:
release:
-1
View File
@@ -3,7 +3,6 @@
# build
build
build-translations/*
hass_frontend/*
dist
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
require: "test-mocha/testconf.js",
timeout: 10000,
};
-1
View File
@@ -1,5 +1,4 @@
build
build-translations/*
translations/*
node_modules/*
hass_frontend/*
@@ -0,0 +1,12 @@
diff --git a/mwc-icon-button-base.js b/mwc-icon-button-base.js
index 45cdaab93ccc0a6daaaaabc01266dcdc32e46bfd..b3ea5b541597308d85f86ce6c23fd00785fda835 100644
--- a/mwc-icon-button-base.js
+++ b/mwc-icon-button-base.js
@@ -63,7 +63,6 @@ export class IconButtonBase extends LitElement {
@touchend="${this.handleRippleDeactivate}"
@touchcancel="${this.handleRippleDeactivate}"
>${this.renderRipple()}
- <i class="material-icons">${this.icon}</i>
<span
><slot></slot
></span>
-55
View File
File diff suppressed because one or more lines are too long
+631
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,4 +6,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.2.cjs
yarnPath: .yarn/releases/yarn-3.0.2.cjs
+9
View File
@@ -35,6 +35,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()),
__DEMO__: false,
__SUPERVISOR__: false,
__BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
@@ -82,6 +83,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
// Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
@@ -163,6 +165,7 @@ module.exports.config = {
cast({ isProdBuild, latestBuild }) {
const entry = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
};
if (latestBuild) {
@@ -193,6 +196,9 @@ module.exports.config = {
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild,
latestBuild,
defineOverlay: {
__SUPERVISOR__: true,
},
};
},
@@ -205,6 +211,9 @@ module.exports.config = {
publicPath: publicPath(latestBuild),
isProdBuild,
latestBuild,
defineOverlay: {
__DEMO__: true,
},
};
},
};
+4 -2
View File
@@ -5,6 +5,7 @@ const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./locale-data.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./compress.js");
@@ -26,7 +27,8 @@ gulp.task(
"gen-icons-json",
"gen-pages-dev",
"gen-index-app-dev",
"build-translations"
"build-translations",
"build-locale-data"
),
"copy-static-app",
env.useWDS()
@@ -44,7 +46,7 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests
+2 -2
View File
@@ -18,7 +18,7 @@ gulp.task(
},
"clean-cast",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast",
"gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
@@ -33,7 +33,7 @@ gulp.task(
},
"clean-cast",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast",
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
"gen-index-cast-prod"
+7 -2
View File
@@ -20,7 +20,12 @@ gulp.task(
},
"clean-demo",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
gulp.parallel(
"gen-icons-json",
"gen-index-demo-dev",
"build-translations",
"build-locale-data"
),
"copy-static-demo",
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
)
@@ -35,7 +40,7 @@ gulp.task(
"clean-demo",
// Cast needs to be backwards compatible and older HA has no translations
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-demo",
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
"gen-index-demo-prod"
+18
View File
@@ -154,6 +154,15 @@ gulp.task("gen-index-cast-dev", (done) => {
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: "/frontend_latest/media.js",
es5MediaJS: "/frontend_es5/media.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
@@ -192,6 +201,15 @@ gulp.task("gen-index-cast-prod", (done) => {
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: latestManifest["media.js"],
es5MediaJS: es5Manifest["media.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
+2
View File
@@ -51,6 +51,7 @@ gulp.task(
gulp.parallel(
"gen-icons-json",
"build-translations",
"build-locale-data",
"gather-gallery-demos"
),
"copy-static-gallery",
@@ -70,6 +71,7 @@ gulp.task(
gulp.parallel(
"gen-icons-json",
"build-translations",
"build-locale-data",
"gather-gallery-demos"
),
"copy-static-gallery",
+30 -1
View File
@@ -22,11 +22,18 @@ function copyTranslations(staticDir) {
// Translation output
fs.copySync(
polyPath("build-translations/output"),
polyPath("build/translations/output"),
staticPath("translations")
);
}
function copyLocaleData(staticDir) {
const staticPath = genStaticPath(staticDir);
// Locale data output
fs.copySync(polyPath("build/locale-data"), staticPath("locale-data"));
}
function copyMdiIcons(staticDir) {
const staticPath = genStaticPath(staticDir);
@@ -72,6 +79,11 @@ function copyFonts(staticDir) {
);
}
function copyQrScannerWorker(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(npmPath("qr-scanner/qr-scanner-worker.min.js"), staticPath("js"));
}
function copyMapPanel(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(
@@ -84,6 +96,11 @@ function copyMapPanel(staticDir) {
);
}
gulp.task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-translations-app", async () => {
const staticDir = paths.app_output_static;
copyTranslations(staticDir);
@@ -94,6 +111,11 @@ gulp.task("copy-translations-supervisor", async () => {
copyTranslations(staticDir);
});
gulp.task("copy-locale-data-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static;
// Basic static files
@@ -103,10 +125,14 @@ gulp.task("copy-static-app", async () => {
copyPolyfills(staticDir);
copyFonts(staticDir);
copyTranslations(staticDir);
copyLocaleData(staticDir);
copyMdiIcons(staticDir);
// Panel assets
copyMapPanel(staticDir);
// Qr Scanner assets
copyQrScannerWorker(staticDir);
});
gulp.task("copy-static-demo", async () => {
@@ -123,6 +149,7 @@ gulp.task("copy-static-demo", async () => {
copyMapPanel(paths.demo_output_static);
copyFonts(paths.demo_output_static);
copyTranslations(paths.demo_output_static);
copyLocaleData(paths.demo_output_static);
copyMdiIcons(paths.demo_output_static);
});
@@ -137,6 +164,7 @@ gulp.task("copy-static-cast", async () => {
copyMapPanel(paths.cast_output_static);
copyFonts(paths.cast_output_static);
copyTranslations(paths.cast_output_static);
copyLocaleData(paths.cast_output_static);
copyMdiIcons(paths.cast_output_static);
});
@@ -152,5 +180,6 @@ gulp.task("copy-static-gallery", async () => {
copyMapPanel(paths.gallery_output_static);
copyFonts(paths.gallery_output_static);
copyTranslations(paths.gallery_output_static);
copyLocaleData(paths.gallery_output_static);
copyMdiIcons(paths.gallery_output_static);
});
+42 -4
View File
@@ -22,17 +22,40 @@ const getMeta = () => {
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
encoding,
});
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
return {
path: svg.match(/ d="([^"]+)"/)[1],
name: icon.name,
tags: icon.tags,
aliases: icon.aliases,
};
});
};
const addRemovedMeta = (meta) => {
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
const removed = JSON.parse(file);
const combinedMeta = [...meta, ...removed];
const removedMeta = removed.map((removeIcon) => ({
path: removeIcon.path,
name: removeIcon.name,
tags: [],
aliases: [],
}));
const combinedMeta = [...meta, ...removedMeta];
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
};
const homeAutomationTag = "Home Automation";
const orderMeta = (meta) => {
const homeAutomationMeta = meta.filter((icon) =>
icon.tags.includes(homeAutomationTag)
);
const otherMeta = meta.filter(
(icon) => !icon.tags.includes(homeAutomationTag)
);
return [...homeAutomationMeta, ...otherMeta];
};
const splitBySize = (meta) => {
const chunks = [];
const CHUNK_SIZE = 50000;
@@ -77,8 +100,10 @@ const findDifferentiator = (curString, prevString) => {
};
gulp.task("gen-icons-json", (done) => {
const meta = addRemovedMeta(getMeta());
const split = splitBySize(meta);
const meta = getMeta();
const metaAndRemoved = addRemovedMeta(meta);
const split = splitBySize(metaAndRemoved);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
@@ -116,5 +141,18 @@ gulp.task("gen-icons-json", (done) => {
JSON.stringify({ version: package.version, parts })
);
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconList.json"),
JSON.stringify(
orderMeta(meta).map((icon) => ({
name: icon.name,
keywords: [
...icon.tags.map((t) => t.toLowerCase().replace(/\s\/\s/g, " ")),
...icon.aliases,
],
}))
)
);
done();
});
+4 -5
View File
@@ -1,9 +1,6 @@
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env");
const paths = require("../paths");
require("./clean.js");
require("./gen-icons-json.js");
@@ -20,10 +17,11 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-hassio",
"gen-icons-json",
"gen-index-hassio-dev",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
)
);
@@ -35,9 +33,10 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-hassio",
"gen-icons-json",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod",
...// Don't compress running tests
+74
View File
@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const del = require("del");
const path = require("path");
const gulp = require("gulp");
const fs = require("fs");
const paths = require("../paths");
const outDir = "build/locale-data";
gulp.task("clean-locale-data", () => del([outDir]));
gulp.task("ensure-locale-data-build-dir", (done) => {
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
done();
});
const modules = {
"intl-relativetimeformat": "RelativeTimeFormat",
"intl-datetimeformat": "DateTimeFormat",
"intl-numberformat": "NumberFormat",
};
gulp.task("create-locale-data", (done) => {
const translationMeta = JSON.parse(
fs.readFileSync(
path.join(paths.translations_src, "translationMetadata.json")
)
);
Object.entries(modules).forEach(([module, className]) => {
Object.keys(translationMeta).forEach((lang) => {
try {
const localeData = String(
fs.readFileSync(
require.resolve(`@formatjs/${module}/locale-data/${lang}.js`)
)
)
.replace(
new RegExp(
`\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`,
"im"
),
""
)
.replace(/\)\s*}/im, "");
// make sure we have valid JSON
JSON.parse(localeData);
if (!fs.existsSync(path.join(outDir, module))) {
fs.mkdirSync(path.join(outDir, module), { recursive: true });
}
fs.writeFileSync(
path.join(outDir, `${module}/${lang}.json`),
localeData
);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
}
});
done();
});
});
gulp.task(
"build-locale-data",
gulp.series(
"clean-locale-data",
"ensure-locale-data-build-dir",
"create-locale-data"
)
);
+10 -2
View File
@@ -17,7 +17,7 @@ const paths = require("../paths");
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";
const workDir = "build-translations";
const workDir = "build/translations";
const fullDir = workDir + "/full";
const coreDir = workDir + "/core";
const outDir = workDir + "/output";
@@ -121,7 +121,7 @@ gulp.task("clean-translations", () => del([workDir]));
gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) {
fs.mkdirSync(workDir);
fs.mkdirSync(workDir, { recursive: true });
}
done();
});
@@ -336,6 +336,14 @@ gulp.task("build-translation-fragment-supervisor", () =>
gulp
.src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor))
.pipe(
rename((filePath) => {
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(workDir + "/supervisor"))
);
+30 -26
View File
@@ -35,26 +35,29 @@ const isWsl =
* listenHost?: string
* }}
*/
const runDevServer = ({
const runDevServer = async ({
compiler,
contentBase,
port,
listenHost = "localhost",
}) =>
new WebpackDevServer(compiler, {
open: true,
watchContentBase: true,
contentBase,
}).listen(port, listenHost, (err) => {
if (err) {
throw err;
}
// Server listening
log(
"[webpack-dev-server]",
`Project is running at http://localhost:${port}`
);
});
}) => {
const server = new WebpackDevServer(
{
open: true,
host: listenHost,
port,
static: {
directory: contentBase,
watch: true,
},
},
compiler
);
await server.start();
// Server listening
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`);
};
const doneHandler = (done) => (err, stats) => {
if (err) {
@@ -107,13 +110,13 @@ gulp.task("webpack-prod-app", () =>
)
);
gulp.task("webpack-dev-server-demo", () => {
gulp.task("webpack-dev-server-demo", () =>
runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
contentBase: paths.demo_output_root,
port: 8090,
});
});
})
);
gulp.task("webpack-prod-demo", () =>
prodBuild(
@@ -123,15 +126,15 @@ gulp.task("webpack-prod-demo", () =>
)
);
gulp.task("webpack-dev-server-cast", () => {
gulp.task("webpack-dev-server-cast", () =>
runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
contentBase: paths.cast_output_root,
port: 8080,
// Accessible from the network, because that's how Cast hits it.
listenHost: "0.0.0.0",
});
});
})
);
gulp.task("webpack-prod-cast", () =>
prodBuild(
@@ -148,7 +151,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false,
latestBuild: true,
})
).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
).watch({ ignored: /build/, poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
@@ -164,14 +167,15 @@ gulp.task("webpack-prod-hassio", () =>
)
);
gulp.task("webpack-dev-server-gallery", () => {
gulp.task("webpack-dev-server-gallery", () =>
runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
contentBase: paths.gallery_output_root,
port: 8100,
});
});
listenHost: "0.0.0.0",
})
);
gulp.task("webpack-prod-gallery", () =>
prodBuild(
File diff suppressed because one or more lines are too long
+12
View File
@@ -6,6 +6,7 @@ const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const bundle = require("./bundle.js");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
class LogStartCompilePlugin {
ignoredFirst = false;
@@ -74,6 +75,7 @@ const createWebpackConfig = ({
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
@@ -125,6 +127,13 @@ const createWebpackConfig = ({
alias: {
"lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js",
"lit/directives/until$": "lit/directives/until.js",
"lit/directives/class-map$": "lit/directives/class-map.js",
"lit/directives/style-map$": "lit/directives/style-map.js",
"lit/directives/if-defined$": "lit/directives/if-defined.js",
"lit/directives/guard$": "lit/directives/guard.js",
"lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
},
},
@@ -142,6 +151,9 @@ const createWebpackConfig = ({
// To silence warning in worker plugin
globalObject: "self",
},
experiments: {
topLevelAwait: true,
},
};
};
+46
View File
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
<style>
body {
--logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
--logo-repeat: no-repeat;
--playback-logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
--theme-hue: 200;
--progress-color: #03a9f4;
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
--splash-size: cover;
--background-color: #41bdf5;
}
</style>
<script>
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
</script>
</head>
<body>
<%= renderTemplate('_js_base') %>
<cast-media-player></cast-media-player>
<script>
import("<%= latestMediaJS %>");
window.latestJS = true;
</script>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5MediaJS %>");
};
<% } else { %>
_ls("<%= es5MediaJS %>");
<% } %>
}
</script>
</body>
</html>
+6 -4
View File
@@ -1,4 +1,5 @@
import "@material/mwc-button/mwc-button";
import { mdiCast, mdiCastConnected } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket";
@@ -17,6 +18,7 @@ import {
import { atLeastVersion } from "../../../../src/common/config/version";
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
import "../../../../src/components/ha-icon";
import "../../../../src/components/ha-svg-icon";
import {
getLegacyLovelaceCollection,
getLovelaceCollection,
@@ -73,7 +75,7 @@ class HcCast extends LitElement {
? html`
<p class="center-item">
<mwc-button raised @click=${this._handleLaunch}>
<ha-icon icon="hass:cast"></ha-icon>
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
Start Casting
</mwc-button>
</p>
@@ -111,7 +113,7 @@ class HcCast extends LitElement {
${this.castManager.status
? html`
<mwc-button @click=${this._handleLaunch}>
<ha-icon icon="hass:cast-connected"></ha-icon>
<ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon>
Manage
</mwc-button>
`
@@ -191,7 +193,7 @@ class HcCast extends LitElement {
}
this.connection.close();
location.reload();
} catch (err) {
} catch (err: any) {
alert("Unable to log out!");
}
}
@@ -233,7 +235,7 @@ class HcCast extends LitElement {
color: var(--secondary-text-color);
}
mwc-button ha-icon {
mwc-button ha-svg-icon {
margin-right: 8px;
height: 18px;
}
+12 -11
View File
@@ -1,4 +1,5 @@
import "@material/mwc-button";
import { mdiCastConnected, mdiCast } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import {
Auth,
@@ -19,7 +20,7 @@ import {
loadTokens,
saveTokens,
} from "../../../../src/common/auth/token_storage";
import "../../../../src/components/ha-icon";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/layouts/hass-loading-screen";
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
import "./hc-layout";
@@ -127,11 +128,11 @@ export class HcConnect extends LitElement {
<div class="card-actions">
<mwc-button @click=${this._handleDemo}>
Show Demo
<ha-icon
.icon=${this.castManager.castState === "CONNECTED"
? "hass:cast-connected"
: "hass:cast"}
></ha-icon>
<ha-svg-icon
.path=${this.castManager.castState === "CONNECTED"
? mdiCastConnected
: mdiCast}
></ha-svg-icon>
</mwc-button>
<div class="spacer"></div>
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
@@ -212,7 +213,7 @@ export class HcConnect extends LitElement {
let url: URL;
try {
url = new URL(value);
} catch (err) {
} catch (err: any) {
this.error = "Invalid URL";
return;
}
@@ -240,7 +241,7 @@ export class HcConnect extends LitElement {
try {
this.loading = true;
auth = await getAuth(options);
} catch (err) {
} catch (err: any) {
if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) {
this.cannotConnect = true;
return;
@@ -259,7 +260,7 @@ export class HcConnect extends LitElement {
try {
conn = await createConnection({ auth });
} catch (err) {
} catch (err: any) {
// In case of saved tokens, silently solve problems.
if (init === "saved-tokens") {
if (err === ERR_CANNOT_CONNECT) {
@@ -285,7 +286,7 @@ export class HcConnect extends LitElement {
try {
saveTokens(null);
location.reload();
} catch (err) {
} catch (err: any) {
alert("Unable to log out!");
}
}
@@ -307,7 +308,7 @@ export class HcConnect extends LitElement {
color: darkred;
}
mwc-button ha-icon {
mwc-button ha-svg-icon {
margin-left: 8px;
}
+22
View File
@@ -0,0 +1,22 @@
const castContext = cast.framework.CastReceiverContext.getInstance();
const playerManager = castContext.getPlayerManager();
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD,
(loadRequestData) => {
const media = loadRequestData.media;
// Special handling if it came from Google Assistant
if (media.entity) {
media.contentId = media.entity;
media.streamType = cast.framework.messages.StreamType.LIVE;
media.contentType = "application/vnd.apple.mpegurl";
// @ts-ignore
media.hlsVideoSegmentFormat =
cast.framework.messages.HlsVideoSegmentFormat.FMP4;
}
return loadRequestData;
}
);
castContext.start();
+35 -16
View File
@@ -8,6 +8,9 @@ import { ReceivedMessage } from "./types";
const lovelaceController = new HcMain();
document.body.append(lovelaceController);
lovelaceController.addEventListener("cast-view-changed", (ev) => {
playDummyMedia(ev.detail.title);
});
const mediaPlayer = document.createElement("cast-media-player");
mediaPlayer.style.display = "none";
@@ -28,6 +31,31 @@ const setTouchControlsVisibility = (visible: boolean) => {
}
};
let timeOut: number | undefined;
const playDummyMedia = (viewTitle?: string) => {
const loadRequestData = new cast.framework.messages.LoadRequestData();
loadRequestData.autoplay = true;
loadRequestData.media = new cast.framework.messages.MediaInformation();
loadRequestData.media.contentId =
"https://cast.home-assistant.io/images/google-nest-hub.png";
loadRequestData.media.contentType = "image/jpeg";
loadRequestData.media.streamType = cast.framework.messages.StreamType.NONE;
const metadata = new cast.framework.messages.GenericMediaMetadata();
metadata.title = viewTitle;
loadRequestData.media.metadata = metadata;
loadRequestData.requestId = 0;
playerManager.load(loadRequestData);
if (timeOut) {
clearTimeout(timeOut);
timeOut = undefined;
}
if (castContext.getDeviceCapabilities().touch_input_supported) {
timeOut = window.setTimeout(() => playDummyMedia(viewTitle), 540000); // repeat every 9 minutes to keep it active (gets deactivated after 10 minutes)
}
};
const showLovelaceController = () => {
mediaPlayer.style.display = "none";
lovelaceController.style.display = "initial";
@@ -51,6 +79,7 @@ const showMediaPlayer = () => {
--progress-color: #03a9f4;
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
--splash-size: cover;
--background-color: #41bdf5;
}
`;
document.head.appendChild(style);
@@ -63,22 +92,6 @@ options.customNamespaces = {
[CAST_NS]: cast.framework.system.MessageType.JSON,
};
// The docs say we need to set options.touchScreenOptimizeApp = true
// https://developers.google.com/cast/docs/caf_receiver/customize_ui#accessing_ui_controls
// This doesn't work.
// @ts-ignore
options.touchScreenOptimizedApp = true;
// The class reference say we can set a uiConfig in options to set it
// https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.CastReceiverOptions#uiConfig
// This doesn't work either.
// @ts-ignore
options.uiConfig = new cast.framework.ui.UiConfig();
// @ts-ignore
options.uiConfig.touchScreenOptimizedApp = true;
castContext.setInactivityTimeout(86400); // 1 day
castContext.addCustomMessageListener(
CAST_NS,
// @ts-ignore
@@ -103,6 +116,12 @@ const playerManager = castContext.getPlayerManager();
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD,
(loadRequestData) => {
if (
loadRequestData.media.contentId ===
"https://cast.home-assistant.io/images/google-nest-hub.png"
) {
return loadRequestData;
}
// We received a play media command, hide Lovelace and show media player
showMediaPlayer();
const media = loadRequestData.media;
+25 -2
View File
@@ -1,5 +1,6 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view";
@@ -14,7 +15,7 @@ class HcLovelace extends LitElement {
@property() public viewPath?: string | number;
public urlPath?: string | null;
@property() public urlPath: string | null = null;
protected render(): TemplateResult {
const index = this._viewIndex;
@@ -30,7 +31,7 @@ class HcLovelace extends LitElement {
config: this.lovelaceConfig,
rawConfig: this.lovelaceConfig,
editMode: false,
urlPath: this.urlPath!,
urlPath: this.urlPath,
enableFullEditMode: () => undefined,
mode: "storage",
locale: this.hass.locale,
@@ -54,6 +55,21 @@ class HcLovelace extends LitElement {
const index = this._viewIndex;
if (index !== undefined) {
const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
const viewTitle =
this.lovelaceConfig.views[index].title ||
this.lovelaceConfig.views[index].path;
fireEvent(this, "cast-view-changed", {
title:
dashboardTitle || viewTitle
? `${dashboardTitle || ""}${
dashboardTitle && viewTitle ? ": " : ""
}${viewTitle || ""}`
: undefined,
});
const configBackground =
this.lovelaceConfig.views[index].background ||
this.lovelaceConfig.background;
@@ -101,8 +117,15 @@ class HcLovelace extends LitElement {
}
}
export interface CastViewChanged {
title: string | undefined;
}
declare global {
interface HTMLElementTagNameMap {
"hc-lovelace": HcLovelace;
}
interface HASSDomEvents {
"cast-view-changed": CastViewChanged;
}
}
+64 -18
View File
@@ -13,7 +13,11 @@ import {
ShowDemoMessage,
ShowLovelaceViewMessage,
} from "../../../../src/cast/receiver_messages";
import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
import {
ReceiverErrorCode,
ReceiverErrorMessage,
ReceiverStatusMessage,
} from "../../../../src/cast/sender_messages";
import { atLeastVersion } from "../../../../src/common/config/version";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import {
@@ -40,9 +44,9 @@ export class HcMain extends HassElement {
@state() private _error?: string;
private _unsubLovelace?: UnsubscribeFunc;
@state() private _urlPath?: string | null;
private _urlPath?: string | null;
private _unsubLovelace?: UnsubscribeFunc;
public processIncomingMessage(msg: HassMessage) {
if (msg.type === "connect") {
@@ -68,8 +72,10 @@ export class HcMain extends HassElement {
!this._lovelaceConfig ||
this._lovelacePath === null ||
// Guard against part of HA not being loaded yet.
(this.hass &&
(!this.hass.states || !this.hass.config || !this.hass.services))
!this.hass ||
!this.hass.states ||
!this.hass.config ||
!this.hass.services
) {
return html`
<hc-launch-screen
@@ -107,6 +113,7 @@ export class HcMain extends HassElement {
this._sendStatus();
}
});
this.addEventListener("dialog-closed", this._dialogClosed);
}
private _sendStatus(senderId?: string) {
@@ -118,7 +125,7 @@ export class HcMain extends HassElement {
if (this.hass) {
status.hassUrl = this.hass.auth.data.hassUrl;
status.lovelacePath = this._lovelacePath!;
status.lovelacePath = this._lovelacePath;
status.urlPath = this._urlPath;
}
@@ -131,6 +138,30 @@ export class HcMain extends HassElement {
}
}
private _sendError(
error_code: number,
error_message: string,
senderId?: string
) {
const error: ReceiverErrorMessage = {
type: "receiver_error",
error_code,
error_message,
};
if (senderId) {
this.sendMessage(senderId, error);
} else {
for (const sender of castContext.getSenders()) {
this.sendMessage(sender.id, error);
}
}
}
private _dialogClosed = () => {
document.body.setAttribute("style", "overflow-y: auto !important");
};
private async _handleGetStatusMessage(msg: GetStatusMessage) {
this._sendStatus(msg.senderId!);
}
@@ -148,15 +179,19 @@ export class HcMain extends HassElement {
expires_in: 0,
}),
});
} catch (err) {
this._error = this._getErrorMessage(err);
} catch (err: any) {
const errorMessage = this._getErrorMessage(err);
this._error = errorMessage;
this._sendError(err, errorMessage);
return;
}
let connection;
try {
connection = await createConnection({ auth });
} catch (err) {
this._error = this._getErrorMessage(err);
} catch (err: any) {
const errorMessage = this._getErrorMessage(err);
this._error = errorMessage;
this._sendError(err, errorMessage);
return;
}
if (this.hass) {
@@ -168,24 +203,29 @@ export class HcMain extends HassElement {
}
private async _handleShowLovelaceMessage(msg: ShowLovelaceViewMessage) {
this._showDemo = false;
// We should not get this command before we are connected.
// Means a client got out of sync. Let's send status to them.
if (!this.hass) {
this._sendStatus(msg.senderId!);
this._error = "Cannot show Lovelace because we're not connected.";
this._sendError(ReceiverErrorCode.NOT_CONNECTED, this._error);
return;
}
this._error = undefined;
if (msg.urlPath === "lovelace") {
msg.urlPath = null;
}
this._lovelacePath = msg.viewPath;
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath;
this._lovelaceConfig = undefined;
if (this._unsubLovelace) {
this._unsubLovelace();
}
const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107)
? getLovelaceCollection(this.hass!.connection, msg.urlPath)
: getLegacyLovelaceCollection(this.hass!.connection);
? getLovelaceCollection(this.hass.connection, msg.urlPath)
: getLegacyLovelaceCollection(this.hass.connection);
// We first do a single refresh because we need to check if there is LL
// configuration.
try {
@@ -193,9 +233,17 @@ export class HcMain extends HassElement {
this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
this._handleNewLovelaceConfig(lovelaceConfig)
);
} catch (err) {
// eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg);
} catch (err: any) {
if (
atLeastVersion(this.hass.connection.haVersion, 0, 107) &&
err.code !== "config_not_found"
) {
// eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg);
this._error = `Error fetching Lovelace configuration: ${err.message}`;
this._sendError(ReceiverErrorCode.FETCH_CONFIG_FAILED, this._error);
return;
}
// Generate a Lovelace config.
this._unsubLovelace = () => undefined;
await this._generateLovelaceConfig();
@@ -210,8 +258,6 @@ export class HcMain extends HassElement {
loadLovelaceResources(resources, this.hass!.auth.data.hassUrl);
}
}
this._showDemo = false;
this._lovelacePath = msg.viewPath;
this._sendStatus();
}
@@ -232,7 +278,7 @@ export class HcMain extends HassElement {
}
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
castContext.setApplicationState(lovelaceConfig.title!);
castContext.setApplicationState(lovelaceConfig.title || "");
this._lovelaceConfig = lovelaceConfig;
}
+3 -2
View File
@@ -1,3 +1,4 @@
import { mdiTelevision } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { CastManager } from "../../../src/cast/cast_manager";
@@ -27,7 +28,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
return html``;
}
return html`
<ha-icon icon="hademo:television"></ha-icon>
<ha-svg-icon .path=${mdiTelevision}></ha-svg-icon>
<div class="flex">
<div class="name">Show Chromecast interface</div>
<google-cast-launcher></google-cast-launcher>
@@ -72,7 +73,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
display: flex;
align-items: center;
}
ha-icon {
ha-svg-icon {
padding: 8px;
color: var(--paper-item-icon-color);
}
+2 -2
View File
@@ -44,7 +44,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
(conf) => html`
${conf.name}
<small>
<a target="_blank" href="${conf.authorUrl}">
<a target="_blank" href=${conf.authorUrl}>
${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by",
"name",
@@ -94,7 +94,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
this._switching = true;
try {
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err) {
} catch (err: any) {
alert("Failed to switch config :-(");
} finally {
this._switching = false;
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
import { AreaRegistryEntry } from "../../../src/data/area_registry";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockAreaRegistry = (
hass: MockHomeAssistant,
data: AreaRegistryEntry[] = []
) => hass.mockWS("config/area_registry/list", () => data);
+7
View File
@@ -0,0 +1,7 @@
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockDeviceRegistry = (
hass: MockHomeAssistant,
data: DeviceRegistryEntry[] = []
) => hass.mockWS("config/device_registry/list", () => data);
+3
View File
@@ -82,6 +82,9 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
],
}));
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
hass.mockWS("energy/fossil_energy_consumption", ({ period }) => ({
start: period === "month" ? 500 : period === "day" ? 20 : 5,
}));
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
+7
View File
@@ -0,0 +1,7 @@
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEntityRegistry = (
hass: MockHomeAssistant,
data: EntityRegistryEntry[] = []
) => hass.mockWS("config/entity_registry/list", () => data);
+59
View File
@@ -0,0 +1,59 @@
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
hass.config.components.push("hassio");
hass.mockWS("supervisor/api", (msg) => {
if (msg.endpoint === "/supervisor/info") {
const data: HassioSupervisorInfo = {
version: "2021.10.dev0805",
version_latest: "2021.10.dev0806",
update_available: true,
channel: "dev",
arch: "aarch64",
supported: true,
healthy: true,
ip_address: "172.30.32.2",
wait_boot: 5,
timezone: "America/Los_Angeles",
logging: "info",
debug: false,
debug_block: false,
diagnostics: true,
addons: [
{
name: "Visual Studio Code",
slug: "a0d7b954_vscode",
description:
"Fully featured VSCode experience, to edit your HA config in the browser, including auto-completion!",
state: "started",
version: "3.6.2",
version_latest: "3.6.2",
update_available: false,
repository: "a0d7b954",
icon: true,
logo: true,
},
{
name: "Z-Wave JS",
slug: "core_zwave_js",
description:
"Control a ZWave network with Home Assistant Z-Wave JS",
state: "started",
version: "0.1.45",
version_latest: "0.1.45",
update_available: false,
repository: "core",
icon: true,
logo: true,
},
] as any,
addons_repositories: [
"https://github.com/hassio-addons/repository",
] as any,
};
return data;
}
return Promise.reject(`${msg.method} ${msg.endpoint} is not implemented`);
});
};
+146 -22
View File
@@ -1,4 +1,10 @@
import { addHours, differenceInHours, endOfDay } from "date-fns";
import {
addDays,
addHours,
addMonths,
differenceInHours,
endOfDay,
} from "date-fns";
import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
@@ -70,6 +76,7 @@ const generateMeanStatistics = (
id: string,
start: Date,
end: Date,
period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number
) => {
@@ -84,6 +91,7 @@ const generateMeanStatistics = (
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
end: currentDate.toISOString(),
mean,
min: mean - Math.random() * maxDiff,
max: mean + Math.random() * maxDiff,
@@ -92,7 +100,12 @@ const generateMeanStatistics = (
sum: null,
});
lastVal = mean;
currentDate = addHours(currentDate, 1);
currentDate =
period === "day"
? addDays(currentDate, 1)
: period === "month"
? addMonths(currentDate, 1)
: addHours(currentDate, 1);
}
return statistics;
};
@@ -101,6 +114,7 @@ const generateSumStatistics = (
id: string,
start: Date,
end: Date,
period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number
) => {
@@ -115,6 +129,7 @@ const generateSumStatistics = (
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
end: currentDate.toISOString(),
mean: null,
min: null,
max: null,
@@ -122,7 +137,12 @@ const generateSumStatistics = (
state: initValue + sum,
sum,
});
currentDate = addHours(currentDate, 1);
currentDate =
period === "day"
? addDays(currentDate, 1)
: period === "month"
? addMonths(currentDate, 1)
: addHours(currentDate, 1);
}
return statistics;
};
@@ -131,6 +151,7 @@ const generateCurvedStatistics = (
id: string,
start: Date,
end: Date,
_period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number,
metered: boolean
@@ -149,6 +170,7 @@ const generateCurvedStatistics = (
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
end: currentDate.toISOString(),
mean: null,
min: null,
max: null,
@@ -167,11 +189,38 @@ const generateCurvedStatistics = (
const statisticsFunctions: Record<
string,
(id: string, start: Date, end: Date) => StatisticValue[]
(
id: string,
start: Date,
end: Date,
period: "5minute" | "hour" | "day" | "month"
) => StatisticValue[]
> = {
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
"sensor.energy_consumption_tarif_1": (
id: string,
start: Date,
end: Date,
period = "hour"
) => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
const morningLow = generateSumStatistics(
id,
start,
morningEnd,
period,
0,
0.7
);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const morningFinalVal = morningLow.length
? morningLow[morningLow.length - 1].sum!
@@ -180,6 +229,7 @@ const statisticsFunctions: Record<
id,
morningEnd,
eveningStart,
period,
morningFinalVal,
0
);
@@ -187,39 +237,71 @@ const statisticsFunctions: Record<
id,
eveningStart,
end,
period,
morningFinalVal,
0.7
);
return [...morningLow, ...empty, ...eveningLow];
},
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
"sensor.energy_consumption_tarif_2": (
id: string,
start: Date,
end: Date,
period = "hour"
) => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const highTarif = generateSumStatistics(
id,
morningEnd,
eveningStart,
period,
0,
0.3
);
const highTarifFinalVal = highTarif.length
? highTarif[highTarif.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0);
const evening = generateSumStatistics(
id,
eveningStart,
end,
period,
highTarifFinalVal,
0
);
return [...morning, ...highTarif, ...evening];
},
"sensor.energy_production_tarif_1": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end) => {
"sensor.energy_production_tarif_1": (id, start, end, period = "hour") =>
generateSumStatistics(id, start, end, period, 0, 0),
"sensor.energy_production_tarif_1_compensation": (
id,
start,
end,
period = "hour"
) => generateSumStatistics(id, start, end, period, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end, period = "hour") => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
@@ -227,6 +309,7 @@ const statisticsFunctions: Record<
id,
productionStart,
productionEnd,
period,
0,
0.15,
true
@@ -234,18 +317,43 @@ const statisticsFunctions: Record<
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const morning = generateSumStatistics(
id,
start,
productionStart,
period,
0,
0
);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
period,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
const rest = generateSumStatistics(
id,
dayEnd,
end,
period,
productionFinalVal,
1
);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.solar_production": (id, start, end) => {
"sensor.solar_production": (id, start, end, period = "hour") => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
@@ -253,6 +361,7 @@ const statisticsFunctions: Record<
id,
productionStart,
productionEnd,
period,
0,
0.3,
true
@@ -260,19 +369,32 @@ const statisticsFunctions: Record<
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const morning = generateSumStatistics(
id,
start,
productionStart,
period,
0,
0
);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
period,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
const rest = generateSumStatistics(
id,
dayEnd,
end,
period,
productionFinalVal,
2
);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
generateMeanStatistics(id, start, end, 35, 1.3),
};
export const mockHistory = (mockHass: MockHomeAssistant) => {
@@ -347,7 +469,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",
({ statistic_ids, start_time, end_time }, hass) => {
({ statistic_ids, start_time, end_time, period }, hass) => {
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
@@ -355,7 +477,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
statistic_ids.forEach((id: string) => {
if (id in statisticsFunctions) {
statistics[id] = statisticsFunctions[id](id, start, end);
statistics[id] = statisticsFunctions[id](id, start, end, period);
} else {
const entityState = hass.states[id];
const state = entityState ? Number(entityState.state) : 1;
@@ -365,6 +487,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
id,
start,
end,
period,
state,
state * (state > 80 ? 0.01 : 0.05)
)
@@ -372,6 +495,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
id,
start,
end,
period,
state,
state * (state > 80 ? 0.05 : 0.1)
);
+2 -2
View File
@@ -23,9 +23,9 @@ customElements.whenDefined("hui-view").then(() => {
// eslint-disable-next-line
const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView.prototype.createCardElement;
const oldCreateCard = HUIView!.prototype.createCardElement;
HUIView.prototype.createCardElement = function (config) {
HUIView!.prototype.createCardElement = function (config) {
const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") {
(el as HADemoCard).lovelace = this.lovelace;
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
TARGET_LABEL="Needs gallery preview"
if [[ "$NETLIFY" != "true" ]]; then
echo "This script can only be run on Netlify"
exit 1
fi
function createStatus() {
state="$1"
description="$2"
target_url="$3"
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \
-d '{"state": "'"${state}"'", "context": "Netlify/Gallery Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}'
}
if [[ "${PULL_REQUEST}" == "false" ]]; then
gulp build-gallery
else
if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; then
createStatus "pending" "Building gallery preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
gulp build-gallery
if [ $? -eq 0 ]; then
createStatus "success" "Build complete" "$DEPLOY_URL"
else
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
fi
else
createStatus "success" "Build was not requested by PR label"
fi
fi
@@ -0,0 +1,139 @@
import { Button } from "@material/mwc-button";
import { html, LitElement, css, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("demo-black-white-row")
class DemoBlackWhiteRow extends LitElement {
@property() title!: string;
@property() value!: any;
@property() disabled = false;
protected render(): TemplateResult {
return html`
<div class="row">
<div class="content light">
<ha-card .header=${this.title}>
<div class="card-content">
<slot name="light"></slot>
</div>
<div class="card-actions">
<mwc-button
.disabled=${this.disabled}
@click=${this.handleSubmit}
>
Submit
</mwc-button>
</div>
</ha-card>
</div>
<div class="content dark">
<ha-card .header=${this.title}>
<div class="card-content">
<slot name="dark"></slot>
</div>
<div class="card-actions">
<mwc-button
.disabled=${this.disabled}
@click=${this.handleSubmit}
>
Submit
</mwc-button>
</div>
</ha-card>
<pre>${JSON.stringify(this.value, undefined, 2)}</pre>
</div>
</div>
`;
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
});
}
handleSubmit(ev) {
const content = (ev.target as Button).closest(".content")!;
fireEvent(this, "submitted" as any, {
slot: content.classList.contains("light") ? "light" : "dark",
});
}
static styles = css`
.row {
display: flex;
}
.content {
padding: 50px 0;
background-color: var(--primary-background-color);
}
.light {
flex: 1;
padding-left: 50px;
padding-right: 50px;
box-sizing: border-box;
}
.light ha-card {
margin-left: auto;
}
.dark {
display: flex;
flex: 1;
padding-left: 50px;
box-sizing: border-box;
flex-wrap: wrap;
}
ha-card {
width: 400px;
}
pre {
width: 300px;
margin: 0 16px 0;
overflow: auto;
color: var(--primary-text-color);
}
.card-actions {
display: flex;
flex-direction: row-reverse;
border-top: none;
}
@media only screen and (max-width: 1500px) {
.light {
flex: initial;
}
}
@media only screen and (max-width: 1000px) {
.light,
.dark {
padding: 16px;
}
.row,
.dark {
flex-direction: column;
}
ha-card {
margin: 0 auto;
width: 100%;
max-width: 400px;
}
pre {
margin: 16px auto;
}
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-black-white-row": DemoBlackWhiteRow;
}
}
@@ -0,0 +1,91 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import "../../../src/panels/config/automation/action/ha-automation-action";
import { HaChooseAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-choose";
import { HaDelayAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-delay";
import { HaDeviceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
import { HaEventAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-event";
import { HaRepeatAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
import { HaSceneAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-scene";
import { HaServiceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-service";
import { HaWaitForTriggerAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
import { HaWaitAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
import { Action } from "../../../src/data/script";
import { HaConditionAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-condition";
const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Event", actions: [HaEventAction.defaultConfig] },
{ name: "Device", actions: [HaDeviceAction.defaultConfig] },
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
];
@customElement("demo-automation-editor-action")
class DemoHaAutomationEditorAction extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.actions);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-action
slot=${slot}
.hass=${this.hass}
.actions=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-action>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-action": DemoHaAutomationEditorAction;
}
}
@@ -0,0 +1,127 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import type { Condition } from "../../../src/data/automation";
import "../../../src/panels/config/automation/condition/ha-automation-condition";
import { HaDeviceCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
import { HaLogicalCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
import HaNumericStateCondition from "../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state";
import { HaStateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-state";
import { HaSunCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-sun";
import { HaTemplateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-template";
import { HaTimeCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-time";
import { HaTriggerCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
import { HaZoneCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
{
name: "State",
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
},
{
name: "Numeric State",
conditions: [
{ condition: "numeric_state", ...HaNumericStateCondition.defaultConfig },
],
},
{
name: "Sun",
conditions: [{ condition: "sun", ...HaSunCondition.defaultConfig }],
},
{
name: "Zone",
conditions: [{ condition: "zone", ...HaZoneCondition.defaultConfig }],
},
{
name: "Time",
conditions: [{ condition: "time", ...HaTimeCondition.defaultConfig }],
},
{
name: "Template",
conditions: [
{ condition: "template", ...HaTemplateCondition.defaultConfig },
],
},
{
name: "Device",
conditions: [{ condition: "device", ...HaDeviceCondition.defaultConfig }],
},
{
name: "And",
conditions: [{ condition: "and", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Or",
conditions: [{ condition: "or", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Not",
conditions: [{ condition: "not", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Trigger",
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
},
];
@customElement("demo-automation-editor-condition")
class DemoHaAutomationEditorCondition extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.conditions);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-condition
slot=${slot}
.hass=${this.hass}
.conditions=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-condition>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-condition": DemoHaAutomationEditorCondition;
}
}
@@ -0,0 +1,159 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import type { Trigger } from "../../../src/data/automation";
import { HaGeolocationTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
import { HaEventTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-event";
import { HaHassTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
import { HaNumericStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
import { HaSunTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-sun";
import { HaTagTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-tag";
import { HaTemplateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-template";
import { HaTimeTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time";
import { HaTimePatternTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern";
import { HaWebhookTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-webhook";
import { HaZoneTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-zone";
import { HaDeviceTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-device";
import { HaStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
import { HaMQTTTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
import "../../../src/panels/config/automation/trigger/ha-automation-trigger";
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
{
name: "State",
triggers: [{ platform: "state", ...HaStateTrigger.defaultConfig }],
},
{
name: "MQTT",
triggers: [{ platform: "mqtt", ...HaMQTTTrigger.defaultConfig }],
},
{
name: "GeoLocation",
triggers: [
{ platform: "geo_location", ...HaGeolocationTrigger.defaultConfig },
],
},
{
name: "Home Assistant",
triggers: [{ platform: "homeassistant", ...HaHassTrigger.defaultConfig }],
},
{
name: "Numeric State",
triggers: [
{ platform: "numeric_state", ...HaNumericStateTrigger.defaultConfig },
],
},
{
name: "Sun",
triggers: [{ platform: "sun", ...HaSunTrigger.defaultConfig }],
},
{
name: "Time Pattern",
triggers: [
{ platform: "time_pattern", ...HaTimePatternTrigger.defaultConfig },
],
},
{
name: "Webhook",
triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }],
},
{
name: "Zone",
triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }],
},
{
name: "Tag",
triggers: [{ platform: "tag", ...HaTagTrigger.defaultConfig }],
},
{
name: "Time",
triggers: [{ platform: "time", ...HaTimeTrigger.defaultConfig }],
},
{
name: "Template",
triggers: [{ platform: "template", ...HaTemplateTrigger.defaultConfig }],
},
{
name: "Event",
triggers: [{ platform: "event", ...HaEventTrigger.defaultConfig }],
},
{
name: "Device Trigger",
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
},
];
@customElement("demo-automation-editor-trigger")
class DemoHaAutomationEditorTrigger extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.triggers);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-trigger
slot=${slot}
.hass=${this.hass}
.triggers=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-trigger>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-trigger": DemoHaAutomationEditorTrigger;
}
}
@@ -1,3 +1,4 @@
/* eslint-disable lit/no-template-arrow */
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-card";
@@ -1,3 +1,4 @@
/* eslint-disable lit/no-template-arrow */
import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph";
+90 -23
View File
@@ -1,15 +1,19 @@
import { html, css, LitElement, TemplateResult } from "lit";
import "@material/mwc-button/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
import "../../../src/components/ha-logo-svg";
const alerts: {
title?: string;
description: string | TemplateResult;
type: "info" | "warning" | "error" | "success";
dismissable?: boolean;
action?: string;
rtl?: boolean;
iconSlot?: TemplateResult;
actionSlot?: TemplateResult;
}[] = [
{
title: "Test info alert",
@@ -73,13 +77,35 @@ const alerts: {
title: "Error with action",
description: "This is a test error alert with action",
type: "error",
action: "restart",
actionSlot: html`<mwc-button slot="action" label="restart"></mwc-button>`,
},
{
title: "Unsaved data",
description: "You have unsaved data",
type: "warning",
action: "save",
actionSlot: html`<mwc-button slot="action" label="save"></mwc-button>`,
},
{
title: "Slotted icon",
description: "Alert with slotted icon",
type: "warning",
iconSlot: html`<span slot="icon" class="image">
<ha-logo-svg></ha-logo-svg>
</span>`,
},
{
title: "Slotted image",
description: "Alert with slotted image",
type: "warning",
iconSlot: html`<span slot="icon" class="image"
><img src="https://www.home-assistant.io/images/home-assistant-logo.svg"
/></span>`,
},
{
title: "Slotted action",
description: "Alert with slotted action",
type: "info",
actionSlot: html`<mwc-button slot="action" label="action"></mwc-button>`,
},
{
description: "Dismissable information (RTL)",
@@ -91,7 +117,7 @@ const alerts: {
title: "Error with action",
description: "This is a test error alert with action (RTL)",
type: "error",
action: "restart",
actionSlot: html`<mwc-button slot="action" label="restart"></mwc-button>`,
rtl: true,
},
{
@@ -106,38 +132,79 @@ const alerts: {
export class DemoHaAlert extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card header="ha-alert demo">
${alerts.map(
(alert) => html`
<ha-alert
.title=${alert.title || ""}
.alertType=${alert.type}
.dismissable=${alert.dismissable || false}
.actionText=${alert.action || ""}
.rtl=${alert.rtl || false}
>
${alert.description}
</ha-alert>
`
)}
</ha-card>
${["light", "dark"].map(
(mode) => html`
<div class=${mode}>
<ha-card header="ha-alert ${mode} demo">
<div class="card-content">
${alerts.map(
(alert) => html`
<ha-alert
.title=${alert.title || ""}
.alertType=${alert.type}
.dismissable=${alert.dismissable || false}
.rtl=${alert.rtl || false}
>
${alert.iconSlot} ${alert.description} ${alert.actionSlot}
</ha-alert>
`
)}
</div>
</ha-card>
</div>
`
)}
`;
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
});
}
static get styles() {
return css`
:host {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.dark,
.light {
display: block;
background-color: var(--primary-background-color);
padding: 0 50px;
}
ha-card {
max-width: 600px;
margin: 24px auto;
}
ha-alert {
display: block;
margin: 24px 0;
}
.condition {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
span {
margin-right: 16px;
.image {
display: inline-flex;
height: 100%;
align-items: center;
}
img {
max-height: 24px;
width: 24px;
}
mwc-button {
--mdc-theme-primary: var(--primary-text-color);
}
`;
}
+85
View File
@@ -0,0 +1,85 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import "../../../src/components/ha-bar";
import "../../../src/components/ha-card";
const bars: {
min?: number;
max?: number;
value: number;
warning?: number;
error?: number;
}[] = [
{
value: 33,
},
{
value: 150,
},
{
min: -10,
value: 0,
},
{
value: 80,
},
{
value: 200,
max: 13,
},
{
value: 4,
min: 13,
},
];
@customElement("demo-ha-bar")
export class DemoHaBar extends LitElement {
protected render(): TemplateResult {
return html`
${bars
.map((bar) => ({ min: 0, max: 100, warning: 70, error: 90, ...bar }))
.map(
(bar) => html`
<ha-card>
<div class="card-content">
<pre>Config: ${JSON.stringify(bar)}</pre>
<ha-bar
class=${classMap({
warning: bar.value > bar.warning,
error: bar.value > bar.error,
})}
.min=${bar.min}
.max=${bar.max}
.value=${bar.value}
>
</ha-bar>
</div>
</ha-card>
`
)}
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.warning {
--ha-bar-primary-color: var(--warning-color);
}
.error {
--ha-bar-primary-color: var(--error-color);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-bar": DemoHaBar;
}
}
+86
View File
@@ -0,0 +1,86 @@
import { mdiHomeAssistant } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card";
import "../../../src/components/ha-chip";
import "../../../src/components/ha-chip-set";
import "../../../src/components/ha-svg-icon";
const chips: {
icon?: string;
content?: string;
}[] = [
{},
{
icon: mdiHomeAssistant,
},
{
content: "Content",
},
{
icon: mdiHomeAssistant,
content: "Content",
},
];
@customElement("demo-ha-chips")
export class DemoHaChips extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card header="ha-chip demo">
<div class="card-content">
${chips.map(
(chip) => html`
<ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: ""}
${chip.content}
</ha-chip>
`
)}
</div>
</ha-card>
<ha-card header="ha-chip-set demo">
<div class="card-content">
<ha-chip-set>
${chips.map(
(chip) => html`
<ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: ""}
${chip.content}
</ha-chip>
`
)}
</ha-chip-set>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
ha-chip {
margin-bottom: 4px;
}
.card-content {
display: flex;
flex-direction: column;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-chips": DemoHaChips;
}
}
+88
View File
@@ -0,0 +1,88 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card";
import "../../../src/components/ha-faded";
import "../../../src/components/ha-markdown";
const LONG_TEXT = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
`;
const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
@customElement("demo-ha-faded")
export class DemoHaFaded extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card header="ha-faded demo">
<div class="card-content">
<h3>Long text directly as slotted content</h3>
<ha-faded>${LONG_TEXT}</ha-faded>
<h3>Long text with slotted element</h3>
<ha-faded><span>${LONG_TEXT}</span></ha-faded>
<h3>No text</h3>
<ha-faded><span></span></ha-faded>
<h3>Smal text</h3>
<ha-faded><span>${SMALL_TEXT}</span></ha-faded>
<h3>Long text in markdown</h3>
<ha-faded>
<ha-markdown .content=${LONG_TEXT}> </ha-markdown>
</ha-faded>
<h3>Missing 1px from hiding</h3>
<ha-faded faded-height="87">
<span>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc
laoreet velit ut elit volutpat, eget ultrices odio lacinia. In
imperdiet malesuada est, nec sagittis metus ultricies quis. Sed
nisl ex, convallis porttitor ante quis, hendrerit tristique justo.
Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque
sed consequat risus. Suspendisse facilisis ligula a odio
consectetur condimentum. Curabitur vehicula elit nec augue mollis,
et volutpat massa dictum. Nam pellentesque auctor rutrum.
Suspendisse elit est, sodales vel diam nec, porttitor faucibus
massa. Ut pretium ac orci eu pharetra.
</span>
</ha-faded>
<h3>1px over hiding point</h3>
<ha-faded faded-height="85">
<span>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc
laoreet velit ut elit volutpat, eget ultrices odio lacinia. In
imperdiet malesuada est, nec sagittis metus ultricies quis. Sed
nisl ex, convallis porttitor ante quis, hendrerit tristique justo.
Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque
sed consequat risus. Suspendisse facilisis ligula a odio
consectetur condimentum. Curabitur vehicula elit nec augue mollis,
et volutpat massa dictum. Nam pellentesque auctor rutrum.
Suspendisse elit est, sodales vel diam nec, porttitor faucibus
massa. Ut pretium ac orci eu pharetra.
</span>
</ha-faded>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-faded": DemoHaFaded;
}
}
+306
View File
@@ -0,0 +1,306 @@
/* eslint-disable lit/no-template-arrow */
import "@material/mwc-button";
import { LitElement, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators";
import { computeInitialHaFormData } from "../../../src/components/ha-form/compute-initial-ha-form-data";
import type { HaFormSchema } from "../../../src/components/ha-form/types";
import "../../../src/components/ha-form/ha-form";
import "../components/demo-black-white-row";
const SCHEMAS: {
title: string;
translations?: Record<string, string>;
error?: Record<string, string>;
schema: HaFormSchema[];
data?: Record<string, any>;
}[] = [
{
title: "Authentication",
translations: {
username: "Username",
password: "Password",
invalid_login: "Invalid username or password",
},
error: {
base: "invalid_login",
},
schema: [
{
type: "string",
name: "username",
required: true,
},
{
type: "string",
name: "password",
required: true,
},
],
},
{
title: "One of each",
schema: [
{
type: "constant",
value: "Constant Value",
name: "constant",
required: true,
},
{
type: "boolean",
name: "bool",
optional: true,
default: false,
},
{
type: "integer",
name: "int",
optional: true,
default: 10,
},
{
type: "float",
name: "float",
required: true,
},
{
type: "string",
name: "string",
optional: true,
default: "Default",
},
{
type: "select",
options: [
["default", "default"],
["other", "other"],
],
name: "select",
optional: true,
default: "default",
},
{
type: "multi_select",
options: {
default: "Default",
other: "Other",
},
name: "multi",
optional: true,
default: ["default"],
},
{
type: "positive_time_period_dict",
name: "time",
required: true,
},
],
},
{
title: "Numbers",
schema: [
{
type: "integer",
name: "int",
required: true,
},
{
type: "integer",
name: "int with default",
optional: true,
default: 10,
},
{
type: "integer",
name: "int range required",
required: true,
default: 5,
valueMin: 0,
valueMax: 10,
},
{
type: "integer",
name: "int range optional",
optional: true,
valueMin: 0,
valueMax: 10,
},
],
},
{
title: "select",
schema: [
{
type: "select",
options: [
["default", "Default"],
["other", "Other"],
],
name: "select",
required: true,
default: "default",
},
{
type: "select",
options: [
["default", "Default"],
["other", "Other"],
],
name: "select optional",
optional: true,
},
{
type: "select",
options: [
["default", "Default"],
["other", "Other"],
["uno", "mas"],
["one", "more"],
["and", "another_one"],
["option", "1000"],
],
name: "select many otions",
optional: true,
default: "default",
},
],
},
{
title: "Multi select",
schema: [
{
type: "multi_select",
options: {
default: "Default",
other: "Other",
},
name: "multi",
required: true,
default: ["default"],
},
{
type: "multi_select",
options: {
default: "Default",
other: "Other",
uno: "mas",
one: "more",
and: "another_one",
option: "1000",
},
name: "multi many otions",
optional: true,
default: ["default"],
},
],
},
{
title: "Field specific error",
data: {
new_password: "hello",
new_password_2: "bye",
},
translations: {
new_password: "New Password",
new_password_2: "Re-type Password",
not_match: "The passwords do not match",
},
error: {
new_password_2: "not_match",
},
schema: [
{
type: "string",
name: "new_password",
required: true,
},
{
type: "string",
name: "new_password_2",
required: true,
},
],
},
{
title: "OctoPrint",
translations: {
username: "Username",
host: "Host",
port: "Port Number",
path: "Application Path",
ssl: "Use SSL",
},
schema: [
{ type: "string", name: "username", required: true, default: "" },
{ type: "string", name: "host", required: true, default: "" },
{
type: "integer",
valueMin: 1,
valueMax: 65535,
name: "port",
optional: true,
default: 80,
},
{ type: "string", name: "path", optional: true, default: "/" },
{ type: "boolean", name: "ssl", optional: true, default: false },
],
},
];
@customElement("demo-ha-form")
class DemoHaForm extends LitElement {
private data = SCHEMAS.map(
({ schema, data }) => data || computeInitialHaFormData(schema)
);
private disabled = SCHEMAS.map(() => false);
protected render(): TemplateResult {
return html`
${SCHEMAS.map((info, idx) => {
const translations = info.translations || {};
return html`
<demo-black-white-row
.title=${info.title}
.value=${this.data[idx]}
.disabled=${this.disabled[idx]}
@submitted=${() => {
this.disabled[idx] = true;
this.requestUpdate();
setTimeout(() => {
this.disabled[idx] = false;
this.requestUpdate();
}, 2000);
}}
>
${["light", "dark"].map(
(slot) => html`
<ha-form
slot=${slot}
.data=${this.data[idx]}
.schema=${info.schema}
.error=${info.error}
.disabled=${this.disabled[idx]}
.computeError=${(error) => translations[error] || error}
.computeLabel=${(schema) =>
translations[schema.name] || schema.name}
@value-changed=${(e) => {
this.data[idx] = e.detail.value;
this.requestUpdate();
}}
></ha-form>
`
)}
</demo-black-white-row>
`;
})}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-form": DemoHaForm;
}
}
+122
View File
@@ -0,0 +1,122 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-label-badge";
import "../../../src/components/ha-card";
const colors = ["#03a9f4", "#ffa600", "#43a047"];
const badges: {
label?: string;
description?: string;
image?: string;
}[] = [
{
label: "label",
},
{
label: "label",
description: "Description",
},
{
description: "Description",
},
{
label: "label",
description: "Description",
image: "/images/living_room.png",
},
{
description: "Description",
image: "/images/living_room.png",
},
{
label: "label",
image: "/images/living_room.png",
},
{
image: "/images/living_room.png",
},
{
label: "big label",
},
{
label: "big label",
description: "Description",
},
{
label: "big label",
description: "Description",
image: "/images/living_room.png",
},
];
@customElement("demo-ha-label-badge")
export class DemoHaLabelBadge extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card>
<div class="card-content">
${badges.map(
(badge) => html`
<ha-label-badge
style="--ha-label-badge-color: ${colors[
Math.floor(Math.random() * colors.length)
]}"
.label=${badge.label}
.description=${badge.description}
.image=${badge.image}
>
</ha-label-badge>
`
)}
</div>
</ha-card>
<ha-card>
<div class="card-content">
${badges.map(
(badge) => html`
<div class="badge">
<ha-label-badge
style="--ha-label-badge-color: ${colors[
Math.floor(Math.random() * colors.length)
]}"
.label=${badge.label}
.description=${badge.description}
.image=${badge.image}
>
</ha-label-badge>
<pre>${JSON.stringify(badge, null, 2)}</pre>
</div>
`
)}
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
pre {
margin-left: 16px;
background-color: var(--markdown-code-background-color);
padding: 8px;
}
.badge {
display: flex;
flex-direction: row;
margin-bottom: 16px;
align-items: center;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-label-badge": DemoHaLabelBadge;
}
}
+131
View File
@@ -0,0 +1,131 @@
/* eslint-disable lit/no-template-arrow */
import "@material/mwc-button";
import { LitElement, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../src/components/ha-selector/ha-selector";
import "../../../src/components/ha-settings-row";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { BlueprintInput } from "../../../src/data/blueprint";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
const SCHEMAS: {
name: string;
input: Record<string, BlueprintInput | null>;
}[] = [
{
name: "One of each",
input: {
entity: { name: "Entity", selector: { entity: {} } },
device: { name: "Device", selector: { device: {} } },
addon: { name: "Addon", selector: { addon: {} } },
area: { name: "Area", selector: { area: {} } },
target: { name: "Target", selector: { target: {} } },
number_box: {
name: "Number Box",
selector: {
number: {
min: 0,
max: 10,
mode: "box",
},
},
},
number_slider: {
name: "Number Slider",
selector: {
number: {
min: 0,
max: 10,
mode: "slider",
},
},
},
boolean: { name: "Boolean", selector: { boolean: {} } },
time: { name: "Time", selector: { time: {} } },
action: { name: "Action", selector: { action: {} } },
text: { name: "Text", selector: { text: { multiline: false } } },
text_multiline: {
name: "Text multiline",
selector: { text: { multiline: true } },
},
object: { name: "Object", selector: { object: {} } },
select: {
name: "Select",
selector: { select: { options: ["Option 1", "Option 2"] } },
},
},
},
];
@customElement("demo-ha-selector")
class DemoHaSelector extends LitElement {
@state() private hass!: HomeAssistant;
private data = SCHEMAS.map(() => ({}));
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
return html`
${SCHEMAS.map((info, idx) => {
const data = this.data[idx];
const valueChanged = (ev) => {
this.data[idx] = {
...data,
[ev.target.key]: ev.detail.value,
};
this.requestUpdate();
};
return html`
<demo-black-white-row .title=${info.name} .value=${this.data[idx]}>
${["light", "dark"].map((slot) =>
Object.entries(info.input).map(
([key, value]) =>
html`
<ha-settings-row narrow slot=${slot}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
<ha-selector
.hass=${this.hass}
.selector=${value!.selector}
.key=${key}
.value=${data[key] ?? value!.default}
@value-changed=${valueChanged}
></ha-selector>
</ha-settings-row>
`
)
)}
</demo-black-white-row>
`;
})}
`;
}
static styles = css`
paper-input,
ha-selector {
width: 60;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-selector": DemoHaSelector;
}
}
+156
View File
@@ -0,0 +1,156 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("switch", "bed_ac", "on", {
friendly_name: "Ecobee",
}),
getEntity("sensor", "bed_temp", "72", {
friendly_name: "Bedroom Temp",
device_class: "temperature",
unit_of_measurement: "°F",
}),
getEntity("light", "living_room_light", "off", {
friendly_name: "Living Room Light",
}),
getEntity("fan", "living_room", "on", {
friendly_name: "Living Room Fan",
}),
getEntity("sensor", "office_humidity", "73", {
friendly_name: "Office Humidity",
device_class: "humidity",
unit_of_measurement: "%",
}),
getEntity("light", "office", "on", {
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Second Office Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
device_class: "door",
}),
];
// TODO: Update image here
const CONFIGS = [
{
heading: "Bedroom",
config: `
- type: area
area: bedroom
image: "/images/bed.png"
`,
},
{
heading: "Living Room",
config: `
- type: area
area: living_room
image: "/images/living_room.png"
`,
},
{
heading: "Office",
config: `
- type: area
area: office
image: "/images/office.jpg"
`,
},
{
heading: "Kitchen",
config: `
- type: area
area: kitchen
image: "/images/kitchen.png"
`,
},
];
@customElement("demo-hui-area-card")
class DemoArea extends LitElement {
@query("#demos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
hass.mockWS("config/area_registry/list", () => [
{
name: "Bedroom",
area_id: "bedroom",
},
{
name: "Living Room",
area_id: "living_room",
},
{
name: "Office",
area_id: "office",
},
{
name: "Second Office",
area_id: "kitchen",
},
]);
hass.mockWS("config/device_registry/list", () => []);
hass.mockWS("config/entity_registry/list", () => [
{
area_id: "bedroom",
entity_id: "light.bed_light",
},
{
area_id: "bedroom",
entity_id: "switch.bed_ac",
},
{
area_id: "bedroom",
entity_id: "sensor.bed_temp",
},
{
area_id: "living_room",
entity_id: "light.living_room_light",
},
{
area_id: "living_room",
entity_id: "fan.living_room",
},
{
area_id: "office",
entity_id: "light.office",
},
{
area_id: "office",
entity_id: "sensor.office_humidity",
},
{
area_id: "kitchen",
entity_id: "fan.kitchen",
},
{
area_id: "kitchen",
entity_id: "binary_sensor.kitchen_door",
},
]);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-hui-area-card": DemoArea;
}
}
@@ -187,6 +187,7 @@ const createEntityRegistryEntries = (
device_id: "mock-device-id",
area_id: null,
disabled_by: null,
entity_category: null,
entity_id: "binary_sensor.updater",
name: null,
icon: null,
@@ -211,6 +212,7 @@ const createDeviceRegistryEntries = (
area_id: null,
name_by_user: null,
disabled_by: null,
configuration_url: null,
},
];
+164
View File
@@ -0,0 +1,164 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../src/components/ha-card";
import {
SUPPORT_OPEN,
SUPPORT_STOP,
SUPPORT_CLOSE,
SUPPORT_SET_POSITION,
SUPPORT_OPEN_TILT,
SUPPORT_STOP_TILT,
SUPPORT_CLOSE_TILT,
SUPPORT_SET_TILT_POSITION,
} from "../../../src/data/cover";
import "../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos";
const ENTITIES = [
getEntity("cover", "position_buttons", "on", {
friendly_name: "Position Buttons",
supported_features: SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE,
}),
getEntity("cover", "position_slider_half", "on", {
friendly_name: "Position Half-Open",
supported_features:
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
current_position: 50,
}),
getEntity("cover", "position_slider_open", "on", {
friendly_name: "Position Open",
supported_features:
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
current_position: 100,
}),
getEntity("cover", "position_slider_closed", "on", {
friendly_name: "Position Closed",
supported_features:
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
current_position: 0,
}),
getEntity("cover", "tilt_buttons", "on", {
friendly_name: "Tilt Buttons",
supported_features:
SUPPORT_OPEN_TILT + SUPPORT_STOP_TILT + SUPPORT_CLOSE_TILT,
}),
getEntity("cover", "tilt_slider_half", "on", {
friendly_name: "Tilt Half-Open",
supported_features:
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT +
SUPPORT_SET_TILT_POSITION,
current_tilt_position: 50,
}),
getEntity("cover", "tilt_slider_open", "on", {
friendly_name: "Tilt Open",
supported_features:
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT +
SUPPORT_SET_TILT_POSITION,
current_tilt_position: 100,
}),
getEntity("cover", "tilt_slider_closed", "on", {
friendly_name: "Tilt Closed",
supported_features:
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT +
SUPPORT_SET_TILT_POSITION,
current_tilt_position: 0,
}),
getEntity("cover", "position_slider_tilt_slider", "on", {
friendly_name: "Both Sliders",
supported_features:
SUPPORT_OPEN +
SUPPORT_STOP +
SUPPORT_CLOSE +
SUPPORT_SET_POSITION +
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT +
SUPPORT_SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
}),
getEntity("cover", "position_tilt_slider", "on", {
friendly_name: "Position & Tilt Slider",
supported_features:
SUPPORT_OPEN +
SUPPORT_STOP +
SUPPORT_CLOSE +
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT +
SUPPORT_SET_TILT_POSITION,
current_tilt_position: 70,
}),
getEntity("cover", "position_slider_tilt", "on", {
friendly_name: "Position Slider & Tilt",
supported_features:
SUPPORT_OPEN +
SUPPORT_STOP +
SUPPORT_CLOSE +
SUPPORT_SET_POSITION +
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT,
current_position: 30,
}),
getEntity("cover", "position_slider_only_tilt_slider", "on", {
friendly_name: "Position Slider Only & Tilt Buttons",
supported_features:
SUPPORT_SET_POSITION +
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT,
current_position: 30,
}),
getEntity("cover", "position_slider_only_tilt", "on", {
friendly_name: "Position Slider Only & Tilt",
supported_features:
SUPPORT_SET_POSITION +
SUPPORT_OPEN_TILT +
SUPPORT_STOP_TILT +
SUPPORT_CLOSE_TILT +
SUPPORT_SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
}),
];
@customElement("demo-more-info-cover")
class DemoMoreInfoCover extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-cover": DemoMoreInfoCover;
}
}
+18 -23
View File
@@ -1,5 +1,5 @@
import "@material/mwc-button";
import { html, LitElement, TemplateResult } from "lit";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../src/data/lovelace";
@@ -9,7 +9,6 @@ import { actionHandler } from "../../../src/panels/lovelace/common/directives/ac
export class DemoUtilLongPress extends LitElement {
protected render(): TemplateResult {
return html`
${this.renderStyle()}
${[1, 2, 3].map(
() => html`
<ha-card>
@@ -41,26 +40,22 @@ export class DemoUtilLongPress extends LitElement {
area.scrollTop = area.scrollHeight;
}
private renderStyle() {
return html`
<style>
ha-card {
width: 200px;
margin: calc(42vh - 140px) auto;
padding: 8px;
text-align: center;
}
ha-card:first-of-type {
margin-top: 16px;
}
ha-card:last-of-type {
margin-bottom: 16px;
}
static styles = css`
ha-card {
width: 200px;
margin: calc(42vh - 140px) auto;
padding: 8px;
text-align: center;
}
ha-card:first-of-type {
margin-top: 16px;
}
ha-card:last-of-type {
margin-bottom: 16px;
}
textarea {
height: 50px;
}
</style>
`;
}
textarea {
height: 50px;
}
`;
}
+3 -7
View File
@@ -65,10 +65,11 @@ class HaGallery extends PolymerElement {
<app-header slot="header" fixed>
<app-toolbar>
<ha-icon-button
icon="hass:arrow-left"
on-click="_backTapped"
class$="[[_computeHeaderButtonClass(_demo)]]"
></ha-icon-button>
>
<ha-icon icon="hass:arrow-left"></ha-icon>
</ha-icon-button>
<div main-title>
[[_withDefault(_demo, "Home Assistant Gallery")]]
</div>
@@ -175,11 +176,6 @@ class HaGallery extends PolymerElement {
this.addEventListener("alert-dismissed-clicked", () =>
this.$.notifications.showDialog({ message: "Alert dismissed clicked" })
);
this.addEventListener("alert-action-clicked", () =>
this.$.notifications.showDialog({ message: "Alert action clicked" })
);
this.addEventListener("hass-more-info", (ev) => {
if (ev.detail.entityId) {
this.$.notifications.showDialog({
@@ -4,6 +4,7 @@ import { property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card";
import {
HassioAddonInfo,
@@ -32,7 +33,7 @@ class HassioAddonRepositoryEl extends LitElement {
return filterAndSort(addons, filter);
}
return addons.sort((a, b) =>
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
caseInsensitiveStringCompare(a.name, b.name)
);
}
);
+11 -14
View File
@@ -1,4 +1,3 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
@@ -18,7 +17,7 @@ import { navigate } from "../../../src/common/navigate";
import "../../../src/common/search/search-input";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-svg-icon";
import "../../../src/components/ha-icon-button";
import {
HassioAddonInfo,
HassioAddonRepository,
@@ -26,11 +25,10 @@ import {
} from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types";
import { showRegistriesDialog } from "../dialogs/registries/show-dialog-registries";
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
import { supervisorTabs } from "../hassio-tabs";
import "./hassio-addon-repository";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
@@ -77,24 +75,22 @@ class HassioAddonStore extends LitElement {
}
return html`
<hass-tabs-subpage
<hass-subpage
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${supervisorTabs}
main-page
supervisor
.header=${this.supervisor.localize("panel.store")}
>
<span slot="header"> ${this.supervisor.localize("panel.store")} </span>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item>
${this.supervisor.localize("store.repositories")}
</mwc-list-item>
@@ -113,6 +109,7 @@ class HassioAddonStore extends LitElement {
: html`
<div class="search">
<search-input
.hass=${this.hass}
no-label-float
no-underline
.filter=${this._filter}
@@ -131,7 +128,7 @@ class HassioAddonStore extends LitElement {
</div>
`
: ""}
</hass-tabs-subpage>
</hass-subpage>
`;
}
@@ -15,12 +15,13 @@ import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-switch";
import "../../../../src/components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
@@ -77,6 +78,18 @@ class HassioAddonConfig extends LitElement {
this.addon.translations.en?.configuration?.[entry.name].name ||
entry.name;
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
schema.map((entry) =>
entry.type === "select"
? {
...entry,
options: entry.options.map((option) => [option, option]),
}
: entry
)
);
private _filteredShchema = memoizeOne(
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
schema.filter((entry) => entry.name in options || entry.required)
@@ -100,9 +113,11 @@ class HassioAddonConfig extends LitElement {
</h2>
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item .disabled=${!this._canShowSchema}>
${this._yamlMode
? this.supervisor.localize(
@@ -125,12 +140,14 @@ class HassioAddonConfig extends LitElement {
.data=${this._options!}
@value-changed=${this._configChanged}
.computeLabel=${this.computeLabel}
.schema=${this._showOptional
? this.addon.schema!
: this._filteredShchema(
this.addon.options,
this.addon.schema!
)}
.schema=${this._schema(
this._showOptional
? this.addon.schema!
: this._filteredShchema(
this.addon.options,
this.addon.schema!
)
)}
></ha-form>`
: html` <ha-yaml-editor
@value-changed=${this._configChanged}
@@ -259,7 +276,7 @@ class HassioAddonConfig extends LitElement {
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.common.update_available",
"error",
@@ -300,7 +317,7 @@ class HassioAddonConfig extends LitElement {
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
}
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -89,9 +89,9 @@ class HassioAddonNetwork extends LitElement {
<td>
<paper-input
@value-changed=${this._configChanged}
placeholder="${this.supervisor.localize(
placeholder=${this.supervisor.localize(
"addon.configuration.network.disabled"
)}"
)}
.value=${item.host ? String(item.host) : ""}
.container=${item.container}
no-label-float
@@ -171,7 +171,7 @@ class HassioAddonNetwork extends LitElement {
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
}
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_reset",
"error",
@@ -207,7 +207,7 @@ class HassioAddonNetwork extends LitElement {
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
}
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -79,7 +79,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
this.hass,
this.addon!.slug
);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.documentation.get_logs",
"error",
@@ -108,7 +108,6 @@ class HassioAddonDashboard extends LitElement {
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
.backPath=${this.addon.version ? "/hassio/dashboard" : "/hassio/store"}
.route=${route}
.tabs=${addonTabs}
supervisor
@@ -222,7 +221,7 @@ class HassioAddonDashboard extends LitElement {
try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
} catch (err) {
} catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;
}
@@ -4,7 +4,7 @@ import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HomeAssistant, Route } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-info";
@@ -12,6 +12,8 @@ import "./hassio-addon-info";
class HassioAddonInfoDashboard extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -27,6 +29,7 @@ class HassioAddonInfoDashboard extends LitElement {
<div class="content">
<hassio-addon-info
.narrow=${this.narrow}
.route=${this.route}
.hass=${this.hass}
.supervisor=${this.supervisor}
.addon=${this.addon}
+247 -320
View File
@@ -1,6 +1,5 @@
import "@material/mwc-button";
import {
mdiArrowUpBoldCircle,
mdiCheckCircle,
mdiChip,
mdiCircle,
@@ -11,6 +10,12 @@ import {
mdiHomeAssistant,
mdiKey,
mdiNetwork,
mdiNumeric1,
mdiNumeric2,
mdiNumeric3,
mdiNumeric4,
mdiNumeric5,
mdiNumeric6,
mdiPound,
mdiShield,
} from "@mdi/js";
@@ -25,7 +30,7 @@ import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-chip";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
@@ -43,7 +48,6 @@ import {
startHassioAddon,
stopHassioAddon,
uninstallHassioAddon,
updateHassioAddon,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import {
@@ -58,14 +62,14 @@ import {
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HomeAssistant, Route } from "../../../../src/types";
import { bytesToString } from "../../../../src/util/bytes-to-string";
import "../../components/hassio-card-content";
import "../../components/supervisor-metric";
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
import { showDialogSupervisorUpdate } from "../../dialogs/update/show-dialog-update";
import { hassioStyle } from "../../resources/hassio-style";
import { addonArchIsSupported } from "../../util/addon";
import "../../update-available/update-available-card";
import { addonArchIsSupported, extractChangelog } from "../../util/addon";
const STAGE_ICON = {
stable: mdiCheckCircle,
@@ -73,10 +77,21 @@ const STAGE_ICON = {
deprecated: mdiExclamationThick,
};
const RATING_ICON = {
1: mdiNumeric1,
2: mdiNumeric2,
3: mdiNumeric3,
4: mdiNumeric4,
5: mdiNumeric5,
6: mdiNumeric6,
};
@customElement("hassio-addon-info")
class HassioAddonInfo extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@@ -113,91 +128,35 @@ class HassioAddonInfo extends LitElement {
return html`
${this.addon.update_available
? html`
<ha-card
.header="${this.supervisor.localize(
"common.update_available",
"count",
1
)}🎉"
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title="${this.supervisor.localize(
"addon.dashboard.new_update_available",
"name",
this.addon.name,
"version",
this.addon.version_latest
)}"
.description="${this.supervisor.localize(
"common.running_version",
"version",
this.addon.version
)}"
icon=${mdiArrowUpBoldCircle}
iconClass="update"
></hassio-card-content>
${!this.addon.available && addonStoreInfo
? !addonArchIsSupported(
this.supervisor.info.supported_arch,
this.addon.arch
)
? html`
<ha-alert alert-type="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
</ha-alert>
`
: html`
<ha-alert alert-type="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch",
"core_version_installed",
this.supervisor.core.version,
"core_version_needed",
addonStoreInfo.homeassistant
)}
</ha-alert>
`
: ""}
</div>
<div class="card-actions">
${this.addon.changelog
? html`
<mwc-button @click=${this._openChangelog}>
${this.supervisor.localize("addon.dashboard.changelog")}
</mwc-button>
`
: html`<span></span>`}
<mwc-button @click=${this._updateClicked}>
${this.supervisor.localize("common.update")}
</mwc-button>
</div>
</ha-card>
<update-available-card
.hass=${this.hass}
.narrow=${this.narrow}
.supervisor=${this.supervisor}
.addonSlug=${this.addon.slug}
></update-available-card>
`
: ""}
${!this.addon.protected
? html`
<ha-card class="warning">
<h1 class="card-header">${this.supervisor.localize(
"addon.dashboard.protection_mode.title"
)}
</h1>
<div class="card-content">
${this.supervisor.localize("addon.dashboard.protection_mode.content")}
</div>
<div class="card-actions protection-enable">
<mwc-button @click=${this._protectionToggled}>
${this.supervisor.localize(
"addon.dashboard.protection_mode.enable"
<ha-alert
alert-type="error"
.title=${this.supervisor.localize(
"addon.dashboard.protection_mode.title"
)}
>
${this.supervisor.localize(
"addon.dashboard.protection_mode.content"
)}
<mwc-button
slot="action"
.label=${this.supervisor.localize(
"addon.dashboard.protection_mode.enable"
)}
@click=${this._protectionToggled}
>
</mwc-button>
</div>
</div>
</ha-card>
`
</ha-alert>
`
: ""}
<ha-card>
@@ -249,12 +208,169 @@ class HassioAddonInfo extends LitElement {
>`}
</div>
<div class="capabilities">
${this.addon.stage !== "stable"
? html` <ha-chip
hasIcon
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
>
<ha-svg-icon
slot="icon"
.path=${STAGE_ICON[this.addon.stage]}
>
</ha-svg-icon>
${this.supervisor.localize(
`addon.dashboard.capability.stages.${this.addon.stage}`
)}
</ha-chip>`
: ""}
<ha-chip
hasIcon
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="rating"
>
<ha-svg-icon slot="icon" .path=${RATING_ICON[this.addon.rating]}>
</ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.rating"
)}
</ha-chip>
${this.addon.host_network
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="host_network"
>
<ha-svg-icon slot="icon" .path=${mdiNetwork}> </ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
</ha-chip>
`
: ""}
${this.addon.full_access
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="full_access"
>
<ha-svg-icon slot="icon" .path=${mdiChip}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
</ha-chip>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="homeassistant_api"
>
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.core"
)}
</ha-chip>
`
: ""}
${this._computeHassioApi
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="hassio_api">
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
</ha-chip>
`
: ""}
${this.addon.docker_api
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="docker_api">
<ha-svg-icon slot="icon" .path=${mdiDocker}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}
</ha-chip>
`
: ""}
${this.addon.host_pid
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="host_pid">
<ha-svg-icon slot="icon" .path=${mdiPound}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
</ha-chip>
`
: ""}
${this.addon.apparmor !== "default"
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
>
<ha-svg-icon slot="icon" .path=${mdiShield}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
</ha-chip>
`
: ""}
${this.addon.auth_api
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="auth_api">
<ha-svg-icon slot="icon" .path=${mdiKey}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
</ha-chip>
`
: ""}
${this.addon.ingress
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="ingress">
<ha-svg-icon
slot="icon"
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
</ha-chip>
`
: ""}
</div>
<div class="description light-color">
${this.addon.description}.<br />
${this.supervisor.localize(
"addon.dashboard.visit_addon_page",
"name",
html`<a href="${this.addon.url!}" target="_blank" rel="noreferrer"
html`<a href=${this.addon.url!} target="_blank" rel="noreferrer"
>${this.addon.name}</a
>`
)}
@@ -269,178 +385,13 @@ class HassioAddonInfo extends LitElement {
/>
`
: ""}
<div class="security">
${this.addon.stage !== "stable"
? html` <ha-label-badge
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.stage"
)}
description=""
>
<ha-svg-icon
.path=${STAGE_ICON[this.addon.stage]}
></ha-svg-icon>
</ha-label-badge>`
: ""}
<ha-label-badge
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="rating"
.value=${this.addon.rating}
label="rating"
description=""
></ha-label-badge>
${this.addon.host_network
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
description=""
>
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.full_access
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
description=""
>
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hass"
)}
description=""
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this._computeHassioApi
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hassio"
)}
.description=${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.docker_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
.label=".${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}"
description=""
>
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.host_pid
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
description=""
>
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.apparmor
? html`
<ha-label-badge
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
description=""
>
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.auth_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
description=""
>
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.ingress
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
description=""
>
<ha-svg-icon
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
`
: ""}
</div>
${this.addon.version
? html`
<div
class="${classMap({
class=${classMap({
"addon-options": true,
started: this.addon.state === "started",
})}"
})}
>
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
@@ -796,7 +747,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -818,7 +769,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -840,7 +791,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -862,7 +813,7 @@ class HassioAddonInfo extends LitElement {
path: "security",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -884,7 +835,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -895,24 +846,16 @@ class HassioAddonInfo extends LitElement {
private async _openChangelog(): Promise<void> {
try {
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
if (
content.includes(`# ${this.addon.version}`) &&
content.includes(`# ${this.addon.version_latest}`)
) {
const newcontent = content.split(`# ${this.addon.version}`)[0];
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
// Only change the content if the new version still exist
// if the changelog does not have the newests version on top
// this will not be true, and we don't modify the content
content = newcontent;
}
}
const content = await fetchHassioAddonChangelog(
this.hass,
this.addon.slug
);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
content,
content: extractChangelog(this.addon, content),
});
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.get_changelog"
@@ -934,7 +877,7 @@ class HassioAddonInfo extends LitElement {
path: "install",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.install"),
text: extractApiErrorMessage(err),
@@ -955,7 +898,7 @@ class HassioAddonInfo extends LitElement {
path: "stop",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.stop"),
text: extractApiErrorMessage(err),
@@ -976,7 +919,7 @@ class HassioAddonInfo extends LitElement {
path: "stop",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.restart"),
text: extractApiErrorMessage(err),
@@ -985,33 +928,6 @@ class HassioAddonInfo extends LitElement {
button.progress = false;
}
private async _updateClicked(): Promise<void> {
showDialogSupervisorUpdate(this, {
supervisor: this.supervisor,
name: this.addon.name,
version: this.addon.version_latest,
backupParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,
},
updateHandler: async () => this._updateAddon(),
});
}
private async _updateAddon(): Promise<void> {
await updateHassioAddon(this.hass, this.addon.slug);
fireEvent(this, "supervisor-collection-refresh", {
collection: "addon",
});
const eventdata = {
success: true,
response: undefined,
path: "update",
};
fireEvent(this, "hass-api-called", eventdata);
}
private async _startClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
@@ -1035,7 +951,7 @@ class HassioAddonInfo extends LitElement {
button.progress = false;
return;
}
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: "Failed to validate addon configuration",
text: extractApiErrorMessage(err),
@@ -1053,7 +969,7 @@ class HassioAddonInfo extends LitElement {
path: "start",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.start"),
text: extractApiErrorMessage(err),
@@ -1091,7 +1007,7 @@ class HassioAddonInfo extends LitElement {
path: "uninstall",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.uninstall"
@@ -1177,34 +1093,31 @@ class HassioAddonInfo extends LitElement {
.description a {
color: var(--primary-color);
}
ha-chip {
text-transform: capitalize;
--ha-chip-text-color: var(--text-primary-color);
--ha-chip-background-color: var(--primary-color);
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
--ha-chip-background-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
--ha-chip-background-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
}
.security {
.capabilities {
margin-bottom: 16px;
}
.card-actions {
justify-content: space-between;
display: flex;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
@@ -1243,7 +1156,21 @@ class HassioAddonInfo extends LitElement {
align-self: end;
}
ha-alert mwc-button {
--mdc-theme-primary: var(--primary-text-color);
}
a {
text-decoration: none;
}
update-available-card {
padding-bottom: 16px;
}
@media (max-width: 720px) {
ha-chip {
line-height: 36px;
}
.addon-options {
max-width: 100%;
}
@@ -71,7 +71,7 @@ class HassioAddonLogs extends LitElement {
this._error = undefined;
try {
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.logs.get_logs",
"error",
+23 -16
View File
@@ -14,7 +14,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import relativeTime from "../../../src/common/datetime/relative_time";
import { relativeTime } from "../../../src/common/datetime/relative_time";
import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
@@ -23,7 +23,8 @@ import {
} from "../../../src/components/data-table/ha-data-table";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import "../../../src/components/ha-icon-button";
import "../../../src/components/ha-svg-icon";
import {
fetchHassioBackups,
friendlyFolderName,
@@ -31,6 +32,7 @@ import {
reloadHassioBackups,
removeBackup,
} from "../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -40,9 +42,9 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
@@ -133,7 +135,7 @@ export class HassioBackups extends LitElement {
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.localize),
relativeTime(new Date(entry), this.hass.locale),
},
secondary: {
title: "",
@@ -156,7 +158,7 @@ export class HassioBackups extends LitElement {
}
return html`
<hass-tabs-subpage-data-table
.tabs=${supervisorTabs}
.tabs=${supervisorTabs(this.hass)}
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
@@ -171,7 +173,8 @@ export class HassioBackups extends LitElement {
clickable
selectable
hasFab
main-page
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
back-path="/config"
supervisor
>
<ha-button-menu
@@ -179,9 +182,11 @@ export class HassioBackups extends LitElement {
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item>
${this.supervisor?.localize("common.reload")}
</mwc-list-item>
@@ -216,13 +221,15 @@ export class HassioBackups extends LitElement {
</mwc-button>
`
: html`
<mwc-icon-button
<ha-icon-button
.label=${this.supervisor.localize(
"snapshot.delete_selected"
)}
.path=${mdiDelete}
id="delete-btn"
class="warning"
@click=${this._deleteSelected}
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip>
@@ -294,7 +301,7 @@ export class HassioBackups extends LitElement {
await Promise.all(
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("backup.failed_to_delete"),
text: extractApiErrorMessage(err),
@@ -368,7 +375,7 @@ export class HassioBackups extends LitElement {
margin-right: -12px;
}
.header-btns > mwc-button,
.header-btns > mwc-icon-button {
.header-btns > ha-icon-button {
margin: 8px;
}
`,
+1 -16
View File
@@ -1,7 +1,6 @@
import { mdiHelpCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-relative-time";
import "../../../src/components/ha-svg-icon";
import { HomeAssistant } from "../../../src/types";
@@ -19,8 +18,6 @@ class HassioCardContent extends LitElement {
@property() public topbarClass?: string;
@property() public datetime?: string;
@property() public iconTitle?: string;
@property() public iconClass?: string;
@@ -37,7 +34,7 @@ class HassioCardContent extends LitElement {
${this.iconImage
? html`
<div class="icon_image ${this.iconClass}">
<img src="${this.iconImage}" .title=${this.iconTitle} />
<img src=${this.iconImage} .title=${this.iconTitle} />
<div></div>
</div>
`
@@ -56,15 +53,6 @@ class HassioCardContent extends LitElement {
/* treat as available when undefined */
this.available === false ? " (Not available)" : ""
}
${this.datetime
? html`
<ha-relative-time
.hass=${this.hass}
class="addition"
.datetime=${this.datetime}
></ha-relative-time>
`
: undefined}
</div>
</div>
`;
@@ -106,9 +94,6 @@ class HassioCardContent extends LitElement {
height: 2.4em;
line-height: 1.2em;
}
ha-relative-time {
display: block;
}
.icon_image img {
max-height: 40px;
max-width: 40px;
+4 -21
View File
@@ -1,4 +1,3 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiFolderUpload } from "@mdi/js";
import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit";
@@ -6,9 +5,8 @@ import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
@@ -18,11 +16,9 @@ declare global {
}
}
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-backup")
export class HassioUploadBackup extends LitElement {
public hass!: HomeAssistant;
public hass?: HomeAssistant;
@state() public value: string | null = null;
@@ -31,6 +27,7 @@ export class HassioUploadBackup extends LitElement {
public render(): TemplateResult {
return html`
<ha-file-upload
.hass=${this.hass}
.uploading=${this._uploading}
.icon=${mdiFolderUpload}
accept="application/x-tar"
@@ -44,20 +41,6 @@ export class HassioUploadBackup extends LitElement {
private async _uploadFile(ev) {
const file = ev.detail.files[0];
if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, {
title: "Backup file is too big",
text: html`The maximum allowed filesize is 1GB.<br />
<a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
target="_blank"
>Have a look here on how to restore it.</a
>`,
confirmText: "ok",
});
return;
}
if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, {
title: "Unsupported file format",
@@ -70,7 +53,7 @@ export class HassioUploadBackup extends LitElement {
try {
const backup = await uploadBackup(this.hass, file);
fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: "Upload failed",
text: extractApiErrorMessage(err),
@@ -181,9 +181,7 @@ export class SupervisorBackupContent extends LitElement {
>
<ha-checkbox
.checked=${this.homeAssistant}
@click=${() => {
this.homeAssistant = !this.homeAssistant;
}}
@click=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>
@@ -272,6 +270,10 @@ export class SupervisorBackupContent extends LitElement {
`;
}
private toggleHomeAssistant() {
this.homeAssistant = !this.homeAssistant;
}
static get styles(): CSSResultGroup {
return css`
.partial-picker ha-formfield {
+2 -2
View File
@@ -20,10 +20,10 @@ class SupervisorMetric extends LitElement {
<div slot="description" .title=${this.tooltip ?? ""}>
<span class="value"> ${roundedValue} % </span>
<ha-bar
class="${classMap({
class=${classMap({
"target-warning": roundedValue > 50,
"target-critical": roundedValue > 85,
})}"
})}
.value=${this.value}
></ha-bar>
</div>
+5 -3
View File
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import { stringCompare } from "../../../src/common/string/compare";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles";
@@ -20,7 +20,9 @@ class HassioAddons extends LitElement {
protected render(): TemplateResult {
return html`
<div class="content">
<h1>${this.supervisor.localize("dashboard.addons")}</h1>
${!atLeastVersion(this.hass.config.version, 2021, 12)
? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> `
: ""}
<div class="card-group">
${!this.supervisor.supervisor.addons?.length
? html`
@@ -33,7 +35,7 @@ class HassioAddons extends LitElement {
</ha-card>
`
: this.supervisor.supervisor.addons
.sort((a, b) => stringCompare(a.name, b.name))
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
.map(
(addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}>
+28 -7
View File
@@ -1,5 +1,8 @@
import { mdiStorePlus } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import "../../../src/components/ha-fab";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
@@ -25,23 +28,41 @@ class HassioDashboard extends LitElement {
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${supervisorTabs}
main-page
.tabs=${supervisorTabs(this.hass)}
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
back-path="/config"
supervisor
hasFab
>
<span slot="header">
${this.supervisor.localize("panel.dashboard")}
${this.supervisor.localize(
atLeastVersion(this.hass.config.version, 2021, 12)
? "panel.addons"
: "panel.dashboard"
)}
</span>
<div class="content">
<hassio-update
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-update>
${!atLeastVersion(this.hass.config.version, 2021, 12)
? html`
<hassio-update
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-update>
`
: ""}
<hassio-addons
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-addons>
</div>
<a href="/hassio/store" slot="fab">
<ha-fab .label=${this.supervisor.localize("panel.store")} extended>
<ha-svg-icon
slot="icon"
.path=${mdiStorePlus}
></ha-svg-icon> </ha-fab
></a>
</hass-tabs-subpage>
`;
}
+7 -114
View File
@@ -3,34 +3,18 @@ import { mdiHomeAssistant } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-svg-icon";
import {
extractApiErrorMessage,
HassioResponse,
ignoreSupervisorError,
} from "../../../src/data/hassio/common";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import { updateCore } from "../../../src/data/supervisor/core";
import {
Supervisor,
supervisorApiWsRequest,
} from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update";
import { hassioStyle } from "../resources/hassio-style";
const computeVersion = (key: string, version: string): string =>
@@ -73,26 +57,18 @@ export class HassioUpdate extends LitElement {
${this._renderUpdateCard(
"Home Assistant Core",
"core",
this.supervisor.core,
"hassio/homeassistant/update",
`https://${
this.supervisor.core.version_latest.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/`
this.supervisor.core
)}
${this._renderUpdateCard(
"Supervisor",
"supervisor",
this.supervisor.supervisor,
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
this.supervisor.supervisor
)}
${this.supervisor.host.features.includes("haos")
? this._renderUpdateCard(
"Operating System",
"os",
this.supervisor.os,
"hassio/os/update",
`https://github.com//home-assistant/hassos/releases/tag/${this.supervisor.os.version_latest}`
this.supervisor.os
)
: ""}
</div>
@@ -103,9 +79,7 @@ export class HassioUpdate extends LitElement {
private _renderUpdateCard(
name: string,
key: string,
object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo,
apiPath: string,
releaseNotesUrl: string
object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo
): TemplateResult {
if (!object.update_available) {
return html``;
@@ -136,96 +110,15 @@ export class HassioUpdate extends LitElement {
</ha-settings-row>
</div>
<div class="card-actions">
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
<mwc-button>
${this.supervisor.localize("common.release_notes")}
<a href="/hassio/update-available/${key}">
<mwc-button .label=${this.supervisor.localize("common.show")}>
</mwc-button>
</a>
<ha-progress-button
.apiPath=${apiPath}
.name=${name}
.key=${key}
.version=${object.version_latest}
@click=${this._confirmUpdate}
>
${this.supervisor.localize("common.update")}
</ha-progress-button>
</div>
</ha-card>
`;
}
private async _confirmUpdate(ev): Promise<void> {
const item = ev.currentTarget;
if (item.key === "core") {
showDialogSupervisorUpdate(this, {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
backupParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
},
updateHandler: async () => this._updateCore(),
});
return;
}
item.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.update.title",
"name",
item.name
),
text: this.supervisor.localize(
"confirm.update.text",
"name",
item.name,
"version",
computeVersion(item.key, item.version)
),
confirmText: this.supervisor.localize("common.update"),
dismissText: this.supervisor.localize("common.cancel"),
});
if (!confirmed) {
item.progress = false;
return;
}
try {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
await supervisorApiWsRequest(this.hass.connection, {
method: "post",
endpoint: item.apiPath.replace("hassio", ""),
timeout: null,
});
} else {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
}
fireEvent(this, "supervisor-collection-refresh", {
collection: item.key,
});
} catch (err) {
// Only show an error if the status code was not expected (user behind proxy)
// or no status at all(connection terminated)
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize("common.error.update_failed"),
text: extractApiErrorMessage(err),
});
}
}
item.progress = false;
}
private async _updateCore(): Promise<void> {
await updateCore(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "core",
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-icon-button";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
@@ -14,7 +15,7 @@ export class DialogHassioBackupUpload
extends LitElement
implements HassDialog<HassioBackupUploadDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _params?: HassioBackupUploadDialogParams;
@@ -52,9 +53,12 @@ export class DialogHassioBackupUpload
<div slot="heading">
<ha-header-bar>
<span slot="title"> Upload backup </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass?.localize("common.close") || "close"}
.path=${mdiClose}
slot="actionItems"
dialogAction="cancel"
></ha-icon-button>
</ha-header-bar>
</div>
<hassio-upload-backup
@@ -9,7 +9,7 @@ import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-icon-button";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
@@ -28,13 +28,14 @@ import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
@customElement("dialog-hassio-backup")
class HassioBackupDialog
extends LitElement
implements HassDialog<HassioBackupDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _error?: string;
@@ -75,9 +76,12 @@ class HassioBackupDialog
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass?.localize("common.close") || "close"}
.path=${mdiClose}
slot="actionItems"
dialogAction="cancel"
></ha-icon-button>
</ha-header-bar>
</div>
${this._restoringBackup
@@ -107,11 +111,13 @@ class HassioBackupDialog
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${(ev: Event) => ev.stopPropagation()}
@closed=${stopPropagation}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass!.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu>`
@@ -125,9 +131,6 @@ class HassioBackupDialog
haStyle,
haStyleDialog,
css`
ha-svg-icon {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block;
text-align: center;
@@ -138,6 +141,9 @@ class HassioBackupDialog
flex-shrink: 0;
display: block;
}
ha-icon-button {
color: var(--secondary-text-color);
}
`,
];
}
@@ -186,25 +192,23 @@ class HassioBackupDialog
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
@@ -238,24 +242,22 @@ class HassioBackupDialog
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
@@ -277,41 +279,38 @@ class HassioBackupDialog
return;
}
this.hass
.callApi(
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? `backups/${this._backup!.slug}`
: `snapshots/${this._backup!.slug}/remove`
}`
)
.then(
() => {
if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete();
}
this.closeDialog();
},
(error) => {
this._error = error.body.message;
this.hass!.callApi(
atLeastVersion(this.hass!.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? `backups/${this._backup!.slug}`
: `snapshots/${this._backup!.slug}/remove`
}`
).then(
() => {
if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete();
}
);
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
}
private async _downloadClicked() {
let signedPath: { path: string };
try {
signedPath = await getSignedPath(
this.hass,
this.hass!,
`/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
);
} catch (err) {
} catch (err: any) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
@@ -127,7 +127,7 @@ class HassioCreateBackupDialog extends LitElement {
this._dialogParams!.onCreate();
this.closeDialog();
} catch (err) {
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
this._creatingBackup = false;
@@ -0,0 +1,180 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import {
DatadiskList,
listDatadisks,
moveDatadisk,
} from "../../../../src/data/hassio/host";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioDatatiskDialogParams } from "./show-dialog-hassio-datadisk";
const calculateMoveTime = memoizeOne((supervisor: Supervisor): number => {
const speed = supervisor.host.disk_life_time !== "" ? 30 : 10;
const moveTime = (supervisor.host.disk_used * 1000) / 60 / speed;
const rebootTime = (supervisor.host.startup_time * 4) / 60;
return Math.ceil((moveTime + rebootTime) / 10) * 10;
});
@customElement("dialog-hassio-datadisk")
class HassioDatadiskDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private dialogParams?: HassioDatatiskDialogParams;
@state() private selectedDevice?: string;
@state() private devices?: DatadiskList["devices"];
@state() private moving = false;
public showDialog(params: HassioDatatiskDialogParams) {
this.dialogParams = params;
listDatadisks(this.hass).then((data) => {
this.devices = data.devices;
});
}
public closeDialog(): void {
this.dialogParams = undefined;
this.selectedDevice = undefined;
this.devices = undefined;
this.moving = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this.dialogParams) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
.heading=${this.moving
? this.dialogParams.supervisor.localize("dialog.datadisk_move.moving")
: this.dialogParams.supervisor.localize("dialog.datadisk_move.title")}
@closed=${this.closeDialog}
?hideActions=${this.moving}
>
${this.moving
? html` <ha-circular-progress alt="Moving" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving_desc"
)}
</p>`
: html` ${this.devices?.length
? html`
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.description",
{
current_path: this.dialogParams.supervisor.os.data_disk,
time: calculateMoveTime(this.dialogParams.supervisor),
}
)}
<br /><br />
<paper-dropdown-menu
.label=${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.select_device"
)}
@value-changed=${this._select_device}
>
<paper-listbox slot="dropdown-content">
${this.devices.map(
(device) => html`<paper-item>${device}</paper-item>`
)}
</paper-listbox>
</paper-dropdown-menu>
`
: this.devices === undefined
? this.dialogParams.supervisor.localize(
"dialog.datadisk_move.loading_devices"
)
: this.dialogParams.supervisor.localize(
"dialog.datadisk_move.no_devices"
)}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.cancel"
)}
</mwc-button>
<mwc-button
.disabled=${!this.selectedDevice}
slot="primaryAction"
@click=${this._moveDatadisk}
>
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.move"
)}
</mwc-button>`}
</ha-dialog>
`;
}
private _select_device(event) {
this.selectedDevice = event.detail.value;
}
private async _moveDatadisk() {
this.moving = true;
try {
await moveDatadisk(this.hass, this.selectedDevice!);
} catch (err: any) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.dialogParams!.supervisor.localize(
"system.host.failed_to_move"
),
text: extractApiErrorMessage(err),
});
this.closeDialog();
}
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
paper-dropdown-menu {
width: 100%;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-datadisk": HassioDatadiskDialog;
}
}
@@ -0,0 +1,17 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioDatatiskDialogParams {
supervisor: Supervisor;
}
export const showHassioDatadiskDialog = (
element: HTMLElement,
dialogParams: HassioDatatiskDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-datadisk",
dialogImport: () => import("./dialog-hassio-datadisk"),
dialogParams,
});
};
@@ -7,6 +7,7 @@ import "../../../../src/common/search/search-input";
import { stringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-icon-button";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { dump } from "../../../../src/resources/js-yaml-dump";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
@@ -70,10 +71,13 @@ class HassioHardwareDialog extends LitElement {
<h2>
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
</h2>
<mwc-icon-button dialogAction="close">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass.localize("common.close")}
.path=${mdiClose}
dialogAction="close"
></ha-icon-button>
<search-input
.hass=${this.hass}
autofocus
no-label-float
.filter=${this._filter}
@@ -141,7 +145,7 @@ class HassioHardwareDialog extends LitElement {
haStyle,
haStyleDialog,
css`
mwc-icon-button {
ha-icon-button {
position: absolute;
right: 16px;
top: 10px;
@@ -47,11 +47,6 @@ class HassioMarkdownDialog extends LitElement {
haStyleDialog,
hassioStyle,
css`
ha-paper-dialog {
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
margin: 0;
padding: 0 16px;
@@ -62,19 +57,6 @@ class HassioMarkdownDialog extends LitElement {
margin-left: 16px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog {
max-height: 100%;
}
ha-paper-dialog::before {
content: "";
position: fixed;
z-index: -1;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: inherit;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
@@ -1,5 +1,4 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button";
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-tab";
@@ -16,9 +15,9 @@ import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-radio";
import "../../../../src/components/ha-related-items";
import "../../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
AccessPoints,
@@ -104,9 +103,12 @@ export class DialogHassioNetwork
<span slot="title">
${this.supervisor.localize("dialog.network.title")}
</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<ha-icon-button
.label=${this.hass.localize("common.close")}
.path=${mdiClose}
slot="actionItems"
dialogAction="cancel"
></ha-icon-button>
</ha-header-bar>
${this._interfaces.length > 1
? html`<mwc-tab-bar
@@ -287,7 +289,7 @@ export class DialogHassioNetwork
this.hass,
this._interface.interface
);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: "Failed to scan for accesspoints",
text: extractApiErrorMessage(err),
@@ -448,7 +450,7 @@ export class DialogHassioNetwork
this._interface!.interface,
interfaceOptions
);
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.network.failed_to_change"),
text: extractApiErrorMessage(err),
@@ -1,13 +1,12 @@
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, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-form/ha-form";
import { HaFormSchema } from "../../../../src/components/ha-form/types";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-settings-row";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
addHassioDockerRegistry,
@@ -20,22 +19,41 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { RegistriesDialogParams } from "./show-dialog-registries";
const SCHEMA = [
{
type: "string",
name: "registry",
required: true,
},
{
type: "string",
name: "username",
required: true,
},
{
type: "string",
name: "password",
required: true,
format: "password",
},
];
@customElement("dialog-hassio-registries")
class HassioRegistriesDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) private _registries?: {
@state() private _registries?: {
registry: string;
username: string;
}[];
@state() private _registry?: string;
@state() private _username?: string;
@state() private _password?: string;
@state() private _input: {
registry?: string;
username?: string;
password?: string;
} = {};
@state() private _opened = false;
@@ -48,6 +66,7 @@ class HassioRegistriesDialog extends LitElement {
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
hideActions
.heading=${createCloseHeading(
this.hass,
this._addingRegistry
@@ -55,100 +74,77 @@ class HassioRegistriesDialog extends LitElement {
: this.supervisor.localize("dialog.registries.title_manage")
)}
>
<div class="form">
${this._addingRegistry
? html`
<paper-input
@value-changed=${this._inputChanged}
class="flex-auto"
name="registry"
.label=${this.supervisor.localize(
"dialog.registries.registry"
)}
required
auto-validate
></paper-input>
<paper-input
@value-changed=${this._inputChanged}
class="flex-auto"
name="username"
.label=${this.supervisor.localize(
"dialog.registries.username"
)}
required
auto-validate
></paper-input>
<paper-input
@value-changed=${this._inputChanged}
class="flex-auto"
name="password"
.label=${this.supervisor.localize(
"dialog.registries.password"
)}
type="password"
required
auto-validate
></paper-input>
${this._addingRegistry
? html`
<ha-form
.data=${this._input}
.schema=${SCHEMA}
@value-changed=${this._valueChanged}
.computeLabel=${this._computeLabel}
></ha-form>
<div class="action">
<mwc-button
?disabled=${Boolean(
!this._registry || !this._username || !this._password
!this._input.registry ||
!this._input.username ||
!this._input.password
)}
@click=${this._addNewRegistry}
>
${this.supervisor.localize("dialog.registries.add_registry")}
</mwc-button>
`
: html`${this._registries?.length
? this._registries.map(
(entry) => html`
<mwc-list-item class="option" hasMeta twoline>
<span>${entry.registry}</span>
<span slot="secondary"
>${this.supervisor.localize(
"dialog.registries.username"
)}:
${entry.username}</span
>
<mwc-icon-button
.entry=${entry}
.title=${this.supervisor.localize(
"dialog.registries.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
>${this.supervisor.localize(
"dialog.registries.no_registries"
)}</span
>
</mwc-list-item>
`}
</div>
`
: html`${this._registries?.length
? this._registries.map(
(entry) => html`
<ha-settings-row class="registry">
<span slot="heading"> ${entry.registry} </span>
<span slot="description">
${this.supervisor.localize(
"dialog.registries.username"
)}:
${entry.username}
</span>
<ha-icon-button
.entry=${entry}
.label=${this.supervisor.localize(
"dialog.registries.remove"
)}
.path=${mdiDelete}
@click=${this._removeRegistry}
></ha-icon-button>
</ha-settings-row>
`
)
: html`
<ha-alert>
${this.supervisor.localize(
"dialog.registries.no_registries"
)}
</ha-alert>
`}
<div class="action">
<mwc-button @click=${this._addRegistry}>
${this.supervisor.localize(
"dialog.registries.add_new_registry"
)}
</mwc-button> `}
</div>
</mwc-button>
</div> `}
</ha-dialog>
`;
}
private _inputChanged(ev: Event) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
private _computeLabel = (schema: HaFormSchema) =>
this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name;
private _valueChanged(ev: CustomEvent) {
this._input = ev.detail.value;
}
public async showDialog(dialogParams: RegistriesDialogParams): Promise<void> {
this._opened = true;
this._input = {};
this.supervisor = dialogParams.supervisor;
await this._loadRegistries();
await this.updateComplete;
@@ -157,6 +153,7 @@ class HassioRegistriesDialog extends LitElement {
public closeDialog(): void {
this._addingRegistry = false;
this._opened = false;
this._input = {};
}
public focus(): void {
@@ -181,16 +178,17 @@ class HassioRegistriesDialog extends LitElement {
private async _addNewRegistry(): Promise<void> {
const data = {};
data[this._registry!] = {
username: this._username,
password: this._password,
data[this._input.registry!] = {
username: this._input.username,
password: this._input.password,
};
try {
await addHassioDockerRegistry(this.hass, data);
await this._loadRegistries();
this._addingRegistry = false;
} catch (err) {
this._input = {};
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_add"),
text: extractApiErrorMessage(err),
@@ -204,7 +202,7 @@ class HassioRegistriesDialog extends LitElement {
try {
await removeHassioDockerRegistry(this.hass, entry.registry);
await this._loadRegistries();
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_remove"),
text: extractApiErrorMessage(err),
@@ -217,32 +215,20 @@ class HassioRegistriesDialog extends LitElement {
haStyle,
haStyleDialog,
css`
ha-dialog.button-left {
--justify-action-buttons: flex-start;
}
paper-icon-item {
cursor: pointer;
}
.form {
color: var(--primary-text-color);
}
.option {
.registry {
border: 1px solid var(--divider-color);
border-radius: 4px;
margin-top: 4px;
}
mwc-button {
margin-left: 8px;
.action {
margin-top: 24px;
width: 100%;
display: flex;
justify-content: flex-end;
}
mwc-icon-button {
ha-icon-button {
color: var(--error-color);
margin: -10px;
}
mwc-list-item {
cursor: default;
}
mwc-list-item span[slot="secondary"] {
color: var(--secondary-text-color);
margin-right: -10px;
}
`,
];
@@ -1,5 +1,4 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiDelete } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
@@ -9,10 +8,11 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-icon-button";
import {
fetchHassioAddonsInfo,
HassioAddonRepository,
@@ -57,7 +57,7 @@ class HassioRepositoriesDialog extends LitElement {
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
repos
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
.sort((a, b) => (a.name < b.name ? -1 : 1))
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
);
protected render(): TemplateResult {
@@ -89,15 +89,14 @@ class HassioRepositoriesDialog extends LitElement {
<div secondary>${repo.maintainer}</div>
<div secondary>${repo.url}</div>
</paper-item-body>
<mwc-icon-button
<ha-icon-button
.slug=${repo.slug}
.title=${this._dialogParams!.supervisor.localize(
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
.path=${mdiDelete}
@click=${this._removeRepository}
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
</paper-item>
`
)
@@ -185,7 +184,7 @@ class HassioRepositoriesDialog extends LitElement {
this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err) {
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
}
@@ -207,7 +206,7 @@ class HassioRepositoriesDialog extends LitElement {
await this._loadData();
input.value = "";
} catch (err) {
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
this._processing = false;
@@ -229,7 +228,7 @@ class HassioRepositoriesDialog extends LitElement {
addons_repositories: newRepositories,
});
await this._loadData();
} catch (err) {
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ export const suggestAddonRestart = async (
if (confirmed) {
try {
await restartHassioAddon(hass, addon.slug);
} catch (err) {
} catch (err: any) {
showAlertDialog(element, {
title: supervisor.localize(
"common.failed_to_restart_name",
@@ -1,204 +0,0 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@customElement("dialog-supervisor-update")
class DialogSupervisorUpdate extends LitElement {
public hass!: HomeAssistant;
@state() private _opened = false;
@state() private _createBackup = true;
@state() private _action: "backup" | "update" | null = null;
@state() private _error?: string;
@state()
private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
public async showDialog(
params: SupervisorDialogSupervisorUpdateParams
): Promise<void> {
this._opened = true;
this._dialogParams = params;
await this.updateComplete;
}
public closeDialog(): void {
this._action = null;
this._createBackup = true;
this._error = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
public focus(): void {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
);
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
return html`
<ha-dialog .open=${this._opened} scrimClickAction escapeKeyAction>
${this._action === null
? html`<slot name="heading">
<h2 id="title" class="header_title">
${this._dialogParams.supervisor.localize(
"confirm.update.title",
"name",
this._dialogParams.name
)}
</h2>
</slot>
<div>
${this._dialogParams.supervisor.localize(
"confirm.update.text",
"name",
this._dialogParams.name,
"version",
this._dialogParams.version
)}
</div>
<ha-settings-row>
<span slot="heading">
${this._dialogParams.supervisor.localize(
"dialog.update.backup"
)}
</span>
<span slot="description">
${this._dialogParams.supervisor.localize(
"dialog.update.create_backup",
"name",
this._dialogParams.name
)}
</span>
<ha-switch
.checked=${this._createBackup}
haptic
@click=${this._toggleBackup}
>
</ha-switch>
</ha-settings-row>
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
${this._dialogParams.supervisor.localize("common.cancel")}
</mwc-button>
<mwc-button
.disabled=${this._error !== undefined}
@click=${this._update}
slot="primaryAction"
>
${this._dialogParams.supervisor.localize("common.update")}
</mwc-button>`
: html`<ha-circular-progress alt="Updating" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this._action === "update"
? this._dialogParams.supervisor.localize(
"dialog.update.updating",
"name",
this._dialogParams.name,
"version",
this._dialogParams.version
)
: this._dialogParams.supervisor.localize(
"dialog.update.creating_backup",
"name",
this._dialogParams.name
)}
</p>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
</ha-dialog>
`;
}
private _toggleBackup() {
this._createBackup = !this._createBackup;
}
private async _update() {
if (this._createBackup) {
this._action = "backup";
try {
await createHassioPartialBackup(
this.hass,
this._dialogParams!.backupParams
);
} catch (err) {
this._error = extractApiErrorMessage(err);
this._action = null;
return;
}
}
this._action = "update";
try {
await this._dialogParams!.updateHandler!();
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
this._action = null;
}
return;
}
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
.form {
color: var(--primary-text-color);
}
ha-settings-row {
margin-top: 32px;
padding: 0;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-supervisor-update": DialogSupervisorUpdate;
}
}
@@ -1,21 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor;
name: string;
version: string;
backupParams: any;
updateHandler: () => Promise<void>;
}
export const showDialogSupervisorUpdate = (
element: HTMLElement,
dialogParams: SupervisorDialogSupervisorUpdateParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-supervisor-update",
dialogImport: () => import("./dialog-supervisor-update"),
dialogParams,
});
};
+1 -9
View File
@@ -10,7 +10,7 @@ import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import "../../src/layouts/hass-loading-screen";
import { HomeAssistant, Route } from "../../src/types";
import { HomeAssistant } from "../../src/types";
import "./hassio-router";
import { SupervisorBaseElement } from "./supervisor-base-element";
@@ -24,8 +24,6 @@ export class HassioMain extends SupervisorBaseElement {
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route?: Route;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
@@ -113,12 +111,6 @@ export class HassioMain extends SupervisorBaseElement {
: this.hass.themes.default_theme);
themeSettings = this.hass.selectedTheme;
if (themeSettings?.dark === undefined) {
themeSettings = {
...this.hass.selectedTheme,
dark: this.hass.themes.darkMode,
};
}
} else {
themeName =
(this.hass.selectedTheme as unknown as string) ||
+4 -1
View File
@@ -34,6 +34,9 @@ const REDIRECTS: Redirects = {
supervisor_store: {
redirect: "/hassio/store",
},
supervisor_addons: {
redirect: "/hassio/dashboard",
},
supervisor_addon: {
redirect: "/hassio/addon",
params: {
@@ -87,7 +90,7 @@ class HassioMyRedirect extends LitElement {
let url: string;
try {
url = this._createRedirectUrl(redirect);
} catch (err) {
} catch (err: any) {
this._error = this.supervisor.localize("my.error");
return;
}
+4
View File
@@ -35,6 +35,10 @@ class HassioRouter extends HassRouterPage {
backups: "dashboard",
store: "dashboard",
system: "dashboard",
"update-available": {
tag: "update-available-dashboard",
load: () => import("./update-available/update-available-dashboard"),
},
addon: {
tag: "hassio-addon-dashboard",
load: () => import("./addon-view/hassio-addon-dashboard"),
+15 -9
View File
@@ -1,16 +1,22 @@
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
import {
mdiBackupRestore,
mdiCogs,
mdiPuzzle,
mdiViewDashboard,
} from "@mdi/js";
import { atLeastVersion } from "../../src/common/config/version";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../src/types";
export const supervisorTabs: PageNavigation[] = [
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] => [
{
translationKey: "panel.dashboard",
translationKey: atLeastVersion(hass.config.version, 2021, 12)
? "panel.addons"
: "panel.dashboard",
path: `/hassio/dashboard`,
iconPath: mdiViewDashboard,
},
{
translationKey: "panel.store",
path: `/hassio/store`,
iconPath: mdiStore,
iconPath: atLeastVersion(hass.config.version, 2021, 12)
? mdiPuzzle
: mdiViewDashboard,
},
{
translationKey: "panel.backups",
+10 -10
View File
@@ -12,6 +12,7 @@ import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import { nextRender } from "../../../src/common/util/render-status";
import "../../../src/components/ha-icon-button";
import {
fetchHassioAddonInfo,
HassioAddonDetails,
@@ -72,12 +73,11 @@ class HassioIngressView extends LitElement {
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
? html`<div class="header">
<mwc-icon-button
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
<ha-icon-button
.label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
.path=${mdiMenu}
@click=${this._toggleMenu}
>
<ha-svg-icon .path=${mdiMenu}></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
<div class="main-title">${this._addon.name}</div>
</div>
${iframe}`
@@ -91,7 +91,7 @@ class HassioIngressView extends LitElement {
if (requestedAddon) {
try {
addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon);
} catch (err) {
} catch (err: any) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
title: requestedAddon,
@@ -145,7 +145,7 @@ class HassioIngressView extends LitElement {
try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err) {
} catch (err: any) {
await showAlertDialog(this, {
text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor",
@@ -179,7 +179,7 @@ class HassioIngressView extends LitElement {
try {
session = await createSessionPromise;
} catch (err) {
} catch (err: any) {
await showAlertDialog(this, {
text: "Unable to create an Ingress session",
title: addon.name,
@@ -195,7 +195,7 @@ class HassioIngressView extends LitElement {
this._sessionKeepAlive = window.setInterval(async () => {
try {
await validateHassioSession(this.hass, session);
} catch (err) {
} catch (err: any) {
session = await createHassioSession(this.hass);
}
}, 60000);
@@ -241,7 +241,7 @@ class HassioIngressView extends LitElement {
flex-grow: 1;
}
mwc-icon-button {
ha-icon-button {
pointer-events: auto;
}
+6 -2
View File
@@ -25,7 +25,7 @@ import {
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { HomeAssistant } from "../../src/types";
import { HomeAssistant, Route } from "../../src/types";
import { getTranslation } from "../../src/util/common-translation";
declare global {
@@ -38,6 +38,8 @@ declare global {
export class SupervisorBaseElement extends urlSyncMixin(
ProvideHassLitMixin(LitElement)
) {
@property({ attribute: false }) public route?: Route;
@property({ attribute: false }) public supervisor: Partial<Supervisor> = {
localize: () => "",
};
@@ -108,7 +110,9 @@ export class SupervisorBaseElement extends urlSyncMixin(
this._language = this.hass.language;
}
this._initializeLocalize();
this._initSupervisor();
if (this.route?.prefix === "/hassio") {
this._initSupervisor();
}
}
private async _initializeLocalize() {
+14 -32
View File
@@ -2,7 +2,7 @@ import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { atLeastVersion } from "../../../src/common/config/version";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
@@ -12,7 +12,7 @@ import {
fetchHassioStats,
HassioStats,
} from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { restartCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -22,7 +22,6 @@ import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import "../components/supervisor-metric";
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-core-info")
@@ -67,14 +66,15 @@ class HassioCoreInfo extends LitElement {
<span slot="description">
core-${this.supervisor.core.version_latest}
</span>
${this.supervisor.core.update_available
${!atLeastVersion(this.hass.config.version, 2021, 12) &&
this.supervisor.core.update_available
? html`
<ha-progress-button
.title=${this.supervisor.localize("common.update")}
@click=${this._coreUpdate}
>
${this.supervisor.localize("common.update")}
</ha-progress-button>
<a href="/hassio/update-available/core">
<mwc-button
.label=${this.supervisor.localize("common.show")}
>
</mwc-button>
</a>
`
: ""}
</ha-settings-row>
@@ -144,7 +144,7 @@ class HassioCoreInfo extends LitElement {
try {
await restartCore(this.hass);
} catch (err) {
} catch (err: any) {
if (this.hass.connection.connected) {
showAlertDialog(this, {
title: this.supervisor.localize(
@@ -160,27 +160,6 @@ class HassioCoreInfo extends LitElement {
}
}
private async _coreUpdate(): Promise<void> {
showDialogSupervisorUpdate(this, {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
backupParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
},
updateHandler: async () => this._updateCore(),
});
}
private async _updateCore(): Promise<void> {
await updateCore(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "core",
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -239,6 +218,9 @@ class HassioCoreInfo extends LitElement {
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
a {
text-decoration: none;
}
`,
];
}
+59 -104
View File
@@ -9,6 +9,7 @@ import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-icon-button";
import "../../../src/components/ha-settings-row";
import {
extractApiErrorMessage,
@@ -18,10 +19,8 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
configSyncOS,
dataDiskMove,
rebootHost,
shutdownHost,
updateOS,
} from "../../../src/data/hassio/host";
import {
fetchNetworkInfo,
@@ -40,8 +39,9 @@ import {
roundWithOneDecimal,
} from "../../../src/util/calculate";
import "../components/supervisor-metric";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { showHassioDatadiskDialog } from "../dialogs/datadisk/show-dialog-hassio-datadisk";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info")
@@ -105,11 +105,15 @@ class HassioHostInfo extends LitElement {
<span slot="description">
${this.supervisor.host.operating_system}
</span>
${this.supervisor.os.update_available
${!atLeastVersion(this.hass.config.version, 2021, 12) &&
this.supervisor.os.update_available
? html`
<ha-progress-button @click=${this._osUpdate}>
${this.supervisor.localize("commmon.update")}
</ha-progress-button>
<a href="/hassio/update-available/os">
<mwc-button
.label=${this.supervisor.localize("common.show")}
>
</mwc-button>
</a>
`
: ""}
</ha-settings-row>
@@ -181,25 +185,39 @@ class HassioHostInfo extends LitElement {
: ""}
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item @click=${() => this._handleMenuAction("hardware")}>
<ha-icon-button
.label=${this.hass.localize("common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item
.action=${"hardware"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.hardware")}
</mwc-list-item>
${this.supervisor.host.features.includes("haos")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("import_from_usb")}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>`
: ""}
${this.supervisor.host.features.includes("agent")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("data_disk_move")}
>
${this.supervisor.localize("system.host.data_disk_move")}
</mwc-list-item>`
? html`
<mwc-list-item
.action=${"import_from_usb"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>
${this.supervisor.host.features.includes("os_agent") &&
atLeastVersion(this.supervisor.host.agent_version, 1, 2, 0)
? html`
<mwc-list-item
.action=${"move_datadisk"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize(
"system.host.move_datadisk"
)}
</mwc-list-item>
`
: ""}
`
: ""}
</ha-button-menu>
</div>
@@ -222,25 +240,31 @@ class HassioHostInfo extends LitElement {
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
});
private async _handleMenuAction(action: string) {
switch (action) {
private async _handleMenuAction(ev) {
switch ((ev.target as any).action) {
case "hardware":
await this._showHardware();
break;
case "import_from_usb":
await this._importFromUSB();
break;
case "data_disk_move":
await this._dataDiskMove();
case "move_datadisk":
await this._moveDatadisk();
break;
}
}
private _moveDatadisk(): void {
showHassioDatadiskDialog(this, {
supervisor: this.supervisor,
});
}
private async _showHardware(): Promise<void> {
let hardware;
try {
hardware = await fetchHassioHardwareInfo(this.hass);
} catch (err) {
} catch (err: any) {
await showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list"
@@ -270,7 +294,7 @@ class HassioHostInfo extends LitElement {
try {
await rebootHost(this.hass);
} catch (err) {
} catch (err: any) {
// Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
@@ -300,7 +324,7 @@ class HassioHostInfo extends LitElement {
try {
await shutdownHost(this.hass);
} catch (err) {
} catch (err: any) {
// Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
@@ -312,50 +336,6 @@ class HassioHostInfo extends LitElement {
button.progress = false;
}
private async _osUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.update.title",
"name",
"Home Assistant Operating System"
),
text: this.supervisor.localize(
"confirm.update.text",
"name",
"Home Assistant Operating System",
"version",
this.supervisor.os.version_latest
),
confirmText: this.supervisor.localize("common.update"),
dismissText: "no",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await updateOS(this.hass);
fireEvent(this, "supervisor-collection-refresh", { collection: "os" });
} catch (err) {
if (this.hass.connection.connected) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Home Assistant Operating System"
),
text: extractApiErrorMessage(err),
});
}
}
button.progress = false;
}
private async _changeNetworkClicked(): Promise<void> {
showNetworkDialog(this, {
supervisor: this.supervisor,
@@ -379,7 +359,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", {
collection: "host",
});
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_set_hostname"),
text: extractApiErrorMessage(err),
@@ -394,7 +374,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", {
collection: "host",
});
} catch (err) {
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_import_from_usb"
@@ -404,34 +384,6 @@ class HassioHostInfo extends LitElement {
}
}
private async _dataDiskMove(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.host.data_disk_move"),
text: html`${this.supervisor.localize(
"dialog.data_disk_move.description",
{ current_path: this.supervisor.os.data_disk }
)} <br /><br />${this.supervisor.localize(
"dialog.data_disk_move.confirm_text"
)}`,
confirmText: this.supervisor.localize("dialog.data_disk_move.move"),
dismissText: this.supervisor.localize("dialog.data_disk_move.cancel"),
});
if (!confirmed) {
return;
}
try {
await dataDiskMove(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_move"),
text: extractApiErrorMessage(err),
});
}
}
}
private async _loadData(): Promise<void> {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
fireEvent(this, "supervisor-collection-refresh", {
@@ -501,6 +453,9 @@ class HassioHostInfo extends LitElement {
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
a {
text-decoration: none;
}
`,
];
}

Some files were not shown because too many files have changed in this diff Show More