Compare commits

...
Author SHA1 Message Date
Paulus Schoutsen f2773821eb Revert accidental added draft changes 2022-09-03 22:41:19 -04:00
Philip AllgaierandGitHub 81cc745c0a Align wording in automation and script editor overflow menus (#13575) 2022-09-03 13:22:42 -04:00
Michel van de WeteringandGitHub 43f9c9ebc9 Add mediadescription for channel media type (#13434) 2022-09-03 11:44:02 +02:00
Michel van de WeteringandGitHub a9d1feb196 Hide soundmode when mediaplayer is off or unavailable (#13347) 2022-09-03 11:41:24 +02:00
Paulus Schoutsen 72aea57105 Bumped version to 20220902.0 2022-09-02 16:14:53 -04:00
Philip AllgaierandGitHub 031ecf5be8 Align visuals of automation and script editor after redesign (#13567) 2022-09-02 13:03:53 -04:00
Paul BotteinandGitHub 93e7927686 Fix automation trace link (#13563) 2022-09-02 13:02:50 -04:00
Paul BotteinandGitHub 320d8e6190 Improve blueprint editor layout (#13564) 2022-09-02 13:02:36 -04:00
Paul BotteinandGitHub efa4f65686 Add information in overflow menu (#13570) 2022-09-02 13:02:01 -04:00
Paul BotteinandGitHub ec257710ff Add overflow menu to automation picker (#13569) 2022-09-02 13:01:05 -04:00
Paulus SchoutsenandGitHub ffad6f340f Fix some descriptions (#13562) 2022-09-01 21:43:30 -05:00
Zack BarettandGitHub 9f9b0b6457 Bumped version to 20220901.0 (#13560) 2022-09-01 20:11:10 +00:00
Zack BarettandGitHub a4227680de Fix Unable to select last slot (#13559) 2022-09-01 19:54:32 +00:00
Zack BarettandGitHub 5cfd263617 Fix Sunday issue and add minutes to events (#13556) 2022-09-01 16:06:51 +00:00
Bram KragtenandGitHub 430e671901 unique-id -> id (#13552) 2022-09-01 11:51:38 -04:00
Ernst KlamerandGitHub 8fcd396445 Add icon for device class moisture (#13553) 2022-09-01 11:48:04 -04:00
Erik MontnemeryandGitHub e273b6b659 Improve delete device button and confirmation dialog (#13500) 2022-09-01 10:38:56 -05:00
Bram KragtenandGitHub 2751adf440 remove duplicate controls blueprint automation (#13554) 2022-09-01 10:37:22 -05:00
Matthias de BaatandGitHub d661450121 Typo on dialogs page (#13555) 2022-09-01 10:36:56 -05:00
Steve RepsherandGitHub 4511ded205 Add semantic heading to script editor (#13546) 2022-09-01 16:19:08 +02:00
Erik MontnemeryandGitHub 2bf0c5d72d Only update device class if changed by user (#13551) 2022-09-01 12:25:32 +00:00
604e5d5e09 Add semantic headings to automation editor (#13542)
Co-authored-by: Bram Kragten <[email protected]>
2022-08-31 14:15:14 -05:00
Bram KragtenandGitHub 774aee406c Fix tag trigger (#13540)
* Fix tag trigger

* Fix
2022-08-31 12:28:50 -04:00
Zack BarettandGitHub 775837b60f Bumped version to 20220831.0 (#13539) 2022-08-31 18:28:31 +02:00
Bram KragtenandGitHub 030b2b921a Format duration object (#13538) 2022-08-31 16:24:24 +00:00
Bram KragtenandGitHub 229bc26327 Don't fire change on tag picker when not changed (#13537) 2022-08-31 16:08:35 +00:00
Bram KragtenandGitHub 99e85173eb Change padding buttons entity settings (#13536) 2022-08-31 15:48:54 +00:00
Bram KragtenandGitHub be0c22d7ae Fix header mobile automation editor (#13534) 2022-08-31 15:42:00 +00:00
Zack BarettandGitHub fee1092a08 Fix Selection on IOS (#13533) 2022-08-31 15:40:03 +00:00
Bram KragtenandGitHub d041bd9fd3 use device and area names in service call description (#13532) 2022-08-31 15:30:44 +00:00
Matthias de BaatandGitHub 93debac19a Add dialogs guidelines (#13526)
* Add dialogs guidelines

* Updated the Buttons and X-icon text
2022-08-31 11:00:19 -04:00
Bram KragtenandGitHub 3fc94106b8 Add entities, devices and areas to hass (#13530) 2022-08-31 13:15:21 +00:00
Bram KragtenandGitHub e976f9c119 Simplify action descriptions (#13529) 2022-08-31 12:48:28 +00:00
Bram KragtenandGitHub be4dcbe405 Fix dialog placement and overflow (#13527) 2022-08-31 07:55:26 -04:00
Paul BotteinandGitHub c116ad67ed Fix multiple sortable (#13522) 2022-08-31 12:01:36 +02:00
Erik MontnemeryandGitHub 1e19799da9 Improve handling of units in energy dashboard (#13454) 2022-08-31 11:30:50 +02:00
Paulus Schoutsen 9b2dcbdb59 Fix script/size_stats 2022-08-30 21:53:17 -04:00
Steve RepsherandGitHub ae4a37f23a Combo-box accessibility and other fixes (#13496) 2022-08-30 21:50:04 -04:00
puddlyandGitHub e463a997c1 Add a button for launching the ZHA options flow to reconfigure the radio (#13506)
* Add a button for launching the ZHA options flow to reconfigure the radio

* Remove unnecessary whitespace

* Rename `Reconfigure` to `Migrate Radio`

* Rename `Download Network Backup` to `Download Backup`
2022-08-30 21:46:20 -04:00
Zack BarettandGitHub 92c8de307d Add CPU and Memory Graphs to hardware page (#13399)
* CPU and Memory Graphs

* localize

* Comments

* Always show the graphs

* Use Subscribe Mixin
2022-08-30 21:45:37 -04:00
Zack BarettandGitHub c751b0b759 Fix schedule bug (#13521) 2022-08-30 21:45:00 -04:00
Erik MontnemeryandGitHub cb5621032d Allow controlling an input_select in unknown state (#13524) 2022-08-30 14:24:05 -04:00
Paul BotteinandGitHub e861460318 Fix drag and drop entities on firefox (#13518) 2022-08-30 11:34:18 -04:00
Zack BarettandGitHub 8fd99fcb15 Fix Schedule Helper UI (#13515) 2022-08-29 21:51:07 -04:00
Bram KragtenandGitHub 260855abbb fix buttons at bottom of entity settings dialog (#13517) 2022-08-29 15:56:57 -05:00
Paulus SchoutsenandGitHub 3648c8c07a Revert "Add redirect_uri to each interaction with login flow (#13389)" (#13504) 2022-08-29 20:23:40 +02:00
Yosi LevyandGitHub e74fd5fcdc Fix RTL issue on date selector in lower resolutions & color slider (#13326) 2022-08-29 20:23:06 +02:00
uvjustinandGitHub 2e46b04204 Bump hls.js to v1.2.1 (#13497) 2022-08-29 20:22:05 +02:00
Franck NijhofandGitHub 989a0b9173 Fix media players with unknown state not being able to browse media (#13502) 2022-08-29 12:14:56 -04:00
Joakim SørensenandGitHub 80a2a7b989 Adds fixed schedule entity icon (#13514) 2022-08-29 11:19:14 -04:00
Franck NijhofandGitHub 2547a975f6 Always show description field in automation editor (#13503) 2022-08-26 13:25:01 +00:00
Franck NijhofandGitHub 35fa763086 Automation icon tweaks (#13495) 2022-08-25 20:39:21 -04:00
Franck NijhofandGitHub 44e38cd24e Redesign automation editor main settings (#13488) 2022-08-25 19:34:39 +00:00
Franck NijhofandGitHub ad58d16dfa Move condition test button into overflow menu (#13478) 2022-08-25 18:57:29 +00:00
Franck NijhofandGitHub 98352ae7b7 Replace service call icon (#13491) 2022-08-25 14:23:23 -04:00
J. Nick KostonandGitHub b9fbad663d Bump home-assistant-js-websocket to 8.0.0 (#13492) 2022-08-25 18:03:34 +00:00
fd166fa89e Show when an integration is being setup in the UI (#13446)
Co-authored-by: Paulus Schoutsen <[email protected]>
2022-08-25 14:01:06 -04:00
Franck NijhofandGitHub 88decba851 Move all destructive device actions to the bottom of the overflow menu (#13490) 2022-08-25 18:33:09 +02:00
Franck NijhofandGitHub 8db1881a93 Hide else/default in if/choose actions (#13480) 2022-08-25 10:49:32 -05:00
Franck NijhofandGitHub 0038f54cea Add missing icon to ZHA device remove action (#13489) 2022-08-25 10:43:54 -05:00
Yosi LevyandGitHub a3d80f1280 RTL heater+state fixes (#13366) 2022-08-25 14:20:04 +00:00
Joakim SørensenandGitHub 1c05bc6380 Catch reload issues (#13487) 2022-08-25 15:02:30 +02:00
Erik MontnemeryandGitHub 5d1536030a Refactor getStatisticLabel (#13486) 2022-08-25 12:53:16 +00:00
Erik MontnemeryandGitHub a8833a5ec1 Use name from statistics metadata in energy config panel (#13474) 2022-08-25 13:47:44 +02:00
Erik MontnemeryandGitHub 6446534e0b Use name from statistics metadata in energy dashboard (#13469) 2022-08-25 13:47:32 +02:00
Erik MontnemeryandGitHub d64c81a123 Use name from metadata in statistics card (#13451)
* Use name from metadata in statistics card

* Refactor to use getStatisticLabel
2022-08-25 13:47:16 +02:00
Paulus SchoutsenandGitHub 80e7993923 Move restored entity warning up and use ha-alert (#13477) 2022-08-25 13:09:31 +02:00
Franck NijhofandGitHub 700af72303 Add icon to trigger/condition/action editor rows (#13481) 2022-08-25 13:04:40 +02:00
Joakim SørensenandGitHub 255cb23c7d Show homeassistant when creating partial backup (#13482) 2022-08-25 11:26:56 +02:00
Florian RüchelandGitHub 669f7efa97 Fix scripts docs link (#13484)
In home-assistant/home-assistant.io#22391 the editor docs were removed
making the current link invalid. This points the docs link to the syntax
docs one level up.
2022-08-25 11:01:52 +02:00
Paulus SchoutsenandGitHub 166d6f1c88 Show graph for zone in more info (#13475) 2022-08-24 09:18:45 -05:00
Paulus SchoutsenandGitHub d64ade3848 Scroll to new added trigger/condition/row (#13473) 2022-08-24 08:58:29 -05:00
Franck NijhofandGitHub c2542a3baa Use yarn to resolve lint-staged in pre-commit (#13470) 2022-08-24 09:27:35 -04:00
Franck NijhofandGitHub 6b7c00edbc Change integration enable/disable icons to match automations (#13472) 2022-08-24 08:00:33 -05:00
Franck NijhofandGitHub 89c6fa7383 Conditionally add extra divider in integration card overflow menu (#13468) 2022-08-24 08:00:13 -05:00
Franck NijhofandGitHub ca91f71d2e Remove default actions/conditions from parallel and if actions (#13467) 2022-08-24 07:58:12 -05:00
Bram KragtenandGitHub 25e0c05723 Fix padding navigation list (#13466) 2022-08-24 11:53:51 +02:00
Steve RepsherandGitHub 8fd5273fae Fix various issues in time/duration input (#13462) 2022-08-24 11:25:29 +02:00
807bb10199 Add some Trigger Descriptions (#13460)
Co-authored-by: Franck Nijhof <[email protected]>
Co-authored-by: Bram Kragten <[email protected]>
2022-08-24 09:18:50 +00:00
Franck NijhofandGitHub 5ce81232b5 Fix automation/script conditions editor (#13465) 2022-08-24 10:59:27 +02:00
Steve RepsherandGitHub a1bc748bc1 Fix accessibility of settings and system navigation (#12845) 2022-08-23 23:45:44 -04:00
Franck NijhofandGitHub be169f9c83 Redesign trigger/condition/action overflow menus (#13453)
* Redesign trigger/condition/action overflow menus

* Reorder items, changed enable/disable icons, cleanup aria

* Simplify menu item names
2022-08-23 18:26:38 -04:00
Franck NijhofandGitHub ed82ae9f68 Add config entry selector (#13432)
* Add config entry selector

* Let backend filter by integration

* Add to gallery

* Adjust translation

* Fix imports

* Rename in gallery as well

* Fix typo in localize key

* Prettier
2022-08-23 18:26:15 -04:00
Franck NijhofandGitHub 8bcbeb299b Add icons to integration action overflow menu (#13457) 2022-08-23 21:37:37 +02:00
fc1481d365 Add icons to device action overflow menu (#13455)
* Add icons to device action overflow menu

* Update size of meta icon

* Tweak naming as suggest by Paulus

* Missed on suggestion, also adjusted

* Delete device -> Delete

* View network

* Rename key

* Prettier

Co-authored-by: Zack <[email protected]>
2022-08-23 19:13:22 +00:00
Paulus SchoutsenandGitHub 2475f6bd41 Glue percent sign (#13458) 2022-08-23 15:45:08 +00:00
Franck NijhofandGitHub dff3ffe935 Add support for renaming actions, conditions and triggers (#13444) 2022-08-23 17:21:16 +02:00
puddlyandGitHub 1616911ba9 Display ZHA network settings and allow downloading backups (#13415) 2022-08-23 10:24:38 -04:00
Franck NijhofandGitHub 8d18fb79fb Use icons in add trigger/condition/action menus (#13452) 2022-08-23 09:13:39 -05:00
Zack BarettandGitHub f5b44656cf Update a few Condition Descriptions (#13404)
* Update a few Describe-conditions

* Update to multiple states
2022-08-23 10:12:55 -04:00
Zack BarettandGitHub c82782fa1b Update Automation Picker Table (#13405) 2022-08-23 09:47:15 -04:00
Franck NijhofandGitHub ab14cf9e9b Use trigger alias in trace timelines (#13447) 2022-08-23 08:58:05 -04:00
Franck NijhofandGitHub c0051aeb68 Tweak displayed action/condition/trigger names (#13445) 2022-08-22 20:25:27 -05:00
Franck NijhofandGitHub 44422086d7 Add alias support to all triggers (#13442) 2022-08-22 16:08:32 -05:00
Franck NijhofandGitHub 738367a7c7 Use alias instead of description in case set in actions/conditions (#13441) 2022-08-22 13:27:13 -05:00
Franck NijhofandGitHub 5fb2e3316a Use number selector for above & below in numeric trigger/condition (#13422) 2022-08-22 11:16:11 -05:00
Franck NijhofandGitHub 82f48d106f Use template selector in numeric state templates (#13428) 2022-08-22 12:04:36 -04:00
Steve RepsherandGitHub bbc5b02a22 Tighten UI localize key exceptions (#13430) 2022-08-22 12:04:02 -04:00
Paulus SchoutsenandGitHub dfface6904 Merge more info "info" and "history" like before (#13425) 2022-08-22 13:56:48 +00:00
Franck NijhofandGitHub 9ed0cb3011 Use duration input for timeout in wait_for_trigger action (#13426)
* Use duration input for timeout in wait_for_trigger action

* Specify event type
2022-08-22 08:55:26 -04:00
Franck NijhofandGitHub 4b54cb4a35 Make updates more distinct recognizable by device name (#13433) 2022-08-22 08:17:52 -04:00
Franck NijhofandGitHub 1b5c30712e Rename exclude_attributes to hide_attributes for clarity (#13436) 2022-08-22 11:25:17 +02:00
Joakim SørensenandGitHub 7d3d800d4c Fix progressbar margins (#13435) 2022-08-22 10:46:07 +02:00
Franck NijhofandGitHub d4262ecb09 Use dropdown mode for script mode selector (#13429) 2022-08-20 22:00:20 -04:00
Franck NijhofandGitHub ec7dea93a0 Allow days in calendar trigger offset (#13427) 2022-08-20 18:38:44 -04:00
aa2641d5c9 Add exclude attributes support to attribute selector (#13421)
* Add exclude attribute support to attribute selector

* Fix typing

* Fix rebase f-up

* Revert const removal

* Make exclude_attributes readonly and fix some propert mismatches

Co-authored-by: Zack <[email protected]>
Co-authored-by: Steve Repsher <[email protected]>
2022-08-20 14:54:42 -04:00
Paulus SchoutsenandGitHub 5ecde44243 Do not hide overflow in expansion panel when expanded (#13423) 2022-08-20 18:38:03 +00:00
Paulus SchoutsenandGitHub 209ba79823 Expand trigger/condition/row when menu item changes editor (#13424) 2022-08-20 14:29:43 -04:00
Steve RepsherandGitHub 52a1594969 Remove all exceptions from supervisor localize keys (#13402) 2022-08-20 13:37:57 -04:00
Steve RepsherandGitHub 24509425ca Fix some localize key type errors in lovelace editors (#13403) 2022-08-20 13:36:58 -04:00
ildar170975andGitHub 7e5cd9a1c8 Logbook card: place a gap between an icon/image & a text (#13348) 2022-08-20 14:29:29 +00:00
Franck NijhofandGitHub 8b13a9ff2e Add attribute support to state selector (#13420) 2022-08-20 09:24:11 -05:00
Paulus SchoutsenandGitHub b33c546610 Combine more info and entity registry editor (#13416) 2022-08-19 21:27:07 -05:00
Franck NijhofandGitHub 38fd6108b4 Keep formatted attribute name in attribute selector (#13413) 2022-08-19 08:44:42 -05:00
Franck NijhofandGitHub 57fdea19fd Fix trigger state attribute selector (#13410) 2022-08-19 08:44:02 -05:00
Franck NijhofandGitHub d2a19e04ef Add state selector (#13411) 2022-08-19 09:20:13 -04:00
Steve RepsherandGitHub 196456d0c4 Add translations to nightly artifacts (#13409) 2022-08-19 08:57:58 -04:00
Paulus SchoutsenandGitHub 5c16447eed Fix DOM reuse for trigger/condition/action (#13407) 2022-08-19 08:05:15 -04:00
Paulus SchoutsenandGitHub f3d92ba0e0 Fix Gallery menu expansion (#13408) 2022-08-18 23:32:56 -04:00
Steve RepsherandGitHub 088b3587e0 Remove string exception for localize keys (#13354) 2022-08-18 12:43:15 -04:00
Paulus SchoutsenandGitHub ede9d8a073 Add back learn more labels to automation editor (#13401) 2022-08-18 16:17:19 +00:00
Paulus SchoutsenandGitHub 8c71885b4c Add support for the file selector (#13382) 2022-08-18 11:43:43 -04:00
Paulus SchoutsenandGitHub 47b820d28f Collapse automation/script editor sections by default (#13390) 2022-08-18 14:04:35 +00:00
Steve RepsherandGitHub d7b888f761 Fix localize key types related to form schemas (Group 3) (#13400)
* Fix key type errors for card editors (Round 4)

* Fix key type errors for remaining form schemas
2022-08-17 23:57:26 -04:00
Paulus SchoutsenandGitHub 12e57dfcae Allow testing describe functionality in the gallery (#13398) 2022-08-17 13:20:35 -05:00
Steve RepsherandGitHub 9a1fc02755 Fix localize key types related to form schemas (Group 2) (#13342) 2022-08-17 11:01:05 -05:00
Steve RepsherandGitHub eb4dbef610 Bump husky & lint-staged and prevent translation edits (#13392) 2022-08-17 08:09:07 -04:00
Paulus Schoutsen 33ce27de02 Bumped version to 20220816.0 2022-08-16 17:18:38 -04:00
Paulus SchoutsenandGitHub 089f531492 Add redirect_uri to each interaction with login flow (#13389) 2022-08-16 17:17:18 -04:00
uvjustinandGitHub 3aa813e391 Bump hls.js to v1.2.0 (#13383) 2022-08-16 09:10:18 -05:00
Zack BarettandGitHub a989eb1c66 Fix Target Selector (#13380) 2022-08-13 20:56:15 -05:00
alvinchen1andGitHub e0448be24d Add initial field to the helper input_number in UI (#13378) 2022-08-13 17:29:33 -04:00
651cafc464 Allow Markdown and description placeholder usage in data field descriptions (#13377)
Co-authored-by: Zack Barett <[email protected]>
2022-08-13 18:57:28 +00:00
Zack BarettandGitHub 38607a6410 Add UI for Schedule Helper (#13375) 2022-08-12 13:58:08 +00:00
Steve RepsherandGitHub 9046c0d0bf Merge pull request #13372 from steverep/fix-more-keys
Fix more bad or missing localize keys
2022-08-10 16:48:12 -04:00
Steve Repsher 589cec10f6 Fix bad key for quick bar hint
Also fixed a bunch of lit plugin errors.
2022-08-10 16:25:01 -04:00
Steve Repsher dba9658658 Fix bad key for add entities to view 2022-08-10 15:59:15 -04:00
Steve Repsher d21bdf2807 Add missing key in logbook card 2022-08-10 15:30:58 -04:00
Steve Repsher 3fe5075ad4 Add missing key in input_select helper dialog 2022-08-10 12:32:46 -04:00
Steve RepsherandGitHub f76a3ea2ce Merge pull request #13364 from steverep/fix-random-bad-keys 2022-08-09 12:50:50 -04:00
Steve Repsher e95a5ebbbf Merge changes in repair flow header 2022-08-09 12:22:47 -04:00
Steve Repsher ae28eb3813 Fix bad key in system information 2022-08-09 12:04:06 -04:00
Steve Repsher b0807cb80c Fix bad keys in repair flow dialog 2022-08-09 11:55:10 -04:00
Steve Repsher 95231554d5 Add missing key for required tag name 2022-08-09 11:01:51 -04:00
Steve Repsher f3b543f46c Fix bad key on error screen 2022-08-08 18:21:39 -04:00
Steve Repsher 9eb81e2211 Add missing key for area not found 2022-08-08 17:59:15 -04:00
Franck NijhofandGitHub 1322ff9295 Process description placeholders in titles for repairs flows (#13362) 2022-08-08 17:36:46 -04:00
Zack BarettandGitHub 5f169b48d9 Merge pull request #13360 from steverep/fix-blueprint-target-types 2022-08-08 12:27:03 -05:00
Steve Repsher 75d05cdb0e Fix key type errors in target picker 2022-08-08 12:12:13 -04:00
Steve Repsher b444d0030f Fix key type errors for blueprints 2022-08-08 12:10:56 -04:00
Zack BarettandGitHub e241b20378 Merge pull request #13357 from steverep/fix-some-card-keys 2022-08-08 09:38:27 -05:00
Joakim SørensenandGitHub ca28feca80 Missing import and refresh correct collection (#13358) 2022-08-08 10:16:13 +02:00
Zack BarettandGitHub f5d9a7d662 Merge pull request #13353 from home-assistant/Make-sure-we-have-supervisor.addon 2022-08-07 16:56:33 -05:00
Steve Repsher 3d236a8f49 Fix key type errors for cloud TTS 2022-08-07 15:34:26 -04:00
Steve Repsher 0ebeec0db6 Fix key type errors for media player 2022-08-07 15:33:08 -04:00
Steve Repsher d23d774ec1 Fix some key type errors in cards 2022-08-07 15:28:22 -04:00
Joakim SørensenandGitHub 0d5b86e2b6 Check store for addons when using my link (#13352) 2022-08-07 12:56:44 -04:00
ludeeus 825558c8db Make sure we have supervisor.addon 2022-08-07 16:53:09 +00:00
Zack BarettandGitHub 12239d7fe3 Merge pull request #13344 from steverep/update-vaadin 2022-08-05 13:52:56 -05:00
Zack BarettandGitHub 6eac6aef18 Merge pull request #13284 from raman325/ws_api 2022-08-05 12:56:47 -05:00
Steve Repsher a79d6b6a4d Upgrade vaadin to 23.1.5 for combobox accessibility 2022-08-05 12:31:07 -04:00
Steve RepsherandGitHub 1d47303127 Separate supervisor localize key exceptions and disallow string (#13317) 2022-08-05 15:39:19 +02:00
Steve RepsherandGitHub 150bc00c31 Fix localize key types related to form schemas (Group 1) (#13258) 2022-08-05 15:37:54 +02:00
Zack BarettandGitHub d4232a2256 Bumped version to 20220802.0 (#13328) 2022-08-02 17:06:00 +02:00
Zack BarettandGitHub f7e348c19b Fix Automation Creation Dialog (#13322) 2022-08-02 13:48:22 +00:00
Zack BarettandGitHub f44fd35b90 Fix input and number more info dialog (#13321) 2022-08-02 15:26:17 +02:00
Erik MontnemeryandGitHub dac1d76bd2 Offer to remove statistics for entities with unsupported state class (#13325) 2022-08-02 11:12:05 +00:00
Franck NijhofandGitHub 0ab823bcf5 Adjust MDI icon for Repairs menu (#13324) 2022-08-02 09:33:36 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
65e952aaeb Bump actions/stale from 5.1.0 to 5.1.1 (#13313)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-02 09:25:58 +02:00
Zack BarettandGitHub cfdf043444 Add ability to auto open system health with params (#13303)
* Add ability to auto open system health with params

* Update my link

* Update url when opening dialog

* comment
2022-08-02 09:23:30 +02:00
Zack BarettandGitHub ecc1bf5206 Merge pull request #13319 from home-assistant/fix-device-page-strip 2022-08-01 16:03:50 -05:00
Zack BarettandGitHub 57d664d87d Merge pull request #13318 from home-assistant/fix-create-helper 2022-08-01 15:47:11 -05:00
Zack cf2cd4043d Fix Device Page name stripping 2022-08-01 15:41:01 -05:00
Bram Kragten 98761cab3f Fix create helper dialog
Fixes #13274
2022-08-01 22:05:52 +02:00
Erik MontnemeryandGitHub 4a622f9424 Tweak suggested_value in HA-form (#13316) 2022-08-01 09:25:33 +00:00
Zack BarettandGitHub c27e3325d9 Bumped version to 20220728.0 (#13300) 2022-07-28 10:10:28 -05:00
Zack BarettandGitHub 08efc2fdd1 Update dialog with status line and other stuff (#13293) 2022-07-28 15:10:05 +00:00
Zack BarettandGitHub 53519ae8ab Fix Dark Mode Map when set in Dashboard card (#13297) 2022-07-28 10:00:37 -05:00
Zack BarettandGitHub 9baeabed19 Add integration dialog Scroll bar styles (#13299) 2022-07-28 09:54:41 -05:00
Zack BarettandGitHub f3229bb8a7 Update Show Skipped/ignored in updates/repairs, update dialog of integration startup time (#13296) 2022-07-28 10:39:16 +02:00
Franck NijhofandGitHub 86c971b76a Add My support for Repairs (#13294) 2022-07-28 02:07:07 +02:00
Paulus SchoutsenandGitHub afc69cb270 Render brand icon for repairs based on issue_domain (#13290) 2022-07-27 22:53:59 +02:00
Paulus SchoutsenandGitHub a379d29a6c Remove non fixable from repair dialog (#13292) 2022-07-27 15:50:07 -05:00
Franck NijhofandGitHub 0769b14566 Add Bluetooth as discovery source (#13291) 2022-07-27 20:30:51 +00:00
Paulus SchoutsenandGitHub 1dc68b72da Pass translation placeholders to repair title translations (#13289) 2022-07-27 19:30:22 +00:00
Bram KragtenandGitHub c08be957ce Merge branch 'master' into dev 2022-07-27 12:23:47 +02:00
Bram Kragten 140e269697 Bumped version to 20220727.0 2022-07-27 12:22:43 +02:00
Bram KragtenandGitHub 7c18d5aa0e Use subscribe to fetch repair issues (#13285) 2022-07-27 12:15:31 +02:00
Yosi LevyandGitHub 1acdc9cd6c Various RTL fixes (#13268) 2022-07-27 11:40:50 +02:00
Yosi LevyandGitHub 7501849044 Fix conversation RTL + text-alignment (#13264) 2022-07-27 11:39:44 +02:00
Yosi LevyandGitHub 26ed13e548 RTL - Humidifier more info location (#13253) 2022-07-27 11:38:35 +02:00
Steve RepsherandGitHub 086c33d8b3 Fix localize key types to remove user groups exception (#13259) 2022-07-27 11:37:52 +02:00
c73677f15d Move System Information to Repairs (#13281)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-27 09:23:55 +00:00
f7090583ac Update Repairs to new design (#13276)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-27 09:06:38 +00:00
68517018cc Add IP Information Dialog (#13283)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-27 08:47:03 +00:00
Raman Gupta 62a0a64554 Update zwave_js WS API commands 2022-07-27 00:08:06 -04:00
Zack BarettandGitHub adf3fa6a0e Use new subscribe mixin for the sidebar to show counts of repairs (#13282)
* Use new subscribe mixin for the sidebar

* spelling
2022-07-26 15:43:46 -05:00
Bram KragtenandGitHub b443ec0af5 Repair issue fixes (#13272) 2022-07-25 15:37:33 +02:00
Zack BarettandGitHub b9ae0e72b1 Add Ignore Action + dialog updates (#13254) 2022-07-25 11:36:00 +02:00
Zack BarettandGitHub 63ea8e6568 Allows for My to support Supported Brands (#13256) 2022-07-25 11:35:13 +02:00
Steve RepsherandGitHub 5be624f45d Fix localize key types to remove config_entry exception (#13257) 2022-07-22 11:01:44 +02:00
Bram KragtenandGitHub 38f19b6180 Repair: load translations for all integrations with issues (#13252) 2022-07-21 09:52:44 -05:00
Steve RepsherandGitHub c99f00ba50 Setup stronger type for localize key (#13244) 2022-07-21 13:13:11 +00:00
ce5776f59d Add Repairs to Settings (#13249)
Co-authored-by: Zack Barett <[email protected]>
Co-authored-by: Bram Kragten <[email protected]>
2022-07-21 14:48:37 +02:00
Steve RepsherandGitHub 12ff70020a Fix more bad localize keys (#13250) 2022-07-21 12:17:29 +02:00
5d605447a5 Support more icons for media players (#12997)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-21 12:16:00 +02:00
Michael IrigoyenandGitHub 6d88d46ce4 Update Material Design Icons to v7.0.96 (#13175)
* Update Material Design Icons to v7.0.96

* Fetch updated MDI packages
2022-07-21 11:43:48 +02:00
Bram KragtenandGitHub cbe2643146 Use translation_key for repairs (#13246) 2022-07-20 11:52:55 -05:00
d332b8ab14 Fix bad localize keys (#13245)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-20 18:34:09 +02:00
Bram KragtenandGitHub adfef05110 Bump contrast and brightness of dark mode map (#13243) 2022-07-20 14:15:34 +00:00
Yosi LevyandGitHub ca6a7bfbe2 Additional RTL energy fixes (#13182) 2022-07-20 14:37:48 +02:00
Franck NijhofandGitHub a22f96a481 Migrate repairs to repairs API (#13242) 2022-07-20 14:34:57 +02:00
Yosi LevyandGitHub 688109524d RTL fixes - media, attributes (#13241) 2022-07-20 11:14:50 +02:00
62dd7111ce Update design.home-assistant.io (#13240)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-20 08:24:52 +00:00
Zack BarettandGitHub 1267575f62 Merge pull request #13235 from home-assistant/fix-builds
Stringify Python version to use 3.10 vs 3.1
2022-07-19 23:25:39 -05:00
Paulus Schoutsen b7da4dc68f Stringify Python version to use 3.10 vs 3.1 2022-07-19 21:11:21 -07:00
Zack BarettandGitHub 826474518f Merge pull request #13228 from voydz/lovelace-map-autofit 2022-07-19 16:44:48 -05:00
Maximilian ErtlandGitHub a4b92fef3a Correctly set "allow-downloads" flag on iframes (#13218) 2022-07-19 20:59:45 +02:00
Bram KragtenandGitHub d41159591c Change map styles to "Voyager" (#13227) 2022-07-19 20:56:50 +02:00
Felix Rudat cb256bc386 Add auto_fit config option to lovelace map card 2022-07-19 17:45:48 +02:00
bd50d6a6a3 Start the Repairs dashboard (#13192)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-19 14:25:47 +00:00
Bram KragtenandGitHub 05418fc83b Fix dev tools event (#13225) 2022-07-19 11:17:13 +00:00
Franck NijhofandGitHub 8b675cdbba Remove unused mypy config from pyproject (#13224) 2022-07-19 13:14:20 +02:00
Franck NijhofandGitHub 36b4909950 Bump Python to 3.10 (#13223) 2022-07-19 12:28:57 +02:00
Raman GuptaandGitHub 157b3ba5f2 Clean up zwave_js device actions logic (#13185) 2022-07-19 12:11:05 +02:00
Yosi LevyandGitHub 72443b4f24 RTL card fixes (#13207) 2022-07-19 12:09:50 +02:00
J. Nick KostonandGitHub 1c7d3fe610 Sync frontend recoverable states with core (#13197) 2022-07-19 12:09:20 +02:00
Franck NijhofandGitHub 6ac4560b36 Use YAML in developer tools events (#13222) 2022-07-19 12:07:51 +02:00
Zack BarettandGitHub 9309a4c7bc Update language when ZHA or Zwave arent installed (re: supported brands) (#13191) 2022-07-19 11:44:02 +02:00
Franck NijhofandGitHub b582a4d014 Bump node to 16 (#13221) 2022-07-19 11:39:08 +02:00
Zack BarettandGitHub e4d233afa8 Filter Integration in Target and Area selectors + clean up some code (#13202) 2022-07-18 22:07:55 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b131b255ec Bump actions/stale from 3.0.13 to 5.1.0 (#13212)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 17:09:37 +02:00
Kendell RandGitHub 20bdb9ff35 Use theme default font for charts (#13210)
* Use theme default font for charts

* Prettier
2022-07-18 15:35:51 +02:00
Zack BarettandGitHub 666ef7a978 Merge pull request #13199 from home-assistant/2022.7-hotfix 2022-07-14 17:26:04 -05:00
Zack 24a97347df Version Bump 2022-07-14 17:13:02 -05:00
Zack Barett 0825d5c64e Fix Suggested Value in HA-Form (#13173) 2022-07-14 17:11:53 -05:00
Erik MontnemeryandZack 535e752ec7 Correct display of barometric pressure and rain (#13183) 2022-07-14 17:11:39 -05:00
Zack BarettandGitHub b611a58fce Add support for Supported Brands (#13184) 2022-07-13 17:51:17 +02:00
Zack BarettandGitHub 24e54554ad Fix History Graph Name not being friendly (#13179) 2022-07-13 11:48:52 +02:00
Erik MontnemeryandGitHub d23fca4dd1 Correct display of barometric pressure and rain (#13183) 2022-07-12 16:13:29 -05:00
729e2f5248 Use entity name in device info page (#13165)
* Use entity name in device info page

* Adjust to new format

* Use latest API

* Fix types

* Fix CI?

Co-authored-by: Zack <[email protected]>
2022-07-11 19:07:07 -07:00
Zack BarettandGitHub 437723c6a6 Fix Number Selector Label (#13178) 2022-07-12 01:05:47 +00:00
Bram KragtenandGitHub a30c8205b1 Update dialog styles (#13132) 2022-07-11 15:07:10 -05:00
c50cf78bb4 Allow stale ZHA coordinator entries to be deleted (#13154)
Co-authored-by: Bram Kragten <[email protected]>
2022-07-11 13:14:37 -05:00
Zack BarettandGitHub be52ba0ea9 Fix Suggested Value in HA-Form (#13173) 2022-07-11 09:46:32 -05:00
Yosi LevyandGitHub 55e9ebc4d2 Energy panel/cards - RTL fixes (#13171) 2022-07-11 15:47:22 +02:00
Bram KragtenandGitHub 29c3fb0f92 Fix energy demo (#13172) 2022-07-11 08:44:34 -05:00
Erik MontnemeryandGitHub 7c3cd9d88d Merge pull request #13170 from home-assistant/number_customize_units
Allow customizing number unit of measurement
2022-07-11 14:49:47 +02:00
Erik 4881d699e3 Allow customizing number unit of measurement 2022-07-11 14:05:23 +02:00
Joakim SørensenandGitHub 414db83359 Hide homeassistant from partial restore if no version (#13168) 2022-07-11 12:55:43 +02:00
Joakim SørensenandGitHub cd4f6e19f4 Await backup restore (#13167) 2022-07-11 12:50:37 +02:00
Joakim SørensenandGitHub da709cbbd1 Add hacs_repository my redirect (#13153) 2022-07-11 09:06:45 +02:00
383 changed files with 11809 additions and 5565 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ on:
- master
env:
NODE_VERSION: 14
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
+1 -1
View File
@@ -6,7 +6,7 @@ on:
- dev
env:
NODE_VERSION: 14
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
+12 -2
View File
@@ -6,8 +6,8 @@ on:
- cron: "0 1 * * *"
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
permissions:
@@ -55,9 +55,19 @@ jobs:
rm -rf dist home_assistant_frontend.egg-info
python3 -m build
- name: Archive translations
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@v3
with:
name: translations
path: translations.tar.gz
if-no-files-found: error
+3 -3
View File
@@ -6,8 +6,8 @@ on:
- published
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
@@ -21,7 +21,7 @@ jobs:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v3
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@v3.0.13
uses: actions/stale@v5.1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90
+1 -1
View File
@@ -8,7 +8,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 14
NODE_VERSION: 16
jobs:
upload:
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn run lint-staged --relative --shell "/bin/bash"
+1 -1
View File
@@ -1 +1 @@
14
16
+30 -1
View File
@@ -1 +1,30 @@
[]
[
{
"path": "M20,20H7A2,2 0 0,1 5,18V8.94L2.23,5.64C2.09,5.47 2,5.24 2,5A1,1 0 0,1 3,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M8.5,7A0.5,0.5 0 0,0 8,7.5V8.5A0.5,0.5 0 0,0 8.5,9H18.5A0.5,0.5 0 0,0 19,8.5V7.5A0.5,0.5 0 0,0 18.5,7H8.5M8.5,11A0.5,0.5 0 0,0 8,11.5V12.5A0.5,0.5 0 0,0 8.5,13H18.5A0.5,0.5 0 0,0 19,12.5V11.5A0.5,0.5 0 0,0 18.5,11H8.5M8.5,15A0.5,0.5 0 0,0 8,15.5V16.5A0.5,0.5 0 0,0 8.5,17H13.5A0.5,0.5 0 0,0 14,16.5V15.5A0.5,0.5 0 0,0 13.5,15H8.5Z",
"name": "android-messages"
},
{
"path": "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z",
"name": "book-variant-multiple"
},
{
"path": "M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z",
"name": "desktop-mac"
},
{
"path": "M21,14V4H3V14H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3C1.89,18 1,17.1 1,16V4C1,2.89 1.89,2 3,2H21M4,5H15V10H4V5M16,5H20V7H16V5M20,8V13H16V8H20M4,11H9V13H4V11M10,11H15V13H10V11Z",
"name": "desktop-mac-dashboard"
},
{
"path": "M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z",
"name": "discord"
},
{
"path": "M8.06,7.78C7.5,7.78 7.17,7.73 7.08,7.64L6.66,13.73C7.19,14.05 7.88,14.3 8.72,14.5C9.56,14.71 10.78,14.77 12.38,14.67C13.97,14.58 15.63,14.23 17.34,13.64L16.55,4.22C15.67,5.09 14.38,5.91 12.66,6.66C11.13,7.31 9.81,7.69 8.72,7.78H8.06M7.97,5.34C7.28,5.94 7,6.34 7.13,6.56C7.22,6.78 7.7,6.84 8.58,6.75C9.67,6.66 10.91,6.31 12.28,5.72C13.22,5.31 14.03,4.88 14.72,4.41C15.41,3.94 15.88,3.55 16.13,3.23C16.38,2.92 16.47,2.7 16.41,2.58C16.34,2.42 16.03,2.34 15.47,2.34C14.34,2.34 12.94,2.7 11.25,3.42C9.81,4.05 8.72,4.69 7.97,5.34M17.34,2.2C17.41,2.33 17.44,2.47 17.44,2.63L18.61,17C18.61,18.73 18,20.09 16.83,21.07C15.64,22.05 14.03,22.55 12,22.55C10,22.55 8.4,22.04 7.2,21C6,20 5.39,18.64 5.39,16.92L6.09,6.47C6.09,6.22 6.2,5.94 6.42,5.63C6.64,5.31 6.84,5.06 7.03,4.88L7.36,4.59C8.33,3.78 9.5,3.08 10.88,2.5C11.81,2.08 12.73,1.77 13.62,1.57C14.5,1.37 15.3,1.3 16,1.38C16.71,1.46 17.16,1.73 17.34,2.2Z",
"name": "google-home"
},
{
"path": "M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z",
"name": "tablet-android"
}
]
+1 -1
View File
@@ -76,7 +76,7 @@ const createWebpackConfig = ({
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new WebpackBar({ fancy: !isProdBuild }),
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
+36 -5
View File
@@ -1,5 +1,4 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
import {
@@ -7,9 +6,14 @@ import {
provideHass,
} from "../../src/fake_data/provide_hass";
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
import "../../src/resources/compatibility";
import { HomeAssistant } from "../../src/types";
import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockEnergy } from "./stubs/energy";
import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend";
import { mockHistory } from "./stubs/history";
@@ -20,9 +24,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -51,8 +52,38 @@ class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
]);
hass.addEntities(energyEntities());
-41
View File
@@ -1,41 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry?domain=co2signal", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};
+20
View File
@@ -0,0 +1,20 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfigEntries = (hass: MockHomeAssistant) => {
hass.mockWS("config_entries/get", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_remove_device: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
};
+3 -1
View File
@@ -4,4 +4,6 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEntityRegistry = (
hass: MockHomeAssistant,
data: EntityRegistryEntry[] = []
) => hass.mockWS("config/entity_registry/list", () => data);
) => {
hass.mockWS("config/entity_registry/list", () => data);
};
+2 -2
View File
@@ -8,7 +8,7 @@ module.exports = [
{
category: "lovelace",
// Label for in the sidebar
header: "Lovelace",
header: "Dashboards",
// Specify order of pages. Any pages in the category folder but not listed here will
// automatically be added after the pages listed here.
pages: ["introduction"],
@@ -34,7 +34,7 @@ module.exports = [
},
{
category: "misc",
header: "Miscelaneous",
header: "Miscellaneous",
},
{
category: "brand",
+4 -3
View File
@@ -5,7 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../src/components/ha-icon-button";
import "../../src/managers/notification-manager";
import "../../src/components/ha-expansion-panel";
import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
import { haStyle } from "../../src/resources/styles";
import { PAGES, SIDEBAR } from "../build/import-pages";
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
@@ -174,9 +174,10 @@ class HaGallery extends LitElement {
const menuItem = this.shadowRoot!.querySelector(
`a[href="#${this._page}"]`
)!;
// Make sure section is expanded
if (menuItem.parentElement instanceof HTMLDetailsElement) {
menuItem.parentElement.open = true;
if (menuItem.parentElement instanceof HaExpansionPanel) {
menuItem.parentElement.expanded = true;
}
}
@@ -1,7 +1,9 @@
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import { Action } from "../../../../src/data/script";
import { describeAction } from "../../../../src/data/script_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
@@ -88,6 +90,15 @@ const ACTIONS = [
then: [{ delay: "00:00:01" }],
else: [{ delay: "00:00:05" }],
},
{
if: [{ condition: "state" }],
then: [{ delay: "00:00:01" }],
},
{
if: [{ condition: "state" }, { condition: "state" }],
then: [{ delay: "00:00:01" }],
else: [{ delay: "00:00:05" }],
},
{
choose: [
{
@@ -103,16 +114,38 @@ const ACTIONS = [
},
];
const initialAction: Action = {
service: "light.turn_on",
target: {
entity_id: "light.kitchen",
},
};
@customElement("demo-automation-describe-action")
export class DemoAutomationDescribeAction extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _action = initialAction;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Actions">
<div class="action">
<span>
${this._action
? describeAction(this.hass, this._action)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
label="Action Config"
.defaultValue=${initialAction}
@value-changed=${this._dataChanged}
></ha-yaml-editor>
</div>
${ACTIONS.map(
(conf) => html`
<div class="action">
@@ -132,6 +165,11 @@ export class DemoAutomationDescribeAction extends LitElement {
hass.addEntities(ENTITIES);
}
private _dataChanged(ev: CustomEvent): void {
ev.stopPropagation();
this._action = ev.detail.isValid ? ev.detail.value : undefined;
}
static get styles() {
return css`
ha-card {
@@ -147,6 +185,9 @@ export class DemoAutomationDescribeAction extends LitElement {
span {
margin-right: 16px;
}
ha-yaml-editor {
width: 50%;
}
`;
}
}
@@ -1,31 +1,81 @@
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import { Condition } from "../../../../src/data/automation";
import { describeCondition } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("device_tracker", "person", "home", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
];
const conditions = [
{ condition: "and" },
{ condition: "not" },
{ condition: "or" },
{ condition: "state" },
{ condition: "numeric_state" },
{ condition: "state", entity_id: "light.kitchen", state: "on" },
{
condition: "numeric_state",
entity_id: "light.kitchen",
attribute: "brightness",
below: 80,
above: 20,
},
{ condition: "sun", after: "sunset" },
{ condition: "sun", after: "sunrise" },
{ condition: "zone" },
{ condition: "sun", after: "sunrise", offset: "-01:00" },
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
{ condition: "time" },
{ condition: "template" },
];
const initialCondition: Condition = {
condition: "state",
entity_id: "light.kitchen",
state: "on",
};
@customElement("demo-automation-describe-condition")
export class DemoAutomationDescribeCondition extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _condition = initialCondition;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Conditions">
<div class="condition">
<span>
${this._condition
? describeCondition(this._condition, this.hass)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
label="Condition Config"
.defaultValue=${initialCondition}
@value-changed=${this._dataChanged}
></ha-yaml-editor>
</div>
${conditions.map(
(conf) => html`
<div class="condition">
<span>${describeCondition(conf as any)}</span>
<span>${describeCondition(conf as any, this.hass)}</span>
<pre>${dump(conf)}</pre>
</div>
`
@@ -34,6 +84,18 @@ export class DemoAutomationDescribeCondition extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
private _dataChanged(ev: CustomEvent): void {
ev.stopPropagation();
this._condition = ev.detail.isValid ? ev.detail.value : undefined;
}
static get styles() {
return css`
ha-card {
@@ -49,6 +111,9 @@ export class DemoAutomationDescribeCondition extends LitElement {
span {
margin-right: 16px;
}
ha-yaml-editor {
width: 50%;
}
`;
}
}
@@ -1,34 +1,92 @@
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import { Trigger } from "../../../../src/data/automation";
import { describeTrigger } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("person", "person", "", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
];
const triggers = [
{ platform: "state" },
{ platform: "state", entity_id: "light.kitchen", from: "off", to: "on" },
{ platform: "mqtt" },
{ platform: "geo_location" },
{ platform: "homeassistant" },
{ platform: "numeric_state" },
{ platform: "sun" },
{
platform: "geo_location",
source: "test_source",
zone: "zone.home",
event: "enter",
},
{ platform: "homeassistant", event: "start" },
{
platform: "numeric_state",
entity_id: "light.kitchen",
attribute: "brightness",
below: 80,
above: 20,
},
{ platform: "sun", event: "sunset" },
{ platform: "time_pattern" },
{ platform: "webhook" },
{ platform: "zone" },
{
platform: "zone",
entity_id: "person.person",
zone: "zone.home",
event: "enter",
},
{ platform: "tag" },
{ platform: "time" },
{ platform: "time", at: "15:32" },
{ platform: "template" },
{ platform: "event" },
{ platform: "event", event_type: "homeassistant_started" },
];
const initialTrigger: Trigger = {
platform: "state",
entity_id: "light.kitchen",
};
@customElement("demo-automation-describe-trigger")
export class DemoAutomationDescribeTrigger extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _trigger = initialTrigger;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Triggers">
<div class="trigger">
<span>
${this._trigger
? describeTrigger(this._trigger, this.hass)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
label="Trigger Config"
.defaultValue=${initialTrigger}
@value-changed=${this._dataChanged}
></ha-yaml-editor>
</div>
${triggers.map(
(conf) => html`
<div class="trigger">
<span>${describeTrigger(conf as any)}</span>
<span>${describeTrigger(conf as any, this.hass)}</span>
<pre>${dump(conf)}</pre>
</div>
`
@@ -37,6 +95,18 @@ export class DemoAutomationDescribeTrigger extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
private _dataChanged(ev: CustomEvent): void {
ev.stopPropagation();
this._trigger = ev.detail.isValid ? ev.detail.value : undefined;
}
static get styles() {
return css`
ha-card {
@@ -52,6 +122,9 @@ export class DemoAutomationDescribeTrigger extends LitElement {
span {
margin-right: 16px;
}
ha-yaml-editor {
width: 50%;
}
`;
}
}
@@ -0,0 +1,32 @@
---
title: Dialgos
subtitle: Dialogs provide important prompts in a user flow.
---
# Material Design 3
Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on it's [website](https://m3.material.io/components/dialogs/overview).
# Highlighted guidelines
## Content
* A best practice is to always use a title, even if it is optional by Material guidelines.
* People mainly read the title and a button. Put the most important information in those two.
* Try to avoid user generated content in the title, this could make the title unreadable long.
* If users become unsure, they read the description. Make sure this explains what will happen.
* Strive for minimalism.
## Buttons and X-icon
* Keep the labels short, for example `Save`, `Delete`, `Enable`.
* Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
* Destructive actions should be a red warning button.
* Alert or confirmation dialogs only have buttons and no X-icon.
* Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
## Example
### Confirmation dialog
> **Delete dashboard?**
>
> Dashboard [dashboard name] will be permanently deleted from Home Assistant.
>
> Cancel / Delete
@@ -3,6 +3,13 @@ title: Alerts
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
---
<style>
ha-alert {
display: block;
margin: 4px 0;
}
</style>
# Alert `<ha-alert>`
The alert offers four severity levels that set a distinctive icon and color.
@@ -0,0 +1,5 @@
---
title: Expansion Panel
---
Expansion panel following all the ARIA guidelines.
@@ -0,0 +1,157 @@
import { mdiPacMan } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-markdown";
import "../../components/demo-black-white-row";
import { LONG_TEXT } from "../../data/text";
const SHORT_TEXT = LONG_TEXT.substring(0, 113);
const SAMPLES: {
template: (slot: string, leftChevron: boolean) => TemplateResult;
}[] = [
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
header="Attr header"
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
header="Attr header"
secondary="Attr secondary"
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
.header=${"Prop header"}
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
.header=${"Prop header"}
.secondary=${"Prop secondary"}
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
.header=${"Prop header"}
>
<span slot="secondary">Slot Secondary</span>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel slot=${slot} .leftChevron=${leftChevron}>
<span slot="header">Slot header</span>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel slot=${slot} .leftChevron=${leftChevron}>
<span slot="header">Slot header with actions</span>
<ha-icon-button
slot="icons"
label="Some Action"
.path=${mdiPacMan}
></ha-icon-button>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
header="Attr Header with actions"
>
<ha-icon-button
slot="icons"
label="Some Action"
.path=${mdiPacMan}
></ha-icon-button>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
];
@customElement("demo-components-ha-expansion-panel")
export class DemoHaExpansionPanel extends LitElement {
protected render(): TemplateResult {
return html`
${SAMPLES.map(
(sample) => html`
<demo-black-white-row>
${["light", "dark"].map((slot) =>
sample.template(slot, slot === "dark")
)}
</demo-black-white-row>
`
)}
`;
}
static get styles() {
return css`
ha-expansion-panel {
margin: -16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-expansion-panel": DemoHaExpansionPanel;
}
}
+14
View File
@@ -3,6 +3,7 @@ import "@material/mwc-button";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
@@ -20,16 +21,22 @@ const ENTITIES = [
}),
getEntity("media_player", "livingroom", "playing", {
friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
}),
getEntity("media_player", "lounge", "idle", {
friendly_name: "Lounge",
supported_features: 444983,
device_class: "speaker",
}),
getEntity("light", "bedroom", "on", {
friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
}),
getEntity("switch", "coffee", "off", {
friendly_name: "Coffee",
device_class: "switch",
}),
];
@@ -141,7 +148,13 @@ const SCHEMAS: {
selector: { attribute: { entity_id: "" } },
context: { filter_entity: "entity" },
},
{
name: "State",
selector: { state: { entity_id: "" } },
context: { filter_entity: "entity", filter_attribute: "Attribute" },
},
{ name: "Device", selector: { device: {} } },
{ name: "Config entry", selector: { config_entry: {} } },
{ name: "Duration", selector: { duration: {} } },
{ name: "area", selector: { area: {} } },
{ name: "target", selector: { target: {} } },
@@ -423,6 +436,7 @@ class DemoHaForm extends LitElement {
hass.addEntities(ENTITIES);
mockEntityRegistry(hass);
mockDeviceRegistry(hass, DEVICES);
mockConfigEntries(hass);
mockAreaRegistry(hass, AREAS);
mockHassioSupervisor(hass);
}
@@ -3,6 +3,7 @@ import "@material/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
@@ -115,11 +116,19 @@ const SCHEMAS: {
name: "One of each",
input: {
entity: { name: "Entity", selector: { entity: {} } },
state: {
name: "State",
selector: { state: { entity_id: "alarm_control_panel.alarm" } },
},
attribute: {
name: "Attribute",
selector: { attribute: { entity_id: "" } },
},
device: { name: "Device", selector: { device: {} } },
config_entry: {
name: "Integration",
selector: { config_entry: {} },
},
duration: { name: "Duration", selector: { duration: {} } },
addon: { name: "Addon", selector: { addon: {} } },
area: { name: "Area", selector: { area: {} } },
@@ -276,6 +285,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
hass.addEntities(ENTITIES);
mockEntityRegistry(hass);
mockDeviceRegistry(hass, DEVICES);
mockConfigEntries(hass);
mockAreaRegistry(hass, AREAS);
mockHassioSupervisor(hass);
hass.mockWS("auth/sign_path", (params) => params);
+2 -2
View File
@@ -31,7 +31,7 @@ const ENTITIES = [
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Second Office Fan",
friendly_name: "Kitchen Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
@@ -102,7 +102,7 @@ class DemoArea extends LitElement {
picture: "/images/office.jpg",
},
{
name: "Second Office",
name: "Kitchen",
area_id: "kitchen",
picture: "/images/kitchen.png",
},
@@ -75,6 +75,10 @@ const ENTITIES = [
timestamp: 1641801600,
friendly_name: "Date and Time",
}),
getEntity("sensor", "humidity", "23.2", {
friendly_name: "Humidity",
unit_of_measurement: "%",
}),
getEntity("input_select", "dropdown", "Soda", {
friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"],
@@ -142,6 +146,7 @@ const CONFIGS = [
- light.non_existing
- climate.ecobee
- input_number.number
- sensor.humidity
`,
},
{
@@ -1,11 +1,11 @@
---
title: Introduction
---
Lovelace has many different cards. Each card allows the user to tell
Dashboards have many different cards. Each card allows the user to tell
a different story about what is going on in their house. These cards
are very customizable, as no household is the same.
This gallery helps our developers and designers to see all the
different states that each card can be in.
Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace.
Check [the Dashboards documentation](https://www.home-assistant.io/dashboards/) for instructions on how to get started with Dashboards.
@@ -191,9 +191,11 @@ const createEntityRegistryEntries = (
hidden_by: null,
entity_category: null,
entity_id: "binary_sensor.updater",
id: "binary_sensor.updater",
name: null,
icon: null,
platform: "updater",
has_entity_name: false,
},
];
+1 -1
View File
@@ -69,7 +69,7 @@ const ENTITIES = [
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effets Light",
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
@@ -81,10 +81,10 @@ class HassioAddonRepositoryEl extends LitElement {
? this.supervisor.localize(
"common.new_version_available"
)
: this.supervisor.localize("addon.installed")
: this.supervisor.localize("addon.state.installed")
: addon.available
? this.supervisor.localize("addon.not_installed")
: this.supervisor.localize("addon.not_available")}
? this.supervisor.localize("addon.state.not_installed")
: this.supervisor.localize("addon.state.not_available")}
.iconClass=${addon.installed
? addon.update_available
? "update"
+11 -2
View File
@@ -22,8 +22,10 @@ import {
HassioAddonRepository,
reloadHassioAddons,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types";
@@ -59,8 +61,15 @@ class HassioAddonStore extends LitElement {
@state() private _filter?: string;
public async refreshData() {
await reloadHassioAddons(this.hass);
await this._loadData();
try {
await reloadHassioAddons(this.hass);
} catch (err) {
showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
} finally {
await this._loadData();
}
}
protected render(): TemplateResult {
@@ -336,7 +336,7 @@ class HassioAddonConfig extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.common.update_available",
"addon.failed_to_reset",
"error",
extractApiErrorMessage(err)
);
@@ -81,7 +81,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.documentation.get_logs",
"addon.documentation.get_documentation",
"error",
extractApiErrorMessage(err)
);
@@ -75,7 +75,7 @@ class HassioAddonDashboard extends LitElement {
></hass-error-screen>`;
}
if (!this.addon) {
if (!this.addon || !this.supervisor?.addon) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
@@ -209,8 +209,8 @@ class HassioAddonDashboard extends LitElement {
}
if (requestedAddon) {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
const validAddon = addonsInfo.addons.some(
const store = await fetchSupervisorStore(this.hass);
const validAddon = store.addons.some(
(addon) => addon.slug === requestedAddon
);
if (!validAddon) {
@@ -238,7 +238,7 @@ class HassioAddonDashboard extends LitElement {
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor",
collection: "addon",
});
}
@@ -263,6 +263,10 @@ class HassioAddonDashboard extends LitElement {
return;
}
try {
if (!this.supervisor.addon) {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
fireEvent(this, "supervisor-update", { addon: addonsInfo });
}
this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
} catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
@@ -40,6 +40,7 @@ import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
AddonCapability,
fetchHassioAddonChangelog,
fetchHassioAddonInfo,
HassioAddonDetails,
@@ -701,7 +702,7 @@ class HassioAddonInfo extends LitElement {
}
private _showMoreInfo(ev): void {
const id = ev.currentTarget.id;
const id = ev.currentTarget.id as AddonCapability;
showHassioMarkdownDialog(this, {
title: this.supervisor.localize(`addon.dashboard.capability.${id}.title`),
content:
+2 -2
View File
@@ -176,7 +176,7 @@ export class HassioBackups extends LitElement {
: supervisorTabs(this.hass)}
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.searchLabel=${this.supervisor.localize("backup.search")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow}
.route=${this.route}
@@ -240,7 +240,7 @@ export class HassioBackups extends LitElement {
: html`
<ha-icon-button
.label=${this.supervisor.localize(
"snapshot.delete_selected"
"backup.delete_selected"
)}
.path=${mdiDelete}
id="delete-btn"
@@ -17,9 +17,12 @@ import {
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types";
import { HomeAssistant, TranslationDict } from "../../../src/types";
import "./supervisor-formfield-label";
type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] &
keyof TranslationDict["ui"]["panel"]["page-onboarding"]["restore"];
interface CheckboxItem {
slug: string;
checked: boolean;
@@ -108,9 +111,9 @@ export class SupervisorBackupContent extends LitElement {
this._focusTarget?.focus();
}
private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
private _localize = (key: BackupOrRestoreKey) =>
this.supervisor?.localize(`backup.${key}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${key}`);
protected render(): TemplateResult {
if (!this.onboarding && !this.supervisor) {
@@ -168,23 +171,24 @@ export class SupervisorBackupContent extends LitElement {
: ""}
${this.backupType === "partial"
? html`<div class="partial-picker">
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>
${!this.backup || this.backup.homeassistant
? html`<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${foldersSection?.templates.length
? html`
<ha-formfield
@@ -201,26 +201,24 @@ class HassioBackupDialog
}
if (!this._dialogParams?.onboarding) {
this.hass!.callApi(
"POST",
try {
await 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
);
this.closeDialog();
} catch (error: any) {
this._error = error.body.message;
}
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
@@ -4,7 +4,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-form/ha-form";
import { HaFormSchema } from "../../../../src/components/ha-form/types";
import type { SchemaUnion } 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";
@@ -19,7 +19,7 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { RegistriesDialogParams } from "./show-dialog-registries";
const SCHEMA: HaFormSchema[] = [
const SCHEMA = [
{
name: "registry",
required: true,
@@ -35,7 +35,7 @@ const SCHEMA: HaFormSchema[] = [
required: true,
selector: { text: { type: "password" } },
},
];
] as const;
@customElement("dialog-hassio-registries")
class HassioRegistriesDialog extends LitElement {
@@ -135,8 +135,8 @@ class HassioRegistriesDialog extends LitElement {
`;
}
private _computeLabel = (schema: HaFormSchema) =>
this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name;
private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) =>
this.supervisor.localize(`dialog.registries.${schema.name}`);
private _valueChanged(ev: CustomEvent) {
this._input = ev.detail.value;
+8 -3
View File
@@ -22,6 +22,7 @@ import {
Supervisor,
SupervisorObject,
supervisorCollection,
SupervisorKeys,
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
@@ -124,9 +125,13 @@ export class SupervisorBaseElement extends urlSyncMixin(
this.supervisor = {
...this.supervisor,
localize: await computeLocalize(this.constructor.prototype, language, {
[language]: data,
}),
localize: await computeLocalize<SupervisorKeys>(
this.constructor.prototype,
language,
{
[language]: data,
}
),
};
}
+1 -1
View File
@@ -26,7 +26,7 @@ import {
import {
UNHEALTHY_REASON_URL,
UNSUPPORTED_REASON_URL,
} from "../../../src/panels/config/system-health/ha-config-system-health";
} from "../../../src/panels/config/repairs/dialog-system-information";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
+9 -2
View File
@@ -1,4 +1,11 @@
module.exports = {
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
"*.{js,ts}": [
"prettier --write",
'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
],
"!(/translations)*.{json,css,md,html}": "prettier --write",
"translations/*/*.json": (files) =>
'printf "%s\n" "These files should not be modified. Instead, make the necessary modifications in src/translations/en.json. Please see translations/README.md for details." ' +
files.join(" ") +
" >&2 && exit 1",
};
+13 -14
View File
@@ -16,6 +16,9 @@
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier",
"postinstall": "husky install",
"prepack": "pinst --disable",
"postpack": "pinst --enable",
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
},
"author": "Paulus Schoutsen <[email protected]> (http://paulusschoutsen.nl)",
@@ -46,6 +49,7 @@
"@fullcalendar/daygrid": "5.9.0",
"@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0",
"@fullcalendar/timegrid": "5.9.0",
"@lit-labs/motion": "^1.0.2",
"@lit-labs/virtualizer": "patch:@lit-labs/[email protected]#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
"@material/chips": "14.0.0-canary.261f2db59.0",
@@ -72,8 +76,8 @@
"@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.9.96",
"@mdi/svg": "6.9.96",
"@mdi/js": "7.0.96",
"@mdi/svg": "7.0.96",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
@@ -89,8 +93,8 @@
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.4",
"@vaadin/combo-box": "^23.0.10",
"@vaadin/vaadin-themable-mixin": "^23.0.10",
"@vaadin/combo-box": "^23.1.5",
"@vaadin/vaadin-themable-mixin": "^23.1.5",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
@@ -107,15 +111,14 @@
"deep-freeze": "^0.0.1",
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.1.5",
"home-assistant-js-websocket": "^7.1.0",
"hls.js": "^1.2.1",
"home-assistant-js-websocket": "^8.0.0",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.1.2",
"lit-vaadin-helpers": "^0.3.0",
"marked": "^4.0.12",
"memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1",
@@ -202,9 +205,9 @@
"gulp-rename": "^2.0.0",
"gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0",
"husky": "^1.3.1",
"husky": "^8.0.1",
"instant-mocha": "^1.3.1",
"lint-staged": "^11.1.2",
"lint-staged": "^13.0.3",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
@@ -213,6 +216,7 @@
"mocha": "^8.4.0",
"object-hash": "^2.0.3",
"open": "^7.0.4",
"pinst": "^3.0.0",
"prettier": "^2.4.1",
"require-dir": "^1.2.0",
"rollup": "^2.8.2",
@@ -245,11 +249,6 @@
"@lit/reactive-element": "1.2.1"
},
"main": "src/home-assistant.js",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"prettier": {
"trailingComma": "es5",
"arrowParens": "always"
+1 -6
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20220707.0"
version = "20220902.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
@@ -23,8 +23,3 @@ include-package-data = true
[tool.setuptools.packages.find]
include = ["hass_frontend*"]
[tool.mypy]
python_version = 3.4
show_error_codes = true
strict = true
+2 -1
View File
@@ -314,7 +314,8 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
}
private _computeStepDescription(step: DataEntryFlowStepForm) {
const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`;
const resourceKey =
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description` as const;
const args: string[] = [];
const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => {
+5 -45
View File
@@ -47,7 +47,7 @@ import {
mdiRobotVacuum,
mdiScriptText,
mdiSineWave,
mdiTextToSpeech,
mdiMicrophoneMessage,
mdiThermometer,
mdiThermostat,
mdiTimerOutline,
@@ -74,8 +74,9 @@ export const FIXED_DOMAIN_ICONS = {
camera: mdiVideo,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiTextToSpeech,
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
demo: mdiHomeAssistant,
fan: mdiFan,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
@@ -97,6 +98,7 @@ export const FIXED_DOMAIN_ICONS = {
proximity: mdiAppleSafari,
remote: mdiRemote,
scene: mdiPalette,
schedule: mdiCalendarClock,
script: mdiScriptText,
select: mdiFormatListBulleted,
sensor: mdiEye,
@@ -124,6 +126,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
gas: mdiGasCylinder,
humidity: mdiWaterPercent,
illuminance: mdiBrightness5,
moisture: mdiWaterPercent,
monetary: mdiCash,
nitrogen_dioxide: mdiMolecule,
nitrogen_monoxide: mdiMolecule,
@@ -165,46 +168,6 @@ export const DOMAINS_WITH_CARD = [
"water_heater",
];
/** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [
"alarm_control_panel",
"automation",
"camera",
"climate",
"configurator",
"counter",
"cover",
"fan",
"group",
"humidifier",
"input_datetime",
"light",
"lock",
"media_player",
"person",
"remote",
"script",
"scene",
"sun",
"timer",
"update",
"vacuum",
"water_heater",
"weather",
];
/** Domains that do not show the default more info dialog content (e.g. the attribute section)
* and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */
export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_number",
"input_select",
"input_text",
"number",
"scene",
"update",
"select",
];
/** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
@@ -236,9 +199,6 @@ export const DOMAINS_INPUT_ROW = [
"vacuum",
];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"];
/** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"];
+23 -1
View File
@@ -1,7 +1,7 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
import { useAmPm } from "./use_am_pm";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
@@ -28,6 +28,28 @@ const formatDateTimeMem = memoizeOne(
)
);
// Aug 9, 8:23 AM
export const formatShortDateTime = (
dateObj: Date,
locale: FrontendLocaleData
) => formatShortDateTimeMem(locale).format(dateObj);
const formatShortDateTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
month: "short",
day: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
}
)
);
// August 9, 2021, 8:23:15 AM
export const formatDateTimeWithSeconds = (
dateObj: Date,
+28
View File
@@ -0,0 +1,28 @@
import { HaDurationData } from "../../components/ha-duration-input";
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
export const formatDuration = (duration: HaDurationData) => {
const d = duration.days || 0;
const h = duration.hours || 0;
const m = duration.minutes || 0;
const s = duration.seconds || 0;
const ms = duration.milliseconds || 0;
if (d > 0) {
return `${d} days ${h}:${leftPad(m)}:${leftPad(s)}`;
}
if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
}
if (m > 0) {
return `${m}:${leftPad(s)}`;
}
if (s > 0) {
return `${s} seconds`;
}
if (ms > 0) {
return `${ms} milliseconds`;
}
return null;
};
+15 -1
View File
@@ -1,7 +1,7 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
import { useAmPm } from "./use_am_pm";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
@@ -64,3 +64,17 @@ const formatTimeWeekdayMem = memoizeOne(
}
)
);
// 21:15
export const formatTime24h = (dateObj: Date) =>
formatTime24hMem().format(dateObj);
const formatTime24hMem = memoizeOne(
() =>
// en-GB to fix Chrome 24:59 to 0:59 https://stackoverflow.com/a/60898146
new Intl.DateTimeFormat("en-GB", {
hour: "numeric",
minute: "2-digit",
hour12: false,
})
);
+8 -13
View File
@@ -5,8 +5,7 @@ export type LeafletModuleType = typeof import("leaflet");
export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async (
mapElement: HTMLElement,
darkMode?: boolean
mapElement: HTMLElement
): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element");
@@ -23,7 +22,7 @@ export const setupLeafletMap = async (
mapElement.parentNode.appendChild(style);
map.setView([52.3731339, 4.8903147], 13);
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
const tileLayer = createTileLayer(Leaflet).addTo(map);
return [map, Leaflet, tileLayer];
};
@@ -31,23 +30,19 @@ export const setupLeafletMap = async (
export const replaceTileLayer = (
leaflet: LeafletModuleType,
map: Map,
tileLayer: TileLayer,
darkMode: boolean
tileLayer: TileLayer
): TileLayer => {
map.removeLayer(tileLayer);
tileLayer = createTileLayer(leaflet, darkMode);
tileLayer = createTileLayer(leaflet);
tileLayer.addTo(map);
return tileLayer;
};
const createTileLayer = (
leaflet: LeafletModuleType,
darkMode: boolean
): TileLayer =>
const createTileLayer = (leaflet: LeafletModuleType): TileLayer =>
leaflet.tileLayer(
`https://{s}.basemaps.cartocdn.com/${
darkMode ? "dark_all" : "light_all"
}/{z}/{x}/{y}${leaflet.Browser.retina ? "@2x.png" : ".png"}`,
`https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}${
leaflet.Browser.retina ? "@2x.png" : ".png"
}`,
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>',
+6 -3
View File
@@ -64,9 +64,12 @@ export const computeStateDisplayFromEntityAttributes = (
// fallback to default
}
}
return `${formatNumber(state, locale)}${
attributes.unit_of_measurement ? " " + attributes.unit_of_measurement : ""
}`;
const unit = !attributes.unit_of_measurement
? ""
: attributes.unit_of_measurement === "%"
? "%"
: ` ${attributes.unit_of_measurement}`;
return `${formatNumber(state, locale)}${unit}`;
}
const domain = computeDomain(entityId);
+43 -1
View File
@@ -8,6 +8,7 @@ import {
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
@@ -25,7 +26,15 @@ import {
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiWeatherNight,
@@ -127,7 +136,40 @@ export const domainIconWithoutDefault = (
}
case "media_player":
return compareState === "playing" ? mdiCastConnected : mdiCast;
switch (stateObj?.attributes.device_class) {
case "speaker":
switch (compareState) {
case "playing":
return mdiSpeakerPlay;
case "paused":
return mdiSpeakerPause;
case "off":
return mdiSpeakerOff;
default:
return mdiSpeaker;
}
case "tv":
switch (compareState) {
case "playing":
return mdiTelevisionPlay;
case "paused":
return mdiTelevisionPause;
case "off":
return mdiTelevisionOff;
default:
return mdiTelevision;
}
default:
switch (compareState) {
case "playing":
case "paused":
return mdiCastConnected;
case "off":
return mdiCastOff;
default:
return mdiCast;
}
}
case "switch":
switch (stateObj?.attributes.device_class) {
+277
View File
@@ -0,0 +1,277 @@
import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE_STATES } from "../../data/entity";
const FIXED_DOMAIN_STATES = {
alarm_control_panel: [
"armed_away",
"armed_custom_bypass",
"armed_home",
"armed_night",
"armed_vacation",
"arming",
"disarmed",
"disarming",
"pending",
"triggered",
],
automation: ["on", "off"],
binary_sensor: ["on", "off"],
button: [],
calendar: ["on", "off"],
camera: ["idle", "recording", "streaming"],
cover: ["closed", "closing", "open", "opening"],
device_tracker: ["home", "not_home"],
fan: ["on", "off"],
humidifier: ["on", "off"],
input_boolean: ["on", "off"],
input_button: [],
light: ["on", "off"],
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
media_player: ["idle", "off", "paused", "playing", "standby"],
person: ["home", "not_home"],
remote: ["on", "off"],
scene: [],
schedule: ["on", "off"],
script: ["on", "off"],
siren: ["on", "off"],
sun: ["above_horizon", "below_horizon"],
switch: ["on", "off"],
update: ["on", "off"],
vacuum: ["cleaning", "docked", "error", "idle", "paused", "returning"],
weather: [
"clear-night",
"cloudy",
"exceptional",
"fog",
"hail",
"lightning-rainy",
"lightning",
"partlycloudy",
"pouring",
"rainy",
"snowy-rainy",
"snowy",
"sunny",
"windy-variant",
"windy",
],
};
const FIXED_DOMAIN_ATTRIBUTE_STATES = {
alarm_control_panel: {
code_format: ["number", "text"],
},
binary_sensor: {
device_class: [
"battery",
"battery_charging",
"co",
"cold",
"connectivity",
"door",
"garage_door",
"gas",
"heat",
"light",
"lock",
"moisture",
"motion",
"moving",
"occupancy",
"opening",
"plug",
"power",
"presence",
"problem",
"running",
"safety",
"smoke",
"sound",
"tamper",
"update",
"vibration",
"window",
],
},
button: {
device_class: ["restart", "update"],
},
camera: {
frontend_stream_type: ["hls", "web_rtc"],
},
climate: {
hvac_action: ["off", "idle", "heating", "cooling", "drying", "fan"],
},
cover: {
device_class: [
"awning",
"blind",
"curtain",
"damper",
"door",
"garage",
"gate",
"shade",
"shutter",
"window",
],
},
humidifier: {
device_class: ["humidifier", "dehumidifier"],
},
media_player: {
device_class: ["tv", "speaker", "receiver"],
media_content_type: [
"app",
"channel",
"episode",
"game",
"image",
"movie",
"music",
"playlist",
"tvshow",
"url",
"video",
],
},
number: {
device_class: ["temperature"],
},
sensor: {
device_class: [
"apparent_power",
"aqi",
"battery",
"carbon_dioxide",
"carbon_monoxide",
"current",
"date",
"duration",
"energy",
"frequency",
"gas",
"humidity",
"illuminance",
"monetary",
"nitrogen_dioxide",
"nitrogen_monoxide",
"nitrous_oxide",
"ozone",
"pm1",
"pm10",
"pm25",
"power_factor",
"power",
"pressure",
"reactive_power",
"signal_strength",
"sulphur_dioxide",
"temperature",
"timestamp",
"volatile_organic_compounds",
"voltage",
],
state_class: ["measurement", "total", "total_increasing"],
},
switch: {
device_class: ["outlet", "switch"],
},
update: {
device_class: ["firmware"],
},
water_heater: {
away_mode: ["on", "off"],
},
};
export const getStates = (
state: HassEntity,
attribute: string | undefined = undefined
): string[] => {
const domain = computeStateDomain(state);
const result: string[] = [];
if (!attribute && domain in FIXED_DOMAIN_STATES) {
result.push(...FIXED_DOMAIN_STATES[domain]);
} else if (
attribute &&
domain in FIXED_DOMAIN_ATTRIBUTE_STATES &&
attribute in FIXED_DOMAIN_ATTRIBUTE_STATES[domain]
) {
result.push(...FIXED_DOMAIN_ATTRIBUTE_STATES[domain][attribute]);
}
// Dynamic values based on the entities
switch (domain) {
case "climate":
if (!attribute) {
result.push(...state.attributes.hvac_modes);
} else if (attribute === "fan_mode") {
result.push(...state.attributes.fan_modes);
} else if (attribute === "preset_mode") {
result.push(...state.attributes.preset_modes);
} else if (attribute === "swing_mode") {
result.push(...state.attributes.swing_modes);
}
break;
case "device_tracker":
case "person":
if (!attribute) {
result.push("home", "not_home");
}
break;
case "fan":
if (attribute === "preset_mode") {
result.push(...state.attributes.preset_modes);
}
break;
case "humidifier":
if (attribute === "mode") {
result.push(...state.attributes.available_modes);
}
break;
case "input_select":
case "select":
if (!attribute) {
result.push(...state.attributes.options);
}
break;
case "light":
if (attribute === "effect") {
result.push(...state.attributes.effect_list);
} else if (attribute === "color_mode") {
result.push(...state.attributes.color_modes);
}
break;
case "media_player":
if (attribute === "sound_mode") {
result.push(...state.attributes.sound_mode_list);
} else if (attribute === "source") {
result.push(...state.attributes.source_list);
}
break;
case "remote":
if (attribute === "current_activity") {
result.push(...state.attributes.activity_list);
}
break;
case "vacuum":
if (attribute === "fan_speed") {
result.push(...state.attributes.fan_speed_list);
}
break;
case "water_heater":
if (!attribute || attribute === "operation_mode") {
result.push(...state.attributes.operation_list);
}
break;
}
if (!attribute) {
// All entities can have unavailable states
result.push(...UNAVAILABLE_STATES);
}
return [...new Set(result)];
};
@@ -0,0 +1,88 @@
import { html } from "lit";
import { getConfigEntries } from "../../data/config_entries";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { isComponentLoaded } from "../config/is_component_loaded";
import { fireEvent } from "../dom/fire_event";
import { navigate } from "../navigate";
export const protocolIntegrationPicked = async (
element: HTMLElement,
hass: HomeAssistant,
slug: string
) => {
if (slug === "zwave_js") {
const entries = await getConfigEntries(hass, {
domain: "zwave_js",
});
if (!entries.length) {
// If the component isn't loaded, ask them to load the integration first
showConfirmationDialog(element, {
text: hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Z-Wave",
supported_hardware_link: html`<a
href=${documentationUrl(hass, "/docs/z-wave/controllers")}
target="_blank"
rel="noreferrer"
>${hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(element, "handler-picked", {
handler: "zwave_js",
});
},
});
return;
}
showZWaveJSAddNodeDialog(element, {
entry_id: entries[0].entry_id,
});
} else if (slug === "zha") {
// If the component isn't loaded, ask them to load the integration first
if (!isComponentLoaded(hass, "zha")) {
showConfirmationDialog(element, {
text: hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Zigbee",
supported_hardware_link: html`<a
href=${documentationUrl(
hass,
"/integrations/zha/#known-working-zigbee-radio-modules"
)}
target="_blank"
rel="noreferrer"
>${hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(element, "handler-picked", {
handler: "zha",
});
},
});
return;
}
navigate("/config/zha/add");
}
};
+60 -4
View File
@@ -3,10 +3,66 @@ import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-plur
import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill";
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
import IntlMessageFormat from "intl-messageformat";
import { Resources } from "../../types";
import { Resources, TranslationDict } from "../../types";
import { getLocalLanguage } from "../../util/common-translation";
export type LocalizeFunc = (key: string, ...args: any[]) => string;
// Exclude some patterns from key type checking for now
// These are intended to be removed as errors are fixed
// Fixing component category will require tighter definition of types from backend and/or web socket
export type LocalizeKeys =
| FlattenObjectKeys<Omit<TranslationDict, "supervisor">>
| `panel.${string}`
| `state.${string}`
| `state_attributes.${string}`
| `state_badge.${string}`
| `ui.card.alarm_control_panel.${string}`
| `ui.card.weather.attributes.${string}`
| `ui.card.weather.cardinal_direction.${string}`
| `ui.components.logbook.${string}`
| `ui.components.selectors.file.${string}`
| `ui.dialogs.entity_registry.editor.${string}`
| `ui.dialogs.more_info_control.vacuum.${string}`
| `ui.dialogs.options_flow.loading.${string}`
| `ui.dialogs.quick-bar.commands.${string}`
| `ui.dialogs.repair_flow.loading.${string}`
| `ui.dialogs.unhealthy.reason.${string}`
| `ui.dialogs.unsupported.reason.${string}`
| `ui.panel.config.${string}.${"caption" | "description"}`
| `ui.panel.config.automation.${string}`
| `ui.panel.config.dashboard.${string}`
| `ui.panel.config.devices.${string}`
| `ui.panel.config.energy.${string}`
| `ui.panel.config.helpers.${string}`
| `ui.panel.config.info.${string}`
| `ui.panel.config.integrations.${string}`
| `ui.panel.config.logs.${string}`
| `ui.panel.config.lovelace.${string}`
| `ui.panel.config.network.${string}`
| `ui.panel.config.scene.${string}`
| `ui.panel.config.url.${string}`
| `ui.panel.config.zha.${string}`
| `ui.panel.config.zwave_js.${string}`
| `ui.panel.developer-tools.tabs.${string}`
| `ui.panel.lovelace.card.${string}`
| `ui.panel.lovelace.editor.${string}`
| `ui.panel.page-authorize.form.${string}`
| `component.${string}`;
// Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types
export type FlattenObjectKeys<
T extends Record<string, any>,
Key extends keyof T = keyof T
> = Key extends string
? T[Key] extends Record<string, unknown>
? `${Key}.${FlattenObjectKeys<T[Key]>}`
: `${Key}`
: never;
export type LocalizeFunc<Keys extends string = LocalizeKeys> = (
key: Keys,
...args: any[]
) => string;
interface FormatType {
[format: string]: any;
}
@@ -65,12 +121,12 @@ export const polyfillsLoaded =
* }
*/
export const computeLocalize = async (
export const computeLocalize = async <Keys extends string = LocalizeKeys>(
cache: any,
language: string,
resources: Resources,
formats?: FormatsType
): Promise<LocalizeFunc> => {
): Promise<LocalizeFunc<Keys>> => {
if (polyfillsLoaded) {
await polyfillsLoaded;
}
+5
View File
@@ -188,6 +188,10 @@ export default class HaChartBase extends LitElement {
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";
this.chart = new ChartConstructor(ctx, {
type: this.chartType,
@@ -376,6 +380,7 @@ export default class HaChartBase extends LitElement {
.chartTooltip .title {
text-align: center;
font-weight: 500;
direction: ltr;
}
.chartTooltip .footer {
font-weight: 500;
+7 -13
View File
@@ -15,13 +15,13 @@ import {
import { customElement, property, state } from "lit/decorators";
import { getGraphColorByIndex } from "../../common/color/colors";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { computeStateName } from "../../common/entity/compute_state_name";
import {
formatNumber,
numberFormatToLocale,
} from "../../common/number/format_number";
import {
getStatisticIds,
getStatisticLabel,
Statistics,
statisticsHaveType,
StatisticsMetaData,
@@ -233,24 +233,18 @@ class StatisticsChart extends LitElement {
const names = this.names || {};
statisticsData.forEach((stats) => {
const firstStat = stats[0];
let name = names[firstStat.statistic_id];
if (!name) {
const entityState = this.hass.states[firstStat.statistic_id];
if (entityState) {
name = computeStateName(entityState);
} else {
name = firstStat.statistic_id;
}
}
const meta = this.statisticIds!.find(
(stat) => stat.statistic_id === firstStat.statistic_id
);
let name = names[firstStat.statistic_id];
if (!name) {
name = getStatisticLabel(this.hass, firstStat.statistic_id, meta);
}
if (!this.unit) {
if (unit === undefined) {
unit = meta?.unit_of_measurement;
} else if (unit !== meta?.unit_of_measurement) {
unit = meta?.display_unit_of_measurement;
} else if (unit !== meta?.display_unit_of_measurement) {
unit = null;
}
}
+4
View File
@@ -221,6 +221,10 @@ class DateRangePickerElement extends WrappedElement {
.calendar-table {
padding: 0 !important;
}
.daterangepicker.ltr {
direction: ltr;
text-align: left;
}
`;
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
@@ -1,7 +1,7 @@
import "@material/mwc-button/mwc-button";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
@@ -172,8 +172,7 @@ export abstract class HaDeviceAutomationPicker<
static get styles(): CSSResultGroup {
return css`
ha-select {
width: 100%;
margin-top: 4px;
display: block;
}
`;
}
+1 -1
View File
@@ -1,7 +1,7 @@
import "@material/mwc-list/mwc-list-item";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
@@ -15,6 +15,14 @@ class HaEntityAttributePicker extends LitElement {
@property() public entityId?: string;
/**
* List of attributes to be hidden.
* @type {Array}
* @attr hide-attributes
*/
@property({ type: Array, attribute: "hide-attributes" })
public hideAttributes?: string[];
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@@ -42,10 +50,12 @@ class HaEntityAttributePicker extends LitElement {
if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items = state
? Object.keys(state.attributes).map((key) => ({
value: key,
label: formatAttributeName(key),
}))
? Object.keys(state.attributes)
.filter((key) => !this.hideAttributes?.includes(key))
.map((key) => ({
value: key,
label: formatAttributeName(key),
}))
: [];
}
}
@@ -58,7 +68,7 @@ class HaEntityAttributePicker extends LitElement {
return html`
<ha-combo-box
.hass=${this.hass}
.value=${this.value || ""}
.value=${this.value ? formatAttributeName(this.value) : ""}
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize(
+1 -1
View File
@@ -1,7 +1,7 @@
import "@material/mwc-list/mwc-list-item";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
@@ -0,0 +1,111 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { computeStateDisplay } from "../../common/entity/compute_state_display";
import { PolymerChangedEvent } from "../../polymer-types";
import { getStates } from "../../common/entity/get_states";
import { HomeAssistant } from "../../types";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@customElement("ha-entity-state-picker")
class HaEntityStatePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId?: string;
@property() public attribute?: string;
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean, attribute: "allow-custom-value" })
public allowCustomValue;
@property() public label?: string;
@property() public value?: string;
@property() public helper?: string;
@property({ type: Boolean }) private _opened = false;
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
protected shouldUpdate(changedProps: PropertyValues) {
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items =
this.entityId && state
? getStates(state, this.attribute).map((key) => ({
value: key,
label: !this.attribute
? computeStateDisplay(
this.hass.localize,
state,
this.hass.locale,
key
)
: key,
}))
: [];
}
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-combo-box
.hass=${this.hass}
.value=${this.value
? this.entityId && this.hass.states[this.entityId]
? computeStateDisplay(
this.hass.localize,
this.hass.states[this.entityId],
this.hass.locale,
this.value
)
: this.value
: ""}
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize("ui.components.entity.entity-state-picker.state")}
.disabled=${this.disabled || !this.entityId}
.required=${this.required}
.helper=${this.helper}
.allowCustomValue=${this.allowCustomValue}
item-value-path="value"
item-label-path="label"
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
</ha-combo-box>
`;
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
this.value = ev.detail.value;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-entity-state-picker": HaEntityStatePicker;
}
}
+33 -10
View File
@@ -1,6 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
@@ -31,12 +31,23 @@ export class HaStatisticPicker extends LitElement {
@property({ type: Boolean }) public disabled?: boolean;
/**
* Show only statistics with these unit of measuments.
* Show only statistics natively stored with these units of measurements.
* @type {Array}
* @attr include-unit-of-measurement
* @attr include-statistics-unit-of-measurement
*/
@property({ type: Array, attribute: "include-unit-of-measurement" })
public includeUnitOfMeasurement?: string[];
@property({
type: Array,
attribute: "include-statistics-unit-of-measurement",
})
public includeStatisticsUnitOfMeasurement?: string[];
/**
* Show only statistics displayed with these units of measurements.
* @type {Array}
* @attr include-display-unit-of-measurement
*/
@property({ type: Array, attribute: "include-display-unit-of-measurement" })
public includeDisplayUnitOfMeasurement?: string[];
/**
* Show only statistics with these device classes.
@@ -86,7 +97,8 @@ export class HaStatisticPicker extends LitElement {
private _getStatistics = memoizeOne(
(
statisticIds: StatisticsMetaData[],
includeUnitOfMeasurement?: string[],
includeStatisticsUnitOfMeasurement?: string[],
includeDisplayUnitOfMeasurement?: string[],
includeDeviceClasses?: string[],
entitiesOnly?: boolean
): Array<{ id: string; name: string; state?: HassEntity }> => {
@@ -101,9 +113,18 @@ export class HaStatisticPicker extends LitElement {
];
}
if (includeUnitOfMeasurement) {
if (includeStatisticsUnitOfMeasurement) {
statisticIds = statisticIds.filter((meta) =>
includeUnitOfMeasurement.includes(meta.unit_of_measurement)
includeStatisticsUnitOfMeasurement.includes(
meta.statistics_unit_of_measurement
)
);
}
if (includeDisplayUnitOfMeasurement) {
statisticIds = statisticIds.filter((meta) =>
includeDisplayUnitOfMeasurement.includes(
meta.display_unit_of_measurement
)
);
}
@@ -184,7 +205,8 @@ export class HaStatisticPicker extends LitElement {
if (this.hasUpdated) {
(this.comboBox as any).items = this._getStatistics(
this.statisticIds!,
this.includeUnitOfMeasurement,
this.includeStatisticsUnitOfMeasurement,
this.includeDisplayUnitOfMeasurement,
this.includeDeviceClasses,
this.entitiesOnly
);
@@ -192,7 +214,8 @@ export class HaStatisticPicker extends LitElement {
this.updateComplete.then(() => {
(this.comboBox as any).items = this._getStatistics(
this.statisticIds!,
this.includeUnitOfMeasurement,
this.includeStatisticsUnitOfMeasurement,
this.includeDisplayUnitOfMeasurement,
this.includeDeviceClasses,
this.entitiesOnly
);
+5 -5
View File
@@ -1,5 +1,5 @@
import { html, LitElement, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event";
@@ -84,20 +84,20 @@ class HaAddonPicker extends LitElement {
} else {
showAlertDialog(this, {
title: this.hass.localize(
"ui.componencts.addon-picker.error.no_supervisor.title"
"ui.components.addon-picker.error.no_supervisor.title"
),
text: this.hass.localize(
"ui.componencts.addon-picker.error.no_supervisor.description"
"ui.components.addon-picker.error.no_supervisor.description"
),
});
}
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.componencts.addon-picker.error.fetch_addons.title"
"ui.components.addon-picker.error.fetch_addons.title"
),
text: this.hass.localize(
"ui.componencts.addon-picker.error.fetch_addons.description"
"ui.components.addon-picker.error.fetch_addons.description"
),
});
}
-1
View File
@@ -83,7 +83,6 @@ class HaAlert extends LitElement {
position: relative;
padding: 8px;
display: flex;
margin: 4px 0;
}
.issue-type.rtl {
flex-direction: row-reverse;
+1 -1
View File
@@ -1,6 +1,6 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
+1
View File
@@ -76,6 +76,7 @@ class HaAttributes extends LitElement {
css`
.attribute-container {
margin-bottom: 8px;
direction: ltr;
}
.data-entry {
display: flex;
+18 -21
View File
@@ -1,10 +1,11 @@
import "@material/mwc-list/mwc-list-item";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import "./ha-select";
import "./ha-textfield";
import { HaTextField } from "./ha-textfield";
import "./ha-input-helper-text";
export interface TimeChangedEvent {
@@ -36,7 +37,7 @@ export class HaBaseTimeInput extends LitElement {
/**
* determines if inputs are required
*/
@property({ type: Boolean }) public required?: boolean;
@property({ type: Boolean }) public required = false;
/**
* 12 or 24 hr format
@@ -123,11 +124,6 @@ export class HaBaseTimeInput extends LitElement {
*/
@property() amPm: "AM" | "PM" = "AM";
/**
* Formatted time string
*/
@property() value?: string;
protected render(): TemplateResult {
return html`
${this.label
@@ -140,11 +136,11 @@ export class HaBaseTimeInput extends LitElement {
id="day"
type="number"
inputmode="numeric"
.value=${this.days}
.value=${this.days.toFixed()}
.label=${this.dayLabel}
name="days"
@input=${this._valueChanged}
@focus=${this._onFocus}
@focusin=${this._onFocus}
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
@@ -161,16 +157,16 @@ export class HaBaseTimeInput extends LitElement {
id="hour"
type="number"
inputmode="numeric"
.value=${this.hours}
.value=${this.hours.toFixed()}
.label=${this.hourLabel}
name="hours"
@input=${this._valueChanged}
@focus=${this._onFocus}
@focusin=${this._onFocus}
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
.max=${this._hourMax}
max=${ifDefined(this._hourMax)}
min="0"
.disabled=${this.disabled}
suffix=":"
@@ -184,7 +180,7 @@ export class HaBaseTimeInput extends LitElement {
.value=${this._formatValue(this.minutes)}
.label=${this.minLabel}
@input=${this._valueChanged}
@focus=${this._onFocus}
@focusin=${this._onFocus}
name="minutes"
no-spinner
.required=${this.required}
@@ -205,7 +201,7 @@ export class HaBaseTimeInput extends LitElement {
.value=${this._formatValue(this.seconds)}
.label=${this.secLabel}
@input=${this._valueChanged}
@focus=${this._onFocus}
@focusin=${this._onFocus}
name="seconds"
no-spinner
.required=${this.required}
@@ -226,7 +222,7 @@ export class HaBaseTimeInput extends LitElement {
.value=${this._formatValue(this.milliseconds, 3)}
.label=${this.millisecLabel}
@input=${this._valueChanged}
@focus=${this._onFocus}
@focusin=${this._onFocus}
name="milliseconds"
no-spinner
.required=${this.required}
@@ -260,9 +256,10 @@ export class HaBaseTimeInput extends LitElement {
`;
}
private _valueChanged(ev) {
this[ev.target.name] =
ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value);
private _valueChanged(ev: InputEvent) {
const textField = ev.currentTarget as HaTextField;
this[textField.name] =
textField.name === "amPm" ? textField.value : Number(textField.value);
const value: TimeChangedEvent = {
hours: this.hours,
minutes: this.minutes,
@@ -277,8 +274,8 @@ export class HaBaseTimeInput extends LitElement {
});
}
private _onFocus(ev) {
ev.target.select();
private _onFocus(ev: FocusEvent) {
(ev.currentTarget as HaTextField).select();
}
/**
@@ -293,7 +290,7 @@ export class HaBaseTimeInput extends LitElement {
*/
private get _hourMax() {
if (this.noHoursLimit) {
return null;
return undefined;
}
if (this.format === 12) {
return 12;
+9 -9
View File
@@ -1,13 +1,18 @@
import "@material/mwc-list/mwc-list-item";
import "./ha-select";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { stringCompare } from "../common/string/compare";
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
import {
Blueprint,
BlueprintDomain,
Blueprints,
fetchBlueprints,
} from "../data/blueprint";
import { HomeAssistant } from "../types";
import "./ha-select";
@customElement("ha-blueprint-picker")
class HaBluePrintPicker extends LitElement {
@@ -17,7 +22,7 @@ class HaBluePrintPicker extends LitElement {
@property() public value = "";
@property() public domain = "automation";
@property() public domain: BlueprintDomain = "automation";
@property() public blueprints?: Blueprints;
@@ -51,7 +56,7 @@ class HaBluePrintPicker extends LitElement {
return html`
<ha-select
.label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.label")}
this.hass.localize("ui.components.blueprint-picker.select_blueprint")}
fixedMenuPosition
naturalMenuWidth
.value=${this.value}
@@ -59,11 +64,6 @@ class HaBluePrintPicker extends LitElement {
@selected=${this._blueprintChanged}
@closed=${stopPropagation}
>
<mwc-list-item value="">
${this.hass.localize(
"ui.components.blueprint-picker.select_blueprint"
)}
</mwc-list-item>
${this._processedBlueprints(this.blueprints).map(
(blueprint) => html`
<mwc-list-item .value=${blueprint.path}>
+32 -23
View File
@@ -9,8 +9,9 @@ import type {
} from "@vaadin/combo-box/vaadin-combo-box-light";
import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer, comboBoxRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { HomeAssistant } from "../types";
import "./ha-icon-button";
@@ -72,31 +73,31 @@ export class HaComboBox extends LitElement {
@property({ attribute: "error-message" }) public errorMessage?: string;
@property({ type: Boolean }) public invalid?: boolean;
@property({ type: Boolean }) public invalid = false;
@property({ type: Boolean }) public icon?: boolean;
@property({ type: Boolean }) public icon = false;
@property() public items?: any[];
@property({ attribute: false }) public items?: any[];
@property() public filteredItems?: any[];
@property({ attribute: false }) public filteredItems?: any[];
@property({ attribute: "allow-custom-value", type: Boolean })
public allowCustomValue?: boolean;
public allowCustomValue = false;
@property({ attribute: "item-value-path" }) public itemValuePath?: string;
@property({ attribute: "item-value-path" }) public itemValuePath = "value";
@property({ attribute: "item-label-path" }) public itemLabelPath?: string;
@property({ attribute: "item-label-path" }) public itemLabelPath = "label";
@property({ attribute: "item-id-path" }) public itemIdPath?: string;
@property() public renderer?: ComboBoxLitRenderer<any>;
@property({ type: Boolean }) public disabled?: boolean;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required?: boolean;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean, reflect: true, attribute: "opened" })
private _opened?: boolean;
public opened?: boolean;
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
@@ -149,37 +150,45 @@ export class HaComboBox extends LitElement {
attr-for-value="value"
>
<ha-textfield
.label=${this.label}
.placeholder=${this.placeholder}
.disabled=${this.disabled}
.required=${this.required}
.validationMessage=${this.validationMessage}
label=${ifDefined(this.label)}
placeholder=${ifDefined(this.placeholder)}
?disabled=${this.disabled}
?required=${this.required}
validationMessage=${ifDefined(this.validationMessage)}
.errorMessage=${this.errorMessage}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
.suffix=${html`<div style="width: 28px;"></div>`}
.suffix=${html`<div
style="width: 28px;"
role="none presentation"
></div>`}
.icon=${this.icon}
.invalid=${this.invalid}
.helper=${this.helper}
helper=${ifDefined(this.helper)}
helperPersistent
>
<slot name="icon" slot="leadingIcon"></slot>
</ha-textfield>
${this.value
? html`<ha-svg-icon
aria-label=${this.hass?.localize("ui.components.combo-box.clear")}
role="button"
tabindex="-1"
aria-label=${ifDefined(this.hass?.localize("ui.common.clear"))}
class="clear-button"
.path=${mdiClose}
@click=${this._clearValue}
></ha-svg-icon>`
: ""}
<ha-svg-icon
aria-label=${this.hass?.localize("ui.components.combo-box.show")}
role="button"
tabindex="-1"
aria-label=${ifDefined(this.label)}
aria-expanded=${this.opened ? "true" : "false"}
class="toggle-button"
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
.path=${this.opened ? mdiMenuUp : mdiMenuDown}
@click=${this._toggleOpen}
></ha-svg-icon>
</vaadin-combo-box-light>
@@ -199,7 +208,7 @@ export class HaComboBox extends LitElement {
}
private _toggleOpen(ev: Event) {
if (this._opened) {
if (this.opened) {
this._comboBox?.close();
ev.stopPropagation();
} else {
@@ -211,7 +220,7 @@ export class HaComboBox extends LitElement {
const opened = ev.detail.value;
// delay this so we can handle click event before setting _opened
setTimeout(() => {
this._opened = opened;
this.opened = opened;
}, 0);
// @ts-ignore
fireEvent(this, ev.type, ev.detail);
+156
View File
@@ -0,0 +1,156 @@
import "@material/mwc-list/mwc-list-item";
import { html, LitElement, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types";
import type { HaComboBox } from "./ha-combo-box";
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
import { domainToName } from "../data/integration";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { brandsUrl } from "../util/brands-url";
import "./ha-combo-box";
export interface ConfigEntryExtended extends ConfigEntry {
localized_domain_name?: string;
}
@customElement("ha-config-entry-picker")
class HaConfigEntryPicker extends LitElement {
public hass!: HomeAssistant;
@property() public integration?: string;
@property() public label?: string;
@property() public value = "";
@property() public helper?: string;
@state() private _configEntries?: ConfigEntryExtended[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = false;
@query("ha-combo-box") private _comboBox!: HaComboBox;
public open() {
this._comboBox?.open();
}
public focus() {
this._comboBox?.focus();
}
protected firstUpdated() {
this._getConfigEntries();
}
private _rowRenderer: ComboBoxLitRenderer<ConfigEntryExtended> = (
item
) => html`<mwc-list-item twoline graphic="icon">
<span
>${item.title ||
this.hass.localize(
"ui.panel.config.integrations.config_entry.unnamed_entry"
)}</span
>
<span slot="secondary">${item.localized_domain_name}</span>
<img
slot="graphic"
src=${brandsUrl({
domain: item.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
</mwc-list-item>`;
protected render(): TemplateResult {
if (!this._configEntries) {
return html``;
}
return html`
<ha-combo-box
.hass=${this.hass}
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.config-entry-picker.config_entry")
: this.label}
.value=${this._value}
.required=${this.required}
.disabled=${this.disabled}
.helper=${this.helper}
.renderer=${this._rowRenderer}
.items=${this._configEntries}
item-value-path="entry_id"
item-id-path="entry_id"
item-label-path="title"
@value-changed=${this._valueChanged}
></ha-combo-box>
`;
}
private _onImageLoad(ev) {
ev.target.style.visibility = "initial";
}
private _onImageError(ev) {
ev.target.style.visibility = "hidden";
}
private async _getConfigEntries() {
getConfigEntries(this.hass, {
type: "integration",
domain: this.integration,
}).then((configEntries) => {
this._configEntries = configEntries
.map(
(entry: ConfigEntry): ConfigEntryExtended => ({
...entry,
localized_domain_name: domainToName(
this.hass.localize,
entry.domain
),
})
)
.sort((conf1, conf2) =>
caseInsensitiveStringCompare(
conf1.localized_domain_name + conf1.title,
conf2.localized_domain_name + conf2.title
)
);
});
}
private get _value() {
return this.value || "";
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
const newValue = ev.detail.value;
if (newValue !== this._value) {
this._setValue(newValue);
}
}
private _setValue(value: string) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });
fireEvent(this, "change");
}, 0);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-entry-picker": HaConfigEntryPicker;
}
}
+18 -11
View File
@@ -11,7 +11,7 @@ export const createCloseHeading = (
hass: HomeAssistant,
title: string | TemplateResult
) => html`
<span class="header_title">${title}</span>
<div class="header_title">${title}</div>
<ha-icon-button
.label=${hass.localize("ui.dialogs.generic.close")}
.path=${mdiClose}
@@ -40,10 +40,13 @@ export class HaDialog extends DialogBase {
z-index: var(--dialog-z-index, 7);
-webkit-backdrop-filter: var(--dialog-backdrop-filter, none);
backdrop-filter: var(--dialog-backdrop-filter, none);
--mdc-dialog-box-shadow: var(--dialog-box-shadow, none);
--mdc-typography-headline6-font-weight: 400;
--mdc-typography-headline6-font-size: 1.574rem;
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
padding-bottom: max(env(safe-area-inset-bottom), 8px);
padding-bottom: max(env(safe-area-inset-bottom), 24px);
}
.mdc-dialog__actions span:nth-child(1) {
flex: var(--secondary-action-button-flex, unset);
@@ -54,28 +57,32 @@ export class HaDialog extends DialogBase {
.mdc-dialog__container {
align-items: var(--vertial-align-dialog, center);
}
.mdc-dialog__title {
padding: 24px 24px 0 24px;
}
.mdc-dialog__actions {
padding: 0 24px 24px 24px;
}
.mdc-dialog__title::before {
display: block;
height: 20px;
height: 0px;
}
.mdc-dialog .mdc-dialog__content {
position: var(--dialog-content-position, relative);
padding: var(--dialog-content-padding, 20px 24px);
padding: var(--dialog-content-padding, 24px);
}
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
padding-bottom: max(
var(--dialog-content-padding, 20px),
var(--dialog-content-padding, 24px),
env(safe-area-inset-bottom)
);
}
.mdc-dialog .mdc-dialog__surface {
position: var(--dialog-surface-position, relative);
top: var(--dialog-surface-top);
margin-top: var(--dialog-surface-margin-top);
min-height: var(--mdc-dialog-min-height, auto);
border-radius: var(
--ha-dialog-border-radius,
var(--ha-card-border-radius, 4px)
);
border-radius: var(--ha-dialog-border-radius, 28px);
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;
@@ -89,8 +96,8 @@ export class HaDialog extends DialogBase {
color: inherit;
}
.header_title {
margin-right: 40px;
margin-inline-end: 40px;
margin-right: 32px;
margin-inline-end: 32px;
margin-inline-start: initial;
direction: var(--direction);
}
+4 -4
View File
@@ -14,17 +14,17 @@ export interface HaDurationData {
@customElement("ha-duration-input")
class HaDurationInput extends LitElement {
@property({ attribute: false }) public data!: HaDurationData;
@property({ attribute: false }) public data?: HaDurationData;
@property() public label?: string;
@property() public helper?: string;
@property({ type: Boolean }) public required?: boolean;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean }) public enableMillisecond?: boolean;
@property({ type: Boolean }) public enableMillisecond = false;
@property({ type: Boolean }) public enableDay?: boolean;
@property({ type: Boolean }) public enableDay = false;
@property({ type: Boolean }) public disabled = false;
+82 -29
View File
@@ -14,11 +14,13 @@ import { nextRender } from "../common/util/render-status";
import "./ha-svg-icon";
@customElement("ha-expansion-panel")
class HaExpansionPanel extends LitElement {
export class HaExpansionPanel extends LitElement {
@property({ type: Boolean, reflect: true }) expanded = false;
@property({ type: Boolean, reflect: true }) outlined = false;
@property({ type: Boolean, reflect: true }) leftChevron = false;
@property() header?: string;
@property() secondary?: string;
@@ -29,23 +31,42 @@ class HaExpansionPanel extends LitElement {
protected render(): TemplateResult {
return html`
<div
id="summary"
@click=${this._toggleContainer}
@keydown=${this._toggleContainer}
role="button"
tabindex="0"
aria-expanded=${this.expanded}
aria-controls="sect1"
>
<slot class="header" name="header">
${this.header}
<slot class="secondary" name="secondary">${this.secondary}</slot>
</slot>
<ha-svg-icon
.path=${mdiChevronDown}
class="summary-icon ${classMap({ expanded: this.expanded })}"
></ha-svg-icon>
<div class="top">
<div
id="summary"
@click=${this._toggleContainer}
@keydown=${this._toggleContainer}
@focus=${this._focusChanged}
@blur=${this._focusChanged}
role="button"
tabindex="0"
aria-expanded=${this.expanded}
aria-controls="sect1"
>
${this.leftChevron
? html`
<ha-svg-icon
.path=${mdiChevronDown}
class="summary-icon ${classMap({ expanded: this.expanded })}"
></ha-svg-icon>
`
: ""}
<slot name="header">
<div class="header">
${this.header}
<slot class="secondary" name="secondary">${this.secondary}</slot>
</div>
</slot>
${!this.leftChevron
? html`
<ha-svg-icon
.path=${mdiChevronDown}
class="summary-icon ${classMap({ expanded: this.expanded })}"
></ha-svg-icon>
`
: ""}
</div>
<slot name="icons"></slot>
</div>
<div
class="container ${classMap({ expanded: this.expanded })}"
@@ -61,23 +82,35 @@ class HaExpansionPanel extends LitElement {
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (changedProps.has("expanded") && this.expanded) {
this._showContent = this.expanded;
setTimeout(() => {
// Verify we're still expanded
if (this.expanded) {
this._container.style.overflow = "initial";
}
}, 300);
}
}
private _handleTransitionEnd() {
this._container.style.removeProperty("height");
this._container.style.overflow = this.expanded ? "initial" : "hidden";
this._showContent = this.expanded;
}
private async _toggleContainer(ev): Promise<void> {
if (ev.defaultPrevented) {
return;
}
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
return;
}
ev.preventDefault();
const newExpanded = !this.expanded;
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
this._container.style.overflow = "hidden";
if (newExpanded) {
this._showContent = true;
@@ -98,12 +131,28 @@ class HaExpansionPanel extends LitElement {
fireEvent(this, "expanded-changed", { expanded: this.expanded });
}
private _focusChanged(ev) {
this.shadowRoot!.querySelector(".top")!.classList.toggle(
"focused",
ev.type === "focus"
);
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
}
.top {
display: flex;
align-items: center;
}
.top.focused {
background: var(--input-fill-color);
}
:host([outlined]) {
box-shadow: none;
border-width: 1px;
@@ -115,7 +164,17 @@ class HaExpansionPanel extends LitElement {
border-radius: var(--ha-card-border-radius, 4px);
}
.summary-icon {
margin-left: 8px;
}
:host([leftchevron]) .summary-icon {
margin-left: 0;
margin-right: 8px;
}
#summary {
flex: 1;
display: flex;
padding: var(--expansion-panel-summary-padding, 0 8px);
min-height: 48px;
@@ -126,15 +185,8 @@ class HaExpansionPanel extends LitElement {
outline: none;
}
#summary:focus {
background: var(--input-fill-color);
}
.summary-icon {
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
margin-left: auto;
margin-inline-start: auto;
margin-inline-end: initial;
direction: var(--direction);
}
@@ -142,6 +194,11 @@ class HaExpansionPanel extends LitElement {
transform: rotate(180deg);
}
.header,
::slotted([slot="header"]) {
flex: 1;
}
.container {
padding: var(--expansion-panel-content-padding, 0 8px);
overflow: hidden;
@@ -153,10 +210,6 @@ class HaExpansionPanel extends LitElement {
height: auto;
}
.header {
display: block;
}
.secondary {
display: block;
color: var(--secondary-text-color);
@@ -6,7 +6,10 @@ export const computeInitialHaFormData = (
): Record<string, any> => {
const data = {};
schema.forEach((field) => {
if (field.description?.suggested_value) {
if (
field.description?.suggested_value !== undefined &&
field.description?.suggested_value !== null
) {
data[field.name] = field.description.suggested_value;
} else if ("default" in field) {
data[field.name] = field.default;
@@ -47,6 +50,7 @@ export const computeInitialHaFormData = (
"text" in selector ||
"addon" in selector ||
"attribute" in selector ||
"file" in selector ||
"icon" in selector ||
"theme" in selector
) {
+4 -3
View File
@@ -3,15 +3,15 @@ import {
CSSResultGroup,
html,
LitElement,
TemplateResult,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { HaCheckbox } from "../ha-checkbox";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
import "../ha-slider";
import { HaTextField } from "../ha-textfield";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
@customElement("ha-form-integer")
export class HaFormInteger extends LitElement implements HaFormElement {
@@ -105,7 +105,8 @@ export class HaFormInteger extends LitElement implements HaFormElement {
}
return (
this.schema.description?.suggested_value ||
(this.schema.description?.suggested_value !== undefined &&
this.schema.description?.suggested_value !== null) ||
this.schema.default ||
this.schema.valueMin ||
0
@@ -5,9 +5,9 @@ import { HaFormElement, HaFormTimeData, HaFormTimeSchema } from "./types";
@customElement("ha-form-positive_time_period_dict")
export class HaFormTimePeriod extends LitElement implements HaFormElement {
@property() public schema!: HaFormTimeSchema;
@property({ attribute: false }) public schema!: HaFormTimeSchema;
@property() public data!: HaFormTimeData;
@property({ attribute: false }) public data!: HaFormTimeData;
@property() public label!: string;
@@ -25,7 +25,7 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement {
return html`
<ha-duration-input
.label=${this.label}
.required=${this.schema.required}
?required=${this.schema.required}
.data=${this.data}
.disabled=${this.disabled}
></ha-duration-input>
+6 -6
View File
@@ -35,20 +35,20 @@ export class HaForm extends LitElement implements HaFormElement {
@property({ attribute: false }) public data!: HaFormDataContainer;
@property({ attribute: false }) public schema!: HaFormSchema[];
@property({ attribute: false }) public schema!: readonly HaFormSchema[];
@property() public error?: Record<string, string>;
@property({ type: Boolean }) public disabled = false;
@property() public computeError?: (schema: HaFormSchema, error) => string;
@property() public computeError?: (schema: any, error) => string;
@property() public computeLabel?: (
schema: HaFormSchema,
data?: HaFormDataContainer
schema: any,
data: HaFormDataContainer
) => string;
@property() public computeHelper?: (schema: HaFormSchema) => string;
@property() public computeHelper?: (schema: any) => string | undefined;
public focus() {
const root = this.shadowRoot?.querySelector(".root");
@@ -168,7 +168,7 @@ export class HaForm extends LitElement implements HaFormElement {
return this.computeHelper ? this.computeHelper(schema) : "";
}
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
private _computeError(error, schema: HaFormSchema | readonly HaFormSchema[]) {
return this.computeError ? this.computeError(error, schema) : error;
}
+13 -4
View File
@@ -31,7 +31,7 @@ export interface HaFormGridSchema extends HaFormBaseSchema {
type: "grid";
name: "";
column_min_width?: string;
schema: HaFormSchema[];
schema: readonly HaFormSchema[];
}
export interface HaFormSelector extends HaFormBaseSchema {
@@ -53,12 +53,15 @@ export interface HaFormIntegerSchema extends HaFormBaseSchema {
export interface HaFormSelectSchema extends HaFormBaseSchema {
type: "select";
options: Array<[string, string]>;
options: ReadonlyArray<readonly [string, string]>;
}
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
type: "multi_select";
options: Record<string, string> | string[] | Array<[string, string]>;
options:
| Record<string, string>
| readonly string[]
| ReadonlyArray<readonly [string, string]>;
}
export interface HaFormFloatSchema extends HaFormBaseSchema {
@@ -78,6 +81,12 @@ export interface HaFormTimeSchema extends HaFormBaseSchema {
type: "positive_time_period_dict";
}
// Type utility to unionize a schema array by flattening any grid schemas
export type SchemaUnion<
SchemaArray extends readonly HaFormSchema[],
Schema = SchemaArray[number]
> = Schema extends HaFormGridSchema ? SchemaUnion<Schema["schema"]> : Schema;
export interface HaFormDataContainer {
[key: string]: HaFormData;
}
@@ -100,7 +109,7 @@ export type HaFormMultiSelectData = string[];
export type HaFormTimeData = HaDurationData;
export interface HaFormElement extends LitElement {
schema: HaFormSchema | HaFormSchema[];
schema: HaFormSchema | readonly HaFormSchema[];
data?: HaFormDataContainer | HaFormData;
label?: string;
}
+3 -1
View File
@@ -132,7 +132,9 @@ export class Gauge extends LitElement {
this._segment_label
? this._segment_label
: this.valueText || formatNumber(this.value, this.locale)
} ${this._segment_label ? "" : this.label}
}${
this._segment_label ? "" : this.label === "%" ? "%" : ` ${this.label}`
}
</text>
</svg>`;
}
+19 -8
View File
@@ -3,6 +3,8 @@ import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
import "./ha-button-menu";
import "./ha-icon-button";
@@ -16,6 +18,7 @@ export interface IconOverflowMenuItem {
disabled?: boolean;
tooltip?: string;
onClick: CallableFunction;
warning?: boolean;
}
@customElement("ha-icon-overflow-menu")
@@ -49,9 +52,13 @@ export class HaIconOverflowMenu extends LitElement {
graphic="icon"
.disabled=${item.disabled}
@click=${item.action}
class=${classMap({ warning: Boolean(item.warning) })}
>
<div slot="graphic">
<ha-svg-icon .path=${item.path}></ha-svg-icon>
<ha-svg-icon
class=${classMap({ warning: Boolean(item.warning) })}
.path=${item.path}
></ha-svg-icon>
</div>
${item.label}
</mwc-list-item>
@@ -81,7 +88,8 @@ export class HaIconOverflowMenu extends LitElement {
`;
}
protected _handleIconOverflowMenuOpened() {
protected _handleIconOverflowMenuOpened(e) {
e.stopPropagation();
// If this component is used inside a data table, the z-index of the row
// needs to be increased. Otherwise the ha-button-menu would be displayed
// underneath the next row in the table.
@@ -99,12 +107,15 @@ export class HaIconOverflowMenu extends LitElement {
}
static get styles() {
return css`
:host {
display: flex;
justify-content: flex-end;
}
`;
return [
haStyle,
css`
:host {
display: flex;
justify-content: flex-end;
}
`,
];
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { customIcons } from "../data/custom_icons";
+96 -1
View File
@@ -29,7 +29,102 @@ interface DeprecatedIcon {
};
}
const mdiDeprecatedIcons: DeprecatedIcon = {};
const mdiDeprecatedIcons: DeprecatedIcon = {
"android-messages": {
newName: "message-text",
removeIn: "2022.10",
},
"book-variant-multiple": {
newName: "bookmark-box-multiple",
removeIn: "2022.10",
},
"desktop-mac": {
newName: "monitor",
removeIn: "2022.10",
},
"desktop-mac-dashboard": {
newName: "monitor-dashboard",
removeIn: "2022.10",
},
discord: {
removeIn: "2022.10",
},
"diving-scuba": {
newName: "diving-scuba-mask",
removeIn: "2022.10",
},
"email-send": {
newName: "email-arrow-right",
removeIn: "2022.10",
},
"email-send-outline": {
newName: "email-arrow-right-outline",
removeIn: "2022.10",
},
"email-receive": {
newName: "email-arrow-left",
removeIn: "2022.10",
},
"email-receive-outline": {
newName: "email-arrow-left-outline",
removeIn: "2022.10",
},
"format-textdirection-r-to-l": {
newName: "format-pilcrow-arrow-left",
removeIn: "2022.10",
},
"format-textdirection-l-to-r": {
newName: "format-pilcrow-arrow-right",
removeIn: "2022.10",
},
"google-controller": {
newName: "controller",
removeIn: "2022.10",
},
"google-controller-off": {
newName: "controller-off",
removeIn: "2022.10",
},
"google-home": {
removeIn: "2022.10",
},
lecturn: {
newName: "lectern",
removeIn: "2022.10",
},
receipt: {
newName: "receipt-text",
removeIn: "2022.10",
},
"receipt-outline": {
newName: "receipt-text-outline",
removeIn: "2022.10",
},
"tablet-android": {
newName: "tablet",
removeIn: "2022.10",
},
"text-to-speech": {
newName: "microphone-message",
removeIn: "2022.10",
},
"text-to-speech-off": {
newName: "microphone-message-off",
removeIn: "2022.10",
},
"timeline-help": {
newName: "timeline-question",
removeIn: "2022.10",
},
"timeline-help-outline": {
newName: "timeline-question-outline",
removeIn: "2022.10",
},
"vector-point": {
newName: "vector-point-select",
removeIn: "2022.10",
},
};
const chunks: Chunks = {};
+22 -12
View File
@@ -1,11 +1,12 @@
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { ActionDetail } from "@material/mwc-list/mwc-list";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { navigate } from "../common/navigate";
import type { PageNavigation } from "../layouts/hass-tabs-subpage";
import type { HomeAssistant } from "../types";
import "./ha-clickable-list-item";
import "./ha-icon-next";
import "./ha-list-item";
import "./ha-svg-icon";
@customElement("ha-navigation-list")
@@ -18,17 +19,22 @@ class HaNavigationList extends LitElement {
@property({ type: Boolean }) public hasSecondary = false;
@property() public label?: string;
public render(): TemplateResult {
return html`
<mwc-list>
<mwc-list
innerRole="menu"
itemRoles="menuitem"
innerAriaLabel=${ifDefined(this.label)}
@action=${this._handleListAction}
>
${this.pages.map(
(page) => html`
<ha-clickable-list-item
<ha-list-item
graphic="avatar"
.twoline=${this.hasSecondary}
.hasMeta=${!this.narrow}
@click=${this._entryClicked}
href=${page.path}
>
<div
slot="graphic"
@@ -44,15 +50,20 @@ class HaNavigationList extends LitElement {
${!this.narrow
? html`<ha-icon-next slot="meta"></ha-icon-next>`
: ""}
</ha-clickable-list-item>
</ha-list-item>
`
)}
</mwc-list>
`;
}
private _entryClicked(ev) {
ev.currentTarget.blur();
private _handleListAction(ev: CustomEvent<ActionDetail>) {
const path = this.pages[ev.detail.index].path;
if (path.endsWith("#external-app-configuration")) {
this.hass.auth.external!.fireMessage({ type: "config_screen/show" });
} else {
navigate(path);
}
}
static styles: CSSResultGroup = css`
@@ -75,10 +86,9 @@ class HaNavigationList extends LitElement {
.icon-background ha-svg-icon {
color: #fff;
}
ha-clickable-list-item {
ha-list-item {
cursor: pointer;
font-size: var(--navigation-list-item-title-font-size);
padding: var(--navigation-list-item-padding) 0;
}
`;
}
+3
View File
@@ -326,6 +326,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
line-height: var(--paper-font-title_-_line-height);
opacity: var(--dark-primary-opacity);
}
h3:first-child {
margin-top: 0;
}
`;
}
}
+5
View File
@@ -52,6 +52,11 @@ export class HaSelect extends SelectBase {
inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label {
inset-inline-start: 48px;
inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select .mdc-select__anchor {
padding-inline-start: 12px;
padding-inline-end: 0px;
+34 -68
View File
@@ -1,8 +1,9 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { DeviceRegistryEntry } from "../../data/device_registry";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@@ -11,7 +12,11 @@ import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { AreaSelector } from "../../data/selector";
import type { AreaSelector } from "../../data/selector";
import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../ha-area-picker";
@@ -29,13 +34,15 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
@@ -45,7 +52,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
];
}
protected updated(changedProperties) {
protected updated(changedProperties: PropertyValues): void {
if (
changedProperties.has("selector") &&
(this.selector.area.device?.integration ||
@@ -58,7 +65,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
}
}
protected render() {
protected render(): TemplateResult {
if (
(this.selector.area.device?.integration ||
this.selector.area.entity?.integration) &&
@@ -77,12 +84,6 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
no-add
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
.disabled=${this.disabled}
.required=${this.required}
></ha-area-picker>
@@ -98,27 +99,22 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
no-add
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
.disabled=${this.disabled}
.required=${this.required}
></ha-areas-picker>
`;
}
private _filterEntities = (entity: EntityRegistryEntry): boolean => {
const filterIntegration = this.selector.area.entity?.integration;
if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
private _filterEntities = (entity: HassEntity): boolean => {
if (!this.selector.area.entity) {
return true;
}
return true;
return filterSelectorEntities(
this.selector.area.entity,
entity,
this._entitySources
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
@@ -126,47 +122,17 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true;
}
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.area.device;
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
return filterSelectorDevices(
this.selector.area.device,
device,
deviceIntegrations
);
};
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
}
declare global {
@@ -8,9 +8,9 @@ import "../entity/ha-entity-attribute-picker";
@customElement("ha-selector-attribute")
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public selector!: AttributeSelector;
@property({ attribute: false }) public selector!: AttributeSelector;
@property() public value?: any;
@@ -22,7 +22,7 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
@property({ type: Boolean }) public required = true;
@property() public context?: {
@property({ attribute: false }) public context?: {
filter_entity?: string;
};
@@ -32,6 +32,7 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
.hass=${this.hass}
.entityId=${this.selector.attribute.entity_id ||
this.context?.filter_entity}
.hideAttributes=${this.selector.attribute.hide_attributes}
.value=${this.value}
.label=${this.label}
.helper=${this.helper}
@@ -47,7 +47,7 @@ export class HaColorTempSelector extends LitElement {
static styles = css`
ha-labeled-slider {
--ha-slider-background: -webkit-linear-gradient(
right,
var(--float-end),
rgb(255, 160, 0) 0%,
white 50%,
rgb(166, 209, 255) 100%
@@ -0,0 +1,47 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { ConfigEntrySelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../ha-config-entry-picker";
@customElement("ha-selector-config_entry")
export class HaConfigEntrySelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: ConfigEntrySelector;
@property() public value?: any;
@property() public label?: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
protected render() {
return html`<ha-config-entry-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.helper=${this.helper}
.disabled=${this.disabled}
.required=${this.required}
.integration=${this.selector.config_entry.integration}
allow-custom-entity
></ha-config-entry-picker>`;
}
static styles = css`
ha-config-entry-picker {
width: 100%;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-config_entry": HaConfigEntrySelector;
}
}
@@ -11,9 +11,9 @@ import type { HaTimeInput } from "../ha-time-input";
@customElement("ha-selector-datetime")
export class HaDateTimeSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public selector!: DateTimeSelector;
@property({ attribute: false }) public selector!: DateTimeSelector;
@property() public value?: string;
@@ -2,8 +2,8 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ConfigEntry } from "../../data/config_entries";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@@ -13,6 +13,7 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { DeviceSelector } from "../../data/selector";
import { filterSelectorDevices } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker";
@@ -34,12 +35,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@state() public _configEntries?: ConfigEntry[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
@@ -107,48 +108,17 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
}
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.device;
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
return filterSelectorDevices(
this.selector.device,
device,
deviceIntegrations
);
};
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
}
declare global {
@@ -2,15 +2,15 @@ import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { DurationSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-duration-input";
import { HaDurationData } from "../ha-duration-input";
@customElement("ha-selector-duration")
export class HaTimeDuration extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public selector!: DurationSelector;
@property({ attribute: false }) public selector!: DurationSelector;
@property() public value?: string;
@property({ attribute: false }) public value?: HaDurationData;
@property() public label?: string;
@@ -28,7 +28,7 @@ export class HaTimeDuration extends LitElement {
.data=${this.value}
.disabled=${this.disabled}
.required=${this.required}
.enableDay=${this.selector.duration.enable_day}
?enableDay=${this.selector.duration.enable_day}
></ha-duration-input>
`;
}
@@ -1,12 +1,12 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { EntitySelector } from "../../data/selector";
import type { EntitySelector } from "../../data/selector";
import { filterSelectorEntities } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../entity/ha-entities-picker";
import "../entity/ha-entity-picker";
@@ -73,37 +73,8 @@ export class HaEntitySelector extends LitElement {
}
}
private _filterEntities = (entity: HassEntity): boolean => {
const {
domain: filterDomain,
device_class: filterDeviceClass,
integration: filterIntegration,
} = this.selector.entity;
if (filterDomain) {
const entityDomain = computeStateDomain(entity);
if (
Array.isArray(filterDomain)
? !filterDomain.includes(entityDomain)
: entityDomain !== filterDomain
) {
return false;
}
}
if (
filterDeviceClass &&
entity.attributes.device_class !== filterDeviceClass
) {
return false;
}
if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
}
return true;
};
private _filterEntities = (entity: HassEntity): boolean =>
filterSelectorEntities(this.selector.entity, entity, this._entitySources);
}
declare global {
@@ -0,0 +1,98 @@
import { mdiFile } from "@mdi/js";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { removeFile, uploadFile } from "../../data/file_upload";
import { FileSelector } from "../../data/selector";
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../types";
import "../ha-file-upload";
@customElement("ha-selector-file")
export class HaFileSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: FileSelector;
@property() public value?: string;
@property() public label?: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@state() private _filename?: { fileId: string; name: string };
@state() private _busy = false;
protected render() {
return html`
<ha-file-upload
.hass=${this.hass}
.accept=${this.selector.file.accept}
.icon=${mdiFile}
.label=${this.label}
.required=${this.required}
.disabled=${this.disabled}
.helper=${this.helper}
.uploading=${this._busy}
.value=${this.value ? this._filename?.name || "Unknown file" : ""}
@file-picked=${this._uploadFile}
@change=${this._removeFile}
></ha-file-upload>
`;
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (
changedProps.has("value") &&
this._filename &&
this.value !== this._filename.fileId
) {
this._filename = undefined;
}
}
private async _uploadFile(ev) {
this._busy = true;
const file = ev.detail.files![0];
try {
const fileId = await uploadFile(this.hass, file);
this._filename = { fileId, name: file.name };
fireEvent(this, "value-changed", { value: fileId });
} catch (err: any) {
showAlertDialog(this, {
text: this.hass.localize("ui.components.selectors.file.upload_failed", {
reason: err.message || err,
}),
});
} finally {
this._busy = false;
}
}
private _removeFile = async () => {
this._busy = true;
try {
await removeFile(this.hass, this.value!);
} catch (err) {
// Not ideal if removal fails, but will be cleaned up later
} finally {
this._busy = false;
}
this._filename = undefined;
fireEvent(this, "value-changed", { value: "" });
};
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-file": HaFileSelector;
}
}
@@ -24,6 +24,7 @@ export class HaIconSelector extends LitElement {
protected render() {
return html`
<ha-icon-picker
.hass=${this.hass}
.label=${this.label}
.value=${this.value}
.required=${this.required}
@@ -15,13 +15,13 @@ import type { HomeAssistant } from "../../types";
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
import "../ha-alert";
import "../ha-form/ha-form";
import type { HaFormSchema } from "../ha-form/types";
import type { SchemaUnion } from "../ha-form/types";
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
const MANUAL_SCHEMA = [
{ name: "media_content_id", required: false, selector: { text: {} } },
{ name: "media_content_type", required: false, selector: { text: {} } },
];
] as const;
@customElement("ha-selector-media")
export class HaMediaSelector extends LitElement {
@@ -163,7 +163,9 @@ export class HaMediaSelector extends LitElement {
</ha-card>`}`;
}
private _computeLabelCallback = (schema: HaFormSchema): string =>
private _computeLabelCallback = (
schema: SchemaUnion<typeof MANUAL_SCHEMA>
): string =>
this.hass.localize(`ui.components.selectors.media.${schema.name}`);
private _entityChanged(ev: CustomEvent) {

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