Compare commits

...

95 Commits

Author SHA1 Message Date
Bram Kragten
0ab8f8fd7c Merge pull request #11043 from home-assistant/dev 2021-12-29 10:51:08 +01:00
Bram Kragten
86b9eb0bd7 Bumped version to 20211229.0 2021-12-29 10:37:39 +01:00
Paulus Schoutsen
011cbe7d22 Bump HAWS to 6.0.1 (#11042) 2021-12-28 22:27:58 -08:00
Bram Kragten
9b0b2c5b71 Merge pull request #11033 from home-assistant/dev 2021-12-27 20:59:22 +01:00
Bram Kragten
be72bf7b3c Bumped version to 20211227.0 2021-12-27 20:45:56 +01:00
Paulus Schoutsen
3e062ba673 Show error screen when connection fails (#11030) 2021-12-27 11:43:38 +01:00
Paulus Schoutsen
322d965539 Bump HAWS to 6 (#11031) 2021-12-27 11:38:59 +01:00
Bram Kragten
7b840527b5 Fix gauge on ios 15.2 (#10992) 2021-12-26 21:56:24 -08:00
Marius
dced053ba2 Take out 'Stop' (#11024) 2021-12-26 21:55:17 -08:00
Joakim Sørensen
fe4322e64b Remove stop button for all installation types (#10987) 2021-12-21 14:28:51 +01:00
Bram Kragten
0800c702fb Merge pull request #10981 from home-assistant/dev 2021-12-20 14:01:20 +01:00
Bram Kragten
b6d6e2fd4b Fix demo loadFragmentTranslation 2021-12-20 13:42:52 +01:00
Bram Kragten
2bbb1bfa7e Bumped version to 20211220.0 2021-12-20 13:36:32 +01:00
Bram Kragten
e2af8ac3cc Fix theme updating entities card (#10979) 2021-12-20 12:14:54 +01:00
Philip Allgaier
25ff5fef14 More granular control for row interaction catching and cursor (#10971) 2021-12-20 10:27:43 +01:00
Philip Allgaier
2f9c088091 Make "Timers" plural on server control reload page (#10972) 2021-12-20 10:26:35 +01:00
Philip Allgaier
50c397901b Show correct cursor if tap_action is set to "none" (#10963) 2021-12-20 10:26:02 +01:00
Philip Allgaier
1f7d4c25d4 Add word wrap to device model and manufacturer (#10941) 2021-12-20 10:24:44 +01:00
Philip Allgaier
29819fac23 Ensure button and automation row inputs are clickable on mobile (#10940) 2021-12-20 10:24:07 +01:00
Paulus Schoutsen
cc301df57d Force reconnect when triggered from external bus (#10938) 2021-12-20 09:59:45 +01:00
Joakim Sørensen
7d5b566312 Disable repository removal if used for installed add-ons (#10922) 2021-12-17 22:21:46 -08:00
Joakim Sørensen
07cd68f5d0 Always send homeassistant for partial restore (#10927) 2021-12-16 13:57:08 -08:00
Joakim Sørensen
99bf6fa781 Handle update-complete event on add-on page (#10929) 2021-12-16 12:39:21 -08:00
Joakim Sørensen
bfad1eb5ac Add bottom margin to update card (#10948) 2021-12-16 12:33:46 -08:00
J. Nick Koston
6f9b2ee569 Add hardware version to the device info card (#10914) 2021-12-16 05:16:23 -06:00
Bram Kragten
b7bd7c1065 Merge pull request #10930 from home-assistant/dev 2021-12-15 13:48:42 +01:00
Bram Kragten
4ebdca2a46 Bumped version to 20211215.0 2021-12-15 13:36:34 +01:00
Philip Allgaier
fc700fdaf0 Outline new collapsable area in state dev tools + auto-expand (#10917) 2021-12-15 13:15:50 +01:00
Philip Allgaier
d8e12f4280 Add tooltips and aria-labels to media player buttons (#10881) 2021-12-13 16:33:34 -08:00
krazos
86114758c3 Add group to input row domains to fix mobile focus issue (#10897) 2021-12-13 16:30:21 -08:00
Joakim Sørensen
792278cf17 Hide stop for hassio (#10905)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-12-13 16:29:01 -08:00
Joakim Sørensen
b8832f2121 Change entrypoint for Settings (#10904) 2021-12-13 16:08:19 -08:00
Joakim Sørensen
76339c90f7 Show app configuration in sidebar for non-admin users (#10890) 2021-12-13 16:06:46 -08:00
Bram Kragten
b3d4451035 Not valid config, but we support it in the editor (#10893) 2021-12-13 11:01:41 -08:00
Joakim Sørensen
dc58481918 Fix overriding username suggestion (#10899) 2021-12-13 18:56:44 +01:00
Philip Allgaier
14af735507 Fix tooltip and aria-label for password input field (#10898)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-12-13 16:32:45 +00:00
Joakim Sørensen
a7b558b64a Add no update available message (#10891) 2021-12-13 17:20:38 +01:00
Joakim Sørensen
b7665bef6f Don't backup core for supervisor/os updates (#10886) 2021-12-13 10:53:02 +01:00
Christopher Toth
5ec37a35f1 Fix all instances where HTML ARIA-ROLE should actually just be role (#10888) 2021-12-13 08:35:46 +00:00
Philip Allgaier
91bb2ddcc4 Make energy graph colors brighter in dark mode (#10789) 2021-12-12 14:10:30 +01:00
Bram Kragten
61bae5da64 Merge pull request #10880 from home-assistant/dev 2021-12-12 13:49:22 +01:00
Bram Kragten
85168b3a35 Bumped version to 20211212.0 2021-12-12 13:37:28 +01:00
Bram Kragten
942150cda2 Remove milliseconds from state trigger when 0 (#10879) 2021-12-12 12:27:14 +00:00
Philip Allgaier
2606d55895 Add tooltips and aria-labels to climate modes (#10875) 2021-12-12 13:25:05 +01:00
Philip Allgaier
1f671198aa Fix tooltip and aria-label for ZWave JS log download (#10876) 2021-12-12 13:24:24 +01:00
Bram Kragten
deb65e7108 Fix button with images (#10872) 2021-12-12 13:19:32 +01:00
Philip Allgaier
cd00f7f874 Fix typo in cover close tilt translation key (#10871) 2021-12-11 20:59:42 +01:00
Bram Kragten
bdd13db8cf Merge pull request #10869 from home-assistant/dev 2021-12-11 17:32:55 +01:00
Bram Kragten
2b0359edba Bumped version to 20211211.0 2021-12-11 17:15:38 +01:00
Philip Allgaier
35e9687170 Replace mwc-icon-button with ha-icon-button in automation picker (#10858) 2021-12-11 17:15:16 +01:00
Bram Kragten
b730676914 Fix translations cover controls (#10868) 2021-12-11 17:13:43 +01:00
Bram Kragten
2890192c05 Fix formfield label touch (#10867) 2021-12-11 17:13:24 +01:00
Bram Kragten
bfb84a834f Still have manual input if camera is not supported (#10849)
* Still have manual input if camera is not supported

* Adjust & fix
2021-12-11 17:12:41 +01:00
Philip Allgaier
ca6fd6c770 Prevent quickbar command entry duplicates (#10861) 2021-12-11 17:01:24 +01:00
Joakim Sørensen
585648ac4c Revert "handle ha-radio and ha-checkbox in ha-formfield" (#10863) 2021-12-10 23:30:35 -08:00
Matthias de Baat
bec5c564b6 Update blueprint description (#10854) 2021-12-10 11:18:05 -08:00
Erik Montnemery
48c66e6349 Tweak some energy related translation strings (#10852) 2021-12-10 09:49:53 -08:00
Bram Kragten
cea40610c0 Add base trigger to struct (#10851) 2021-12-10 14:44:40 +01:00
Bram Kragten
0c3fd8f3ad typo login -> log in (#10850) 2021-12-10 14:41:09 +01:00
Paulus Schoutsen
cdc3d11181 Merge pull request #10846 from home-assistant/dev 2021-12-09 14:05:30 -08:00
Paulus Schoutsen
02bdeebc82 Bumped version to 20211209.0 2021-12-09 13:39:03 -08:00
Bram Kragten
60c7669d8f Put set state in expansion panel (#10845) 2021-12-09 13:38:27 -08:00
Bram Kragten
919bf94a03 Only add milliseconds when enabled or if it has a value (#10842) 2021-12-09 13:38:03 -08:00
Bram Kragten
ead5e288eb Use normal card color in narrow config screen too (#10843) 2021-12-09 13:37:45 -08:00
Bram Kragten
add8a702cc Change select camera UI, remove manual QR input (#10844) 2021-12-09 13:37:30 -08:00
Paulus Schoutsen
39774c0e02 Allow trigger reconnect from external bus (#10819) 2021-12-09 13:30:20 -08:00
Joakim Sørensen
149f381bc3 Make dashboard entries translatable (#10831) 2021-12-09 09:59:27 -08:00
Bram Kragten
faccb12430 Fix keep me logged in (#10835) 2021-12-09 09:57:11 -08:00
Paulus Schoutsen
7039bae9be Disable local only option for system generated users (#10827) 2021-12-09 11:22:32 +01:00
Bram Kragten
0a7b703d57 Clear warnings when yaml changes (#10820) 2021-12-08 09:12:09 +01:00
Joakim Sørensen
24e8028e8f Use _version_latest for change log URL (#10821) 2021-12-07 23:37:06 +01:00
Paulus Schoutsen
8f729e2a95 Merge pull request #10818 from home-assistant/dev 2021-12-06 15:21:37 -08:00
Paulus Schoutsen
8412cd71cb Bumped version to 20211206.0 2021-12-06 15:11:11 -08:00
Joakim Sørensen
5c78b74005 Reorder configuration (#10817) 2021-12-06 15:10:50 -08:00
Bram Kragten
2459477ec4 Add struct for state trigger and condition (#10811)
* Add struct for state trigger and condition

* remove `milliseconds` from struct
2021-12-06 20:13:32 +01:00
Raman Gupta
a065740c91 zwave_js config param should only be a toggle if there are 2 states (#10812) 2021-12-06 10:49:23 -08:00
Bram Kragten
f3104d3c93 Fix zwavejs provisioned view (#10809) 2021-12-06 10:47:35 -08:00
Paulus Schoutsen
1916c179b4 Merge pull request #10816 from spacegaier/issue-10751 2021-12-06 10:43:33 -08:00
Philip Allgaier
e8b9766eb6 Fix camera stream rendering in area card without picture (#10815) 2021-12-06 10:39:09 -08:00
Philip Allgaier
ff7a2c8cb7 Fix clearing of picture (e.g. area and person config) 2021-12-06 19:29:51 +01:00
Bram Kragten
7ccde2cb41 Fix disabled date input (#10813) 2021-12-06 17:25:23 +00:00
Bram Kragten
d6b9b16f02 Add toggle for camera view in area card (#10810)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
2021-12-06 18:02:32 +01:00
Joakim Sørensen
66df15007a Fetch cloud and updates on reconnect (#10808) 2021-12-06 12:54:16 +01:00
Philip Allgaier
f164d21c44 Filter out invalid text input for input_text (#10797) 2021-12-06 12:53:45 +01:00
Philip Allgaier
911d322aac Mark more trigger fields as optional (#10798) 2021-12-06 12:52:38 +01:00
Joakim Sørensen
419879ee7a Fix core changelog URL (#10804) 2021-12-06 12:51:56 +01:00
Joakim Sørensen
c3e1a2edf0 Use ha-logo-svg on info page (#10807) 2021-12-06 12:51:21 +01:00
Paulus Schoutsen
bc9195f7d5 20211203.0 (#10788)
* Fix thingktalk dialog (#10600)

* Add picture uploader to area (#10544)

* Update image-cropper-dialog.ts

* WebRTC fix for Safari (#10602)

* Update MDI to v6.5.95 (#10618)

* Remove deprecated icons (#10622)

* Improve startup experience by removing AppBar skeleton (#10569)

* Correct ZHA LQI sort in device children dialog (#10616)

* Remove add-on store tab (#10624)

* Add markers-updated to ha-locations-editor (#10601)

* Use ha-form for onboarding-create-user (#10604)

* Fix datatable checkbox width (#10631)

* Move updates (#10626)

* Add correct button label to "no_state" statistics fix dialog (#10628)

* Update Lovelace Cast app ID (#10592)

* Cast fixes (#10598)

* Remove customize UI (#10632)

* Show updates on dashboard for dev (#10637)

* Area Card (#10141)

Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Bumped version to 20211117.0

* Fix back button color (#10650)

* Fix active tab (#10654)

* Remove ha-alert actionText (#10646)

* Use ha-formfield around backup checkbox (#10653)

* Simplify launch screen svg (#10643)

* Always render groups/areas in a single column (#10655)

* Send error message to sender (#10660)

* Add frequency device class for sensor (#10621)

* Fix color over slotted image in ha-alert (#10652)

* Make ha-chip-set slot-able (#10647)

* Remove core note on update page (#10661)

* Add iconColor to ha-config-navigation entries (#10658)

* Use white for icons with backgound (#10672)

* Fix color overlay in ha-alert content (#10674)

* Add scenes and scripts as buttons in footer of area cards (#10673)

* Add scenes and scripts as chips in footer of area cards

* Remove unused chips config type

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

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

* Fix typing

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

* Fix dark main-content and split gallery demo (#10675)

* Make "Show more" show everything starting from yesterday (#10533)

* Use component to ensure relative-time in Glance card gets updated (#10666)

* Limit setting up supervisor subscriptions to the supervisor panel (#10680)

* Remove first part of the update description (#10669)

* Fixing typo in #10626 (#10686)

* Bumped version to 20211123.0

* Update background colors of navigation icons (#10691)

* Render update card on add-on page (#10681)

* Fix addon slug (#10693)

* Improve device information when via device is unknown (#10685)

* Don't make button disabled on error (#10699)

* Use app-header-text-color (#10711)

* Finish up config changes (#10710)

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

* Fix typo on config page + adjust icon color (#10713)

* Add ha-faded (#10651)

* Use `ha-icon-button` in `ha-icon-overflow-menu` (#10692)

* Prevent errors in `more-info-climate` if no modes are provided despite support flags (#10694)

* Make "Energy distribution today" translatable (#10696)

* Default to yaml editing when there are multiple states in condition (#10481)

* Filter out disabled entities in the statistics dev tools (#10677)

* Convert cover UI to Lit + ensure proper tilt rendering (#10671)

* Fixed ellipsis usage on graph legend entries. (#10707)

* Ensure required translations are loaded in safe-mode (#10709)

* Ensure markdown card input is a string (#10705)

* Fix chip text color variable overrides (#10722)

* Ensure `conditional` rows getting `state_color` value (#10708)

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

* Fixed invalid hour handling in AMPM mode (#10717)

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

* Installation type property during onboarding was misspelled (#10721)

* Dashboard tweaks (#10729)

* Tweak how scenes behave in generated lovelace (#10730)

* Bumped version to 20211130.0

* Improve hls stream view error handling (#10714)

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

* Move companion app config from sidebar to configuration dashboard (#10733)

* Move companion app config from sidebar to configuration dashboard

* Remove translation refrence

* Fix typo (#10734)

* Revert 10711 (#10736)

* Use backend for day month stats in energy dashboard (#10728)

* Handle 0 updates and show back on supervisor panels (#10744)

* Hide ha-icon-next if narrow (#10746)

* Change the area of scenes in editor (#10731)

* Fix faded element in change log (#10737)

* Updated text (#10747)

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

* Focus Add-ons & Backups in config panel when clicking Supervisor in sidebar (#10745)

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

* Add SmartStart/QR scan support for Z-Wave JS (#10726)

* Show disabled entity names on the device page (#10743)

* Show disabled entity names on the device page

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

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

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

* Bumped version to 20211201.0

* Fix pointer/more-info inconsistencies for entity rows (#10025)

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

* Make graph colors themable (#10698)

* Use puzzle for addons and blur entries on click (#10755)

* Fix create backup checkbox (#10756)

* Use unit system definitions for weather units (#10657)

* handle ha-radio and ha-checkbox in ha-formfield (#10759)

* Fix SU sidebar issues (#10757)

* Use add-ons for mobile header (#10760)

* Hide updates for dev as well (#10761)

* Remove thingtalk cleanup create new automation dialog (#10748)

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

* Add missing translation (#10769)

* Update hui-graph-header-footer.ts (#10476)

* Group entities in area card by domain (#10767)

* Group entities in area card by domain

* Update hui-area-card.ts

* Update

* Add background color when no image

* Add camera support

* exclude unavailable states

* Update hui-area-card.ts

* Use chips for button rows (#10770)

* Bumped version to 20211202.0

* Show add devices fab on devices page for ZJS (#10771)

* Add default icons for button entities (#10774)

* Remove handling of the supervisor panel from the sidebar (#10773)

* Tweak ZJS dashboard (#10772)

* Guard for non numeric states (#10775)

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* Use correct styling for cloud certificate dialog (#10782)

* Allow overriding device class (#10777)

* Restore flex alignment for select and input-select rows (#10783)

* Add support for local only users (#10784)

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* Differentiate between assigned and targeting scene/automations/script (#10781)

* Add provisioned device overview to zwave js (#10785)

* Use groupBy (#10786)

* Ensure we always have an active theme name (fixes dark theme issues) (#10780)

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

* safari doesnt support overflow-wrap: anywhere

* Fix entity marker (#10787)

* Bumped version to 20211203.0

Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: Michael Irigoyen <michael@irigoyen.dev>
Co-authored-by: Lasse Rosenow <10547444+LasseRosenow@users.noreply.github.com>
Co-authored-by: David F. Mulcahey <david.mulcahey@me.com>
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
Co-authored-by: Laszlo Magyar <lmagyar1973@gmail.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Nathan Orick <cnathanorick@gmail.com>
Co-authored-by: Luca Cavalli <lcavalli@users.noreply.github.com>
Co-authored-by: amitfin <amittein@gmail.com>
Co-authored-by: Matthias de Baat <hello@matthiasdebaat.com>
Co-authored-by: rianadon <ryanadolf123@gmail.com>
Co-authored-by: Carlos Garcia Saura <CarlosGS@users.noreply.github.com>
2021-12-03 09:36:27 -08:00
Bram Kragten
7f1a321075 Merge pull request #10590 from home-assistant/dev 2021-11-09 22:06:29 +01:00
Bram Kragten
72b9f8636d Merge pull request #10578 from home-assistant/dev 2021-11-08 18:54:44 +01:00
Bram Kragten
c9cd316c0c Play dummy media to prevent app from closing (#10531) 2021-11-08 13:04:22 +01:00
Bram Kragten
6cf3580fb4 Merge pull request #10506 from home-assistant/dev 2021-11-03 11:02:34 +01:00
Bram Kragten
5d91aefb55 Merge pull request #10453 from home-assistant/dev 2021-10-28 20:24:05 +02:00
Bram Kragten
e3c0530941 Merge pull request #10426 from home-assistant/dev 2021-10-27 21:16:47 +02:00
Paulus Schoutsen
2c9223ed80 Merge pull request #10415 from home-assistant/dev (#10415)
* Use MWC components for ha-form (#10120)

* Dont create icon for supervisor (#10191)

* Fix import (#10206)

* Add "gas" device_class to customize (and sort existing ones) (#10196)

* Make zone names readable on map in dark mode (#10195)

* Tweak ha-form (#10194)

* Extract black/white row into component (#10212)

* Extract black/white row into component

* Remove unused import

* Fix dirty check/leaving automation editor (#10211)

* Add selector demo to gallery (#10213)

* Fix icon overlay for person badges (#10201)

* Convert iframe panel to Lit (#10216)

* Allow disabling an ha-form (#10218)

* Fix alarm panel badge (#10221)

* Add missing validation text (#10225)

* Apply flat polyfill globally (#10222)

* Add ha-bar to gallery (#10242)

* Handle text overflow for tabs (#10239)

* Remove "battery" device class from fixed icon list (#10246)

* Add ha-chip to gallery (#10252)

* Add netlify build script for gallery (#10253)

* Add ha-label-badge to gallery (#10248)

* Use correct build url (#10258)

* Remove "Hass.io" from translation (#10257)

* Update demo template (#10256)

* Add WebRTC stream player (#10193)

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

* Add tamper device class for binary sensor (#10268)

* Fix missing translatable energy texts (#10230)

* Consolidate all icon button logic into `<ha-icon-button>` + ensure tooltip (#9230)

* Fix sizing / positioning error for trace graph node with subsequent branches (#10049)

* Initial support for entity category (#10266)

* Add support for device configuration URL (#10251)

* Add support for device configuration URL

* Lint

* Tweak text

* Bump mdc/mwc to 0.25.2 (#10271)

* Bumped version to 20211014.0

* Warn if iframe won't be able to load the website (#10217)

* Disable ha-form while submitting entry flow (#10290)

* Convert all warning classes to ha-alert (#10289)

* ABC automation types + use MWC (#10287)

* Add "capitalize" option to `hui-timestamp-display` (#10280)

* Add additional binary device classes to inversion list (#10152)

* Fix energy onboarding `add_solar_production` button (#10275) (#10286)

* Unify default dashboard name (#10254)

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

* Fix icon buttons in Safari (#10293)

* Only render badge value if there is no icon and no image (#10310)

* Update MDI to v6.3.95 (#10313)

* Rename `stream_type` to `frontend_stream_type` (#10298)

* Fix translation key energy distribution solar (#10316)

* Prevent mwc-list-item from opening up quick-bar (#10317)

* Remove element resize hook (#10300)

* Improve WebRTC stream error handling and cleanup (#10302)

* Fix formatting of weather extrema temperatures (#10306)

* Ensure current active dark modes gets used for manually set themes (#10307)

* Add views dropdown and footer actions to the "move to view" dialog (#10172)

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

* Icon Picker (#10161)

* Use maxLiveSyncPlaybackRate in ha-hls-player (#10323)

* Revise grid neutrality energy dashboard card, modify energy dashboard presentation to match (#10054)

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

* Fix `ha-icon-button` in `ha-file-upload` (#10328)

* Use error for protection mode alert (#10315)

* Change unsupported reason container to software (#10325)

* Migrate all paper checkbox elements to mwc (#10329)

* Migrate all paper-radio elements to mwc-radio (#10327)

* Correct grid neutrality card tooltip, make consistent with new colors (#10326)

* Fix select options for add-on config (#10330)

* Migrate all paper dialogs to mwc (#10333)

* Stack gas and solar sources (#10244)

* Set default value when enabling optional value (#10247)

* Fix overflow icon color in backup dialog (#10331)

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

* Convert default state icons (#10223)

* Convert default state icons

* update

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

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

* Update ha-config-core.js

* Update

* Finish

* Add siren icon

* FIx

* Add curtain icons

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

* Use secondary-text-color for trailing icon (#10340)

* Use svg icons for default panels (#10342)

* Tweak icon picker a bit (#10319)

* Add support for `no-state` and `entity-no-longer-available` statistic… (#10345)

* Change dark mode input fill color (#10341)

* Replace paper progress with mwc-linear-progess (#10339)

* Bumped version to 20211020.0

* Add auto slider/box mode to number entity (#10272)

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

* Correct automation editor event action translation (#10355)

* Convert cloud account config to Lit (#10350)

* Restore proper state badge image behavior (#10369)

* Add to do list support to markdown (#10129)

* Catch error if input_datetime state is incorrect (#10237)

* Update MDI to v6.4.95 (#10389)

* Remove deprecated icons that where replaced (#10371)

* Make all automation type pickers use natural width to be able to show… (#10391)

* Trim device name from entities on device page (#10285)

* Update markdown card to allow word to be broken (#10387)

* Fix Full Calendar Background color (#10373)

* Add additional properties to zwave_js device info panel (#10132)

* Fix various `slugify()` issues + add tests (#10383)

* Add stopPropagation to move click handlers (#10379)

* Use ha-chip for alarm control panel card (#10393)

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

* Fix timezone issues with date formatting for ES5 (#10370)

* Add automation editor to gallery (#10392)

* Use ha-chip instead of ha-label-badge for add-on capabilities (#10398)

* Do not close edit dialog when more info is escaped (#10249)

* Ensure Sortable is recreated when card editors are reopened (#10382)

* Ensure explicit `false` values from customize form get stored (#10381)

* Add running device class to binary sensor (#10400)

* Ensure consistent card look on device config page (#10386)

* Add "Keep me logged in" checkbox within login flow (#10226)

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

* Update delay label (#10284)

* Introduced ha-icon-overflow-menu component (#10352)

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

* Use ha-alert to warn about logs from custom integrations (#10396)

* Add support for hiding current weather in forecast card (#10267)

* Allow configuration_url to point to an internal panel (#10395)

* Bump Lit (#10409)

* Bump format js (#10405)

* Bump codemirror (#10404)

* Bump and patch material elements (#10406)

* Add blueprint scripts (#9504)

* Make device classes in logbook translatable (#10376)

* Improve device info add to Lovelace (#10413)

* Add navigation option from more-info to history (#9717)

* Move entities to center column on device page (#10412)

* Bumped version to 20211026.0

* Shrink new section titles in more-info dialog a bit (#10414)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Jack Wilsdon <jack.wilsdon@gmail.com>
Co-authored-by: Josh McCarty <josh@joshmccarty.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Allen Porter <allen.porter@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: chriss158 <edgi@arcor.de>
Co-authored-by: Kyle Niewiada <aav7fl@users.noreply.github.com>
Co-authored-by: MartinT <44962077+MartinTuroci@users.noreply.github.com>
Co-authored-by: Michael Irigoyen <michael@irigoyen.dev>
Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com>
Co-authored-by: Will Adler <will@wtadler.com>
Co-authored-by: Rogério Ribeiro <zroger499@gmail.com>
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: Nathan Orick <cnathanorick@gmail.com>
Co-authored-by: Tobias Kündig <tobias@offline.ch>
Co-authored-by: Marc Hörsken <mback2k@users.noreply.github.com>
2021-10-26 13:35:46 -07:00
86 changed files with 1110 additions and 515 deletions

View File

@@ -206,6 +206,7 @@ const createDeviceRegistryEntries = (
model: "Mock Device",
name: "Tag Reader",
sw_version: null,
hw_version: "1.0.0",
id: "mock-device-id",
identifiers: [],
via_device_id: null,

View File

@@ -133,6 +133,7 @@ class HassioAddonInfo extends LitElement {
.narrow=${this.narrow}
.supervisor=${this.supervisor}
.addonSlug=${this.addon.slug}
@update-complete=${this._updateComplete}
></update-available-card>
`
: ""}
@@ -865,6 +866,15 @@ class HassioAddonInfo extends LitElement {
}
}
private _updateComplete() {
const eventdata = {
success: true,
response: undefined,
path: "install",
};
fireEvent(this, "hass-api-called", eventdata);
}
private async _installClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;

View File

@@ -350,9 +350,7 @@ export class SupervisorBackupContent extends LitElement {
if (folders?.length) {
data.folders = folders;
}
if (this.homeAssistant) {
data.homeassistant = this.homeAssistant;
}
data.homeassistant = this.homeAssistant;
return data;
}

View File

@@ -1,5 +1,6 @@
import "@polymer/paper-tooltip/paper-tooltip";
import "@material/mwc-button/mwc-button";
import { mdiDelete } from "@mdi/js";
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
@@ -15,6 +16,7 @@ import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button";
import {
fetchHassioAddonsInfo,
HassioAddonInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
@@ -60,11 +62,24 @@ class HassioRepositoriesDialog extends LitElement {
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
);
private _filteredUsedRepositories = memoizeOne(
(repos: HassioAddonRepository[], addons: HassioAddonInfo[]) =>
repos
.filter((repo) =>
addons.some((addon) => addon.repository === repo.slug)
)
.map((repo) => repo.slug)
);
protected render(): TemplateResult {
if (!this._dialogParams?.supervisor || this._repositories === undefined) {
return html``;
}
const repositories = this._filteredRepositories(this._repositories);
const usedRepositories = this._filteredUsedRepositories(
repositories,
this._dialogParams.supervisor.supervisor.addons
);
return html`
<ha-dialog
.open=${this._opened}
@@ -89,18 +104,32 @@ class HassioRepositoriesDialog extends LitElement {
<div secondary>${repo.maintainer}</div>
<div secondary>${repo.url}</div>
</paper-item-body>
<ha-icon-button
.slug=${repo.slug}
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
.path=${mdiDelete}
@click=${this._removeRepository}
></ha-icon-button>
<div class="delete">
<ha-icon-button
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
<paper-tooltip
animation-delay="0"
position="bottom"
offset="1"
>
${this._dialogParams!.supervisor.localize(
usedRepositories.includes(repo.slug)
? "dialog.repositories.used"
: "dialog.repositories.remove"
)}
</paper-tooltip>
</div>
</paper-item>
`
)
: html` <paper-item> No repositories </paper-item> `}
: html`<paper-item> No repositories </paper-item>`}
<div class="layout horizontal bottom">
<paper-input
class="flex-auto"
@@ -157,6 +186,9 @@ class HassioRepositoriesDialog extends LitElement {
margin: 32px;
text-align: center;
}
div.delete ha-icon-button {
color: var(--error-color);
}
`,
];
}

View File

@@ -48,7 +48,6 @@ import "../../../src/layouts/hass-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import { SUPERVISOR_UPDATE_NAMES } from "../../../src/panels/config/dashboard/ha-config-updates";
import { HomeAssistant, Route } from "../../../src/types";
import { documentationUrl } from "../../../src/util/documentation-url";
import { addonArchIsSupported, extractChangelog } from "../util/addon";
declare global {
@@ -60,7 +59,6 @@ declare global {
type updateType = "os" | "supervisor" | "core" | "addon";
const changelogUrl = (
hass: HomeAssistant,
entry: updateType,
version: string
): string | undefined => {
@@ -68,17 +66,19 @@ const changelogUrl = (
return undefined;
}
if (entry === "core") {
return version?.includes("dev")
return version.includes("dev")
? "https://github.com/home-assistant/core/commits/dev"
: documentationUrl(hass, "/latest-release-notes/");
: version.includes("b")
? "https://next.home-assistant.io/latest-release-notes/"
: "https://www.home-assistant.io/latest-release-notes/";
}
if (entry === "os") {
return version?.includes("dev")
return version.includes("dev")
? "https://github.com/home-assistant/operating-system/commits/dev"
: `https://github.com/home-assistant/operating-system/releases/tag/${version}`;
}
if (entry === "supervisor") {
return version?.includes("dev")
return version.includes("dev")
? "https://github.com/home-assistant/supervisor/commits/main"
: `https://github.com/home-assistant/supervisor/releases/tag/${version}`;
}
@@ -120,7 +120,7 @@ class UpdateAvailableCard extends LitElement {
return html``;
}
const changelog = changelogUrl(this.hass, this._updateType, this._version);
const changelog = changelogUrl(this._updateType, this._version_latest);
return html`
<ha-card
@@ -132,7 +132,13 @@ class UpdateAvailableCard extends LitElement {
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._action === null
${this._version === this._version_latest
? html`<p>
${this.supervisor.localize("update_available.no_update", {
name: this._name,
})}
</p>`
: this._action === null
? html`
${this._changelogContent
? html`
@@ -177,7 +183,7 @@ class UpdateAvailableCard extends LitElement {
)}
</p>`}
</div>
${this._action === null
${this._version !== this._version_latest && this._action === null
? html`
<div class="card-actions">
${changelog
@@ -224,6 +230,9 @@ class UpdateAvailableCard extends LitElement {
}
get _shouldCreateBackup(): boolean {
if (this._updateType && !["core", "addon"].includes(this._updateType)) {
return false;
}
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
if (checkbox) {
return checkbox.checked;

View File

@@ -46,6 +46,7 @@ class UpdateAvailableDashboard extends LitElement {
update-available-card {
margin: auto;
margin-top: 16px;
margin-bottom: 24px;
max-width: 600px;
}
`;

View File

@@ -102,7 +102,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.0.11",
"home-assistant-js-websocket": "^5.11.3",
"home-assistant-js-websocket": "^6.0.1",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20211203.0",
version="20211229.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",

View File

@@ -199,15 +199,19 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"select",
];
/** Domains that render an input element instead of a text value when rendered in a row.
/** 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
* should not act as a click target to open the more info dialog (the row name and state icon
* still do of course) as the click might instead e.g. activate the input field that this row shows.
* still do of course) as the click should instead e.g. activate the input field or toggle
* the button that this row shows.
*/
export const DOMAINS_INPUT_ROW = [
"automation",
"button",
"cover",
"fan",
"group",
"humidifier",
"input_boolean",
"input_datetime",
@@ -222,6 +226,7 @@ export const DOMAINS_INPUT_ROW = [
"script",
"select",
"switch",
"vacuum",
];
/** Domains that should have the history hidden in the more info dialog. */

View File

@@ -46,6 +46,7 @@ class HaAlert extends LitElement {
rtl: this.rtl,
[this.alertType]: true,
})}"
role="alert"
>
<div class="icon ${this.title ? "" : "no-title"}">
<slot name="icon">

View File

@@ -56,6 +56,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
return html`
<ha-icon-button
@click=${this._handleClick}
.label=${this.hass.localize("ui.components.related-filter-menu.filter")}
.path=${mdiFilterVariant}
></ha-icon-button>
<mwc-menu-surface

View File

@@ -18,13 +18,9 @@ export class HaChip extends LitElement {
protected render(): TemplateResult {
return html`
<div class="mdc-chip">
<div class="mdc-chip ${this.noText ? "no-text" : ""}">
${this.hasIcon
? html`<div
class="mdc-chip__icon mdc-chip__icon--leading ${this.noText
? "no-text"
: ""}"
>
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
<slot name="icon"></slot>
</div>`
: null}
@@ -49,6 +45,10 @@ export class HaChip extends LitElement {
color: var(--ha-chip-text-color, var(--primary-text-color));
}
.mdc-chip.no-text {
padding: 0 10px;
}
.mdc-chip:hover {
color: var(--ha-chip-text-color, var(--primary-text-color));
}
@@ -57,8 +57,8 @@ export class HaChip extends LitElement {
--mdc-icon-size: 20px;
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
}
.mdc-chip
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden).no-text {
.mdc-chip.no-text
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
margin-right: -4px;
}
`;

View File

@@ -35,7 +35,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsOpen(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.open_cover"
"ui.dialogs.more_info_control.cover.open_cover"
)}
@click=${this._onOpenTap}
.disabled=${this._computeOpenDisabled()}
@@ -47,7 +47,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsStop(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.stop_cover"
"ui.dialogs.more_info_control.cover.stop_cover"
)}
.path=${mdiStop}
@click=${this._onStopTap}
@@ -58,7 +58,7 @@ class HaCoverControls extends LitElement {
hidden: !supportsClose(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.close_cover"
"ui.dialogs.more_info_control.cover.close_cover"
)}
@click=${this._onCloseTap}
.disabled=${this._computeClosedDisabled()}

View File

@@ -30,7 +30,7 @@ class HaCoverTiltControls extends LitElement {
invisible: !supportsOpenTilt(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.open_tilt_cover"
"ui.dialogs.more_info_control.cover.open_tilt_cover"
)}
.path=${mdiArrowTopRight}
@click=${this._onOpenTiltTap}
@@ -40,7 +40,9 @@ class HaCoverTiltControls extends LitElement {
class=${classMap({
invisible: !supportsStopTilt(this.stateObj),
})}
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.cover.stop_cover"
)}
.path=${mdiStop}
@click=${this._onStopTiltTap}
.disabled=${this.stateObj.state === UNAVAILABLE}
@@ -50,7 +52,7 @@ class HaCoverTiltControls extends LitElement {
invisible: !supportsCloseTilt(this.stateObj),
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.close_tilt_cover"
"ui.dialogs.more_info_control.cover.close_tilt_cover"
)}
.path=${mdiArrowBottomLeft}
@click=${this._onCloseTiltTap}

View File

@@ -96,7 +96,11 @@ export class HaDateInput extends LitElement {
attr-for-value="value"
.i18n=${i18n}
>
<paper-input .label=${this.label} no-label-float>
<paper-input
.label=${this.label}
.disabled=${this.disabled}
no-label-float
>
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
</paper-input>
</vaadin-date-picker-light>`;

View File

@@ -122,14 +122,20 @@ class HaDurationInput extends LitElement {
value %= 60;
}
const newValue: HaDurationData = {
hours,
minutes,
seconds: this._seconds,
};
if (this.enableMillisecond || this._milliseconds) {
newValue.milliseconds = this._milliseconds;
}
newValue[unit] = value;
fireEvent(this, "value-changed", {
value: {
hours,
minutes,
seconds: this._seconds,
milliseconds: this._milliseconds,
...{ [unit]: value },
},
value: newValue,
});
}
}

View File

@@ -66,7 +66,7 @@ export class HaFormString extends LitElement implements HaFormElement {
${isPassword
? html`<ha-icon-button
toggles
.label="Click to toggle between masked and clear password"
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
@click=${this._toggleUnmaskedPassword}
tabindex="-1"
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}

View File

@@ -1,6 +1,7 @@
import { Formfield } from "@material/mwc-formfield";
import { css, CSSResultGroup } from "lit";
import { customElement } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-formfield")
// @ts-expect-error
@@ -13,6 +14,7 @@ export class HaFormfield extends Formfield {
case "HA-CHECKBOX":
case "HA-RADIO":
(input as any).checked = !(input as any).checked;
fireEvent(input, "change");
break;
default:
input.click();

View File

@@ -8,6 +8,10 @@ import { FrontendLocaleData } from "../data/translation";
import { getValueInPercentage, normalize } from "../util/calculate";
import { isSafari } from "../util/is_safari";
// Safari version 15.2 and up behaves differently than other Safari versions.
// https://github.com/home-assistant/frontend/issues/10766
const isSafari152 = isSafari && /Version\/15\.[^0-1]/.test(navigator.userAgent);
const getAngle = (value: number, min: number, max: number) => {
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
return (percentage * 180) / 100;
@@ -113,7 +117,9 @@ export class Gauge extends LitElement {
: undefined
)}
transform=${ifDefined(
isSafari ? `rotate(${this._angle} 50 50)` : undefined
isSafari
? `rotate(${this._angle}${isSafari152 ? "" : " 50 50"})`
: undefined
)}
>
`
@@ -126,7 +132,9 @@ export class Gauge extends LitElement {
: undefined
)}
transform=${ifDefined(
isSafari ? `rotate(${this._angle} 50 50)` : undefined
isSafari
? `rotate(${this._angle}${isSafari152 ? "" : " 50 50"})`
: undefined
)}
>`
}

View File

@@ -29,7 +29,7 @@ export class HaIconOverflowMenu extends LitElement {
protected render(): TemplateResult {
return html`
${this.narrow
? html` <!-- Collapsed Representation for Small Screens -->
? html` <!-- Collapsed representation for small screens -->
<ha-button-menu
@click=${this._handleIconOverflowMenuOpened}
@closed=${this._handleIconOverflowMenuClosed}
@@ -59,8 +59,7 @@ export class HaIconOverflowMenu extends LitElement {
)}
</ha-button-menu>`
: html`
<!-- Icon Representation for Big Screens -->
<!-- Icon representation for big screens -->
${this.items.map((item) =>
item.narrowOnly
? ""
@@ -70,13 +69,12 @@ export class HaIconOverflowMenu extends LitElement {
${item.tooltip}
</paper-tooltip>`
: ""}
<mwc-icon-button
<ha-icon-button
@click=${item.action}
.label=${item.label}
.path=${item.path}
.disabled=${item.disabled}
>
<ha-svg-icon .path=${item.path}></ha-svg-icon>
</mwc-icon-button>
></ha-icon-button>
</div> `
)}
`}

View File

@@ -39,6 +39,7 @@ export class HaPictureUpload extends LitElement {
.uploading=${this._uploading}
.value=${this.value ? html`<img .src=${this.value} />` : ""}
@file-picked=${this._handleFilePicked}
@change=${this._handleFileCleared}
accept="image/png, image/jpeg, image/gif"
></ha-file-upload>
`;
@@ -53,6 +54,10 @@ export class HaPictureUpload extends LitElement {
}
}
private async _handleFileCleared() {
this.value = null;
}
private async _cropFile(file: File) {
if (!["image/png", "image/jpeg", "image/gif"].includes(file.type)) {
showAlertDialog(this, {

View File

@@ -1,6 +1,8 @@
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-select/mwc-select";
import type { Select } from "@material/mwc-select/mwc-select";
import "@material/mwc-textfield/mwc-textfield";
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
import { mdiCamera } from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type QrScanner from "qr-scanner";
@@ -8,6 +10,8 @@ import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { LocalizeFunc } from "../common/translations/localize";
import "./ha-alert";
import "./ha-button-menu";
import "@material/mwc-button/mwc-button";
@customElement("ha-qr-scanner")
class HaQrScanner extends LitElement {
@@ -25,6 +29,8 @@ class HaQrScanner extends LitElement {
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
@query("mwc-textfield") private _manualInput?: TextField;
public disconnectedCallback(): void {
super.disconnectedCallback();
this._qrNotFoundCount = 0;
@@ -58,34 +64,53 @@ class HaQrScanner extends LitElement {
}
protected render(): TemplateResult {
return html`${this._cameras && this._cameras.length > 1
? html`<mwc-select
.label=${this.localize(
"ui.panel.config.zwave_js.add_node.select_camera"
)}
fixedMenuPosition
naturalMenuWidth
@closed=${stopPropagation}
@selected=${this._cameraChanged}
>
${this._cameras!.map(
(camera) => html`
<mwc-list-item .value=${camera.id}>${camera.label}</mwc-list-item>
`
)}
</mwc-select>`
: ""}
${this._error
return html`${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${navigator.mediaDevices
? html`<video></video>
<div id="canvas-container"></div>`
: html`<ha-alert alert-type="warning"
>${!window.isSecureContext
? "You can only use your camera to scan a QR core when using HTTPS."
: "Your browser doesn't support QR scanning."}</ha-alert
>`}`;
<div id="canvas-container">
${this._cameras && this._cameras.length > 1
? html`<ha-button-menu
corner="BOTTOM_START"
fixed
@closed=${stopPropagation}
>
<ha-icon-button
slot="trigger"
.label=${this.localize(
"ui.components.qr-scanner.select_camera"
)}
.path=${mdiCamera}
></ha-icon-button>
${this._cameras!.map(
(camera) => html`
<mwc-list-item
.value=${camera.id}
@click=${this._cameraChanged}
>${camera.label}</mwc-list-item
>
`
)}
</ha-button-menu>`
: ""}
</div>`
: html`<ha-alert alert-type="warning">
${!window.isSecureContext
? this.localize("ui.components.qr-scanner.only_https_supported")
: this.localize("ui.components.qr-scanner.not_supported")}
</ha-alert>
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
<div class="row">
<mwc-textfield
.label=${this.localize("ui.components.qr-scanner.enter_qr_code")}
@keyup=${this._manualKeyup}
@paste=${this._manualPaste}
></mwc-textfield>
<mwc-button @click=${this._manualSubmit}
>${this.localize("ui.common.submit")}</mwc-button
>
</div>`}`;
}
private async _loadQrScanner() {
@@ -134,17 +159,49 @@ class HaQrScanner extends LitElement {
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
};
private _manualKeyup(ev: KeyboardEvent) {
if (ev.key === "Enter") {
this._qrCodeScanned((ev.target as TextField).value);
}
}
private _manualPaste(ev: ClipboardEvent) {
this._qrCodeScanned(
// @ts-ignore
(ev.clipboardData || window.clipboardData).getData("text")
);
}
private _manualSubmit() {
this._qrCodeScanned(this._manualInput!.value);
}
private _cameraChanged(ev: CustomEvent): void {
this._qrScanner?.setCamera((ev.target as Select).value);
this._qrScanner?.setCamera((ev.target as any).value);
}
static styles = css`
canvas {
width: 100%;
}
mwc-select {
width: 100%;
margin-bottom: 16px;
#canvas-container {
position: relative;
}
ha-button-menu {
position: absolute;
bottom: 8px;
right: 8px;
background: #727272b2;
color: white;
border-radius: 50%;
}
.row {
display: flex;
align-items: center;
}
mwc-textfield {
flex: 1;
margin-right: 8px;
}
`;
}

View File

@@ -3,6 +3,7 @@ import {
mdiBell,
mdiCalendar,
mdiCart,
mdiCellphoneCog,
mdiChartBox,
mdiClose,
mdiCog,
@@ -43,6 +44,10 @@ import {
PersistentNotification,
subscribeNotifications,
} from "../data/persistent_notification";
import {
ExternalConfig,
getExternalConfig,
} from "../external_app/external_config";
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant, PanelInfo, Route } from "../types";
@@ -187,6 +192,8 @@ class HaSidebar extends LitElement {
@property({ type: Boolean }) public editMode = false;
@state() private _externalConfig?: ExternalConfig;
@state() private _notifications?: PersistentNotification[];
@state() private _renderEmptySortable = false;
@@ -233,6 +240,7 @@ class HaSidebar extends LitElement {
changedProps.has("expanded") ||
changedProps.has("narrow") ||
changedProps.has("alwaysExpand") ||
changedProps.has("_externalConfig") ||
changedProps.has("_notifications") ||
changedProps.has("editMode") ||
changedProps.has("_renderEmptySortable") ||
@@ -263,6 +271,12 @@ class HaSidebar extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
if (this.hass && this.hass.auth.external) {
getExternalConfig(this.hass.auth.external).then((conf) => {
this._externalConfig = conf;
});
}
subscribeNotifications(this.hass.connection, (notifications) => {
this._notifications = notifications;
});
@@ -358,6 +372,7 @@ class HaSidebar extends LitElement {
: this._renderPanels(beforeSpacer)}
${this._renderSpacer()}
${this._renderPanels(afterSpacer)}
${this._renderExternalConfiguration()}
</paper-listbox>
`;
}
@@ -387,7 +402,7 @@ class HaSidebar extends LitElement {
) {
return html`
<a
aria-role="option"
role="option"
href=${`/${urlPath}`}
data-panel=${urlPath}
tabindex="-1"
@@ -492,7 +507,7 @@ class HaSidebar extends LitElement {
>
<paper-icon-item
class="notifications"
aria-role="option"
role="option"
@click=${this._handleShowNotificationDrawer}
>
<ha-svg-icon slot="item-icon" .path=${mdiBell}></ha-svg-icon>
@@ -523,7 +538,7 @@ class HaSidebar extends LitElement {
href="/profile"
data-panel="panel"
tabindex="-1"
aria-role="option"
role="option"
aria-label=${this.hass.localize("panel.profile")}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
@@ -542,6 +557,43 @@ class HaSidebar extends LitElement {
</a>`;
}
private _renderExternalConfiguration() {
return html`${!this.hass.user?.is_admin &&
this._externalConfig &&
this._externalConfig.hasSettingsScreen
? html`
<a
role="option"
aria-label=${this.hass.localize(
"ui.sidebar.external_app_configuration"
)}
href="#external-app-configuration"
tabindex="-1"
@click=${this._handleExternalAppConfiguration}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>
<paper-icon-item>
<ha-svg-icon
slot="item-icon"
.path=${mdiCellphoneCog}
></ha-svg-icon>
<span class="item-text">
${this.hass.localize("ui.sidebar.external_app_configuration")}
</span>
</paper-icon-item>
</a>
`
: ""}`;
}
private _handleExternalAppConfiguration(ev: Event) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
private get _tooltip() {
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
}

View File

@@ -13,6 +13,7 @@ export interface DeviceRegistryEntry {
model: string | null;
name: string | null;
sw_version: string | null;
hw_version: string | null;
via_device_id: string | null;
area_id: string | null;
name_by_user: string | null;

View File

@@ -156,6 +156,7 @@ export interface MediaPlayerThumbnail {
export interface ControlButton {
icon: string;
// Used as key for action as well as tooltip and aria-label translation key
action: string;
}

View File

@@ -208,11 +208,11 @@ export const enum NodeStatus {
export interface ZwaveJSProvisioningEntry {
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
dsk: string;
securityClasses: SecurityClass[];
/**
* Additional properties to be stored in this provisioning entry, e.g. the device ID from a scanned QR code
*/
[prop: string]: any;
security_classes: SecurityClass[];
additional_properties: {
nodeId?: number;
[prop: string]: any;
};
}
export interface RequestedGrant {
@@ -278,7 +278,7 @@ export const setZwaveDataCollectionPreference = (
export const fetchZwaveProvisioningEntries = (
hass: HomeAssistant,
entry_id: string
): Promise<any> =>
): Promise<ZwaveJSProvisioningEntry[]> =>
hass.callWS({
type: "zwave_js/get_provisioning_entries",
entry_id,

View File

@@ -65,6 +65,9 @@ class MoreInfoMediaPlayer extends LitElement {
action=${control.action}
@click=${this._handleClick}
.path=${control.icon}
.label=${this.hass.localize(
`ui.card.media_player.${control.action}`
)}
>
</ha-icon-button>
`

View File

@@ -99,6 +99,8 @@ export class QuickBar extends LitElement {
private _focusSet = false;
private _focusListElement?: ListItem | null;
public async showDialog(params: QuickBarParams) {
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
this._initializeItemsIfNeeded();
@@ -317,7 +319,8 @@ export class QuickBar extends LitElement {
} else if (ev.code === "ArrowDown") {
ev.preventDefault();
this._getItemAtIndex(0)?.focus();
this._getItemAtIndex(1)?.focus();
this._focusSet = true;
this._focusListElement = this._getItemAtIndex(0);
}
}
@@ -350,6 +353,11 @@ export class QuickBar extends LitElement {
this._initializeItemsIfNeeded();
this._filter = this._search;
} else {
if (this._focusSet && this._focusListElement) {
this._focusSet = false;
// @ts-ignore
this._focusListElement.rippleHandlers.endFocus();
}
this._debouncedSetFilter(this._search);
}
}
@@ -366,12 +374,14 @@ export class QuickBar extends LitElement {
private _setFocusFirstListItem() {
// @ts-ignore
this._getItemAtIndex(0)?.rippleHandlers.startFocus();
this._focusListElement = this._getItemAtIndex(0);
}
private _handleListItemKeyDown(ev: KeyboardEvent) {
const isSingleCharacter = ev.key.length === 1;
const isFirstListItem =
(ev.target as HTMLElement).getAttribute("index") === "0";
this._focusListElement = ev.target as ListItem;
if (ev.key === "ArrowUp") {
if (isFirstListItem) {
this._filterInputField?.focus();
@@ -511,7 +521,13 @@ export class QuickBar extends LitElement {
if (page.component) {
const info = this._getNavigationInfoFromConfig(page);
if (info) {
// Add to list, but only if we do not already have an entry for the same path and component
if (
info &&
!items.some(
(e) => e.path === info.path && e.component === info.component
)
) {
items.push(info);
}
}

View File

@@ -37,6 +37,25 @@ declare global {
}
}
const clearUrlParams = () => {
// Clear auth data from url if we have been able to establish a connection
if (location.search.includes("auth_callback=1")) {
const searchParams = new URLSearchParams(location.search);
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
// Remove all data from QueryCallbackData type
searchParams.delete("auth_callback");
searchParams.delete("code");
searchParams.delete("state");
searchParams.delete("storeToken");
const search = searchParams.toString();
history.replaceState(
null,
"",
`${location.pathname}${search ? `?${search}` : ""}`
);
}
};
const authProm = isExternal
? () =>
import("../external_app/external_auth").then(({ createExternalAuth }) =>
@@ -52,23 +71,7 @@ const authProm = isExternal
const connProm = async (auth) => {
try {
const conn = await createConnection({ auth });
// Clear auth data from url if we have been able to establish a connection
if (location.search.includes("auth_callback=1")) {
const searchParams = new URLSearchParams(location.search);
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
// Remove all data from QueryCallbackData type
searchParams.delete("auth_callback");
searchParams.delete("code");
searchParams.delete("state");
searchParams.delete("storeToken");
const search = searchParams.toString();
history.replaceState(
null,
"",
`${location.pathname}${search ? `?${search}` : ""}`
);
}
clearUrlParams();
return { auth, conn };
} catch (err: any) {
if (err !== ERR_INVALID_AUTH) {
@@ -85,6 +88,7 @@ const connProm = async (auth) => {
}
auth = await authProm();
const conn = await createConnection({ auth });
clearUrlParams();
return { auth, conn };
}
};

View File

@@ -2,7 +2,7 @@
* Auth class that connects to a native app for authentication.
*/
import { Auth } from "home-assistant-js-websocket";
import { ExternalMessaging, InternalMessage } from "./external_messaging";
import { ExternalMessaging, EMMessage } from "./external_messaging";
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
@@ -36,7 +36,7 @@ declare global {
postMessage(payload: BasePayload);
};
externalBus: {
postMessage(payload: InternalMessage);
postMessage(payload: EMMessage);
};
};
};

View File

@@ -1,3 +1,4 @@
import { Connection } from "home-assistant-js-websocket";
import {
externalForwardConnectionEvents,
externalForwardHaptics,
@@ -7,39 +8,50 @@ const CALLBACK_EXTERNAL_BUS = "externalBus";
interface CommandInFlight {
resolve: (data: any) => void;
reject: (err: ExternalError) => void;
reject: (err: EMError) => void;
}
export interface InternalMessage {
export interface EMMessage {
id?: number;
type: string;
payload?: unknown;
}
interface ExternalError {
interface EMError {
code: string;
message: string;
}
interface ExternalMessageResult {
interface EMMessageResultSuccess {
id: number;
type: "result";
success: true;
result: unknown;
}
interface ExternalMessageResultError {
interface EMMessageResultError {
id: number;
type: "result";
success: false;
error: ExternalError;
error: EMError;
}
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError;
interface EMExternalMessageRestart {
id: number;
type: "command";
command: "restart";
}
type ExternalMessage =
| EMMessageResultSuccess
| EMMessageResultError
| EMExternalMessageRestart;
export class ExternalMessaging {
public commands: { [msgId: number]: CommandInFlight } = {};
public connection?: Connection;
public cache: Record<string, any> = {};
public msgId = 0;
@@ -54,7 +66,7 @@ export class ExternalMessaging {
* Send message to external app that expects a response.
* @param msg message to send
*/
public sendMessage<T>(msg: InternalMessage): Promise<T> {
public sendMessage<T>(msg: EMMessage): Promise<T> {
const msgId = ++this.msgId;
msg.id = msgId;
@@ -69,7 +81,9 @@ export class ExternalMessaging {
* Send message to external app without expecting a response.
* @param msg message to send
*/
public fireMessage(msg: InternalMessage) {
public fireMessage(
msg: EMMessage | EMMessageResultSuccess | EMMessageResultError
) {
if (!msg.id) {
msg.id = ++this.msgId;
}
@@ -82,6 +96,43 @@ export class ExternalMessaging {
console.log("Receiving message from external app", msg);
}
if (msg.type === "command") {
if (!this.connection) {
// eslint-disable-next-line no-console
console.warn("Received command without having connection set", msg);
this.fireMessage({
id: msg.id,
type: "result",
success: false,
error: {
code: "commands_not_init",
message: `Commands connection not set`,
},
});
} else if (msg.command === "restart") {
this.connection.reconnect(true);
this.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else {
// eslint-disable-next-line no-console
console.warn("Received unknown command", msg.command, msg);
this.fireMessage({
id: msg.id,
type: "result",
success: false,
error: {
code: "unknown_command",
message: `Unknown command ${msg.command}`,
},
});
}
return;
}
const pendingCmd = this.commands[msg.id];
if (!pendingCmd) {
@@ -99,7 +150,7 @@ export class ExternalMessaging {
}
}
protected _sendExternal(msg: InternalMessage) {
protected _sendExternal(msg: EMMessage) {
if (__DEV__) {
// eslint-disable-next-line no-console
console.log("Sending message to external app", msg);

View File

@@ -271,7 +271,10 @@ export const provideHass = (
updateStates,
updateTranslations,
addTranslations,
loadFragmentTranslation: async (_fragment: string) => hass().localize,
loadFragmentTranslation: async (fragment: string) => {
await updateTranslations(fragment);
return hass().localize;
},
addEntities,
mockWS(type, callback) {
wsCommands[type] = callback;

View File

@@ -127,7 +127,9 @@ export class HassRouterPage extends ReactiveElement {
// Update the url if we know where we're mounted.
if (route) {
navigate(`${route.prefix}/${result}`, { replace: true });
navigate(`${route.prefix}/${result}${location.search}`, {
replace: true,
});
}
}
}

View File

@@ -34,8 +34,6 @@ const panelUrl = (path: string) => {
export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
@state() private _route: Route;
@state() private _error = false;
private _panelUrl: string;
private _haVersion?: string;
@@ -44,14 +42,14 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
private _visiblePromiseResolve?: () => void;
private _visibleLaunchScreen = true;
constructor() {
super();
const path = curPath();
if (["", "/"].includes(path)) {
navigate(`/${getStorageDefaultPanelUrlPath()}`, { replace: true });
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
replace: true,
});
}
this._route = {
prefix: "",
@@ -60,27 +58,22 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
this._panelUrl = panelUrl(path);
}
protected render() {
if (this._isHassComplete() && this.hass) {
return html`
<home-assistant-main
.hass=${this.hass}
.route=${this._route}
></home-assistant-main>
`;
}
return "";
protected renderHass() {
return html`
<home-assistant-main
.hass=${this.hass}
.route=${this._route}
></home-assistant-main>
`;
}
update(changedProps) {
super.update(changedProps);
// Remove launch screen if main gui is loaded
if (this._isHassComplete() && this._visibleLaunchScreen) {
this._visibleLaunchScreen = false;
if (this.hass?.states && this.hass.config && this.hass.services) {
this.render = this.renderHass;
this.update = super.update;
removeLaunchScreen();
}
super.update(changedProps);
}
protected firstUpdated(changedProps) {
@@ -127,10 +120,9 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
});
// Render launch screen info box (loading data / error message)
if (!this._isHassComplete() && this._visibleLaunchScreen) {
renderLaunchScreenInfoBox(
html`<ha-init-page .error=${this._error}></ha-init-page>`
);
// if Home Assistant is not loaded yet.
if (this.render !== this.renderHass) {
this._renderInitInfo(false);
}
}
@@ -186,7 +178,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
if (window.hassConnection) {
result = await window.hassConnection;
} else {
// In the edge case that
// In the edge case that core.ts loads before app.ts
result = await new Promise((resolve) => {
window.hassConnectionReady = resolve;
});
@@ -196,7 +188,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
this._haVersion = conn.haVersion;
this.initializeHass(auth, conn);
} catch (err: any) {
this._error = true;
this._renderInitInfo(true);
}
}
@@ -253,12 +245,10 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
}
}
private _isHassComplete(): boolean {
if (this.hass?.states && this.hass.config && this.hass.services) {
return true;
}
return false;
private _renderInitInfo(error: boolean) {
renderLaunchScreenInfoBox(
html`<ha-init-page .error=${error}></ha-init-page>`
);
}
}

View File

@@ -95,8 +95,11 @@ class OnboardingCreateUser extends LitElement {
private _handleValueChanged(
ev: PolymerChangedEvent<HaFormDataContainer>
): void {
const nameChanged = ev.detail.value.name !== this._newUser.name;
this._newUser = ev.detail.value;
this._maybePopulateUsername();
if (nameChanged) {
this._maybePopulateUsername();
}
this._formError.password_confirm =
this._newUser.password !== this._newUser.password_confirm
? this.localize(

View File

@@ -138,7 +138,8 @@ export default class HaAutomationConditionEditor extends LitElement {
if (!ev.detail.isValid) {
return;
}
fireEvent(this, "value-changed", { value: ev.detail.value });
// @ts-ignore
fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true });
}
static get styles(): CSSResultGroup {

View File

@@ -109,6 +109,7 @@ export default class HaAutomationConditionRow extends LitElement {
: ""}
<ha-automation-condition-editor
@ui-mode-not-available=${this._handleUiModeNotAvailable}
@value-changed=${this._handleChangeEvent}
.yamlMode=${this._yamlMode}
.hass=${this.hass}
.condition=${this.condition}
@@ -127,6 +128,12 @@ export default class HaAutomationConditionRow extends LitElement {
}
}
private _handleChangeEvent(ev: CustomEvent) {
if (ev.detail.yaml) {
this._warnings = undefined;
}
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:

View File

@@ -1,17 +1,27 @@
import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { assert, literal, object, optional, string, union } from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input";
import { StateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { forDictStruct } from "../../structs";
import {
ConditionElement,
handleChangeEvent,
} from "../ha-automation-condition-row";
import "../../../../../components/ha-duration-input";
import { fireEvent } from "../../../../../common/dom/fire_event";
const stateConditionStruct = object({
condition: literal("state"),
entity_id: optional(string()),
attribute: optional(string()),
state: optional(string()),
for: optional(union([string(), forDictStruct])),
});
@customElement("ha-automation-condition-state")
export class HaStateCondition extends LitElement implements ConditionElement {
@@ -23,19 +33,14 @@ export class HaStateCondition extends LitElement implements ConditionElement {
return { entity_id: "", state: "" };
}
public willUpdate(changedProperties: PropertyValues): boolean {
if (
changedProperties.has("condition") &&
Array.isArray(this.condition?.state)
) {
fireEvent(
this,
"ui-mode-not-available",
Error(this.hass.localize("ui.errors.config.no_state_array_support"))
);
// We have to stop the update if state is an array.
// Otherwise the state will be changed to a comma-separated string by the input element.
return false;
public shouldUpdate(changedProperties: PropertyValues) {
if (changedProperties.has("condition")) {
try {
assert(this.condition, stateConditionStruct);
} catch (e: any) {
fireEvent(this, "ui-mode-not-available", e);
return false;
}
}
return true;
}

View File

@@ -0,0 +1,13 @@
import { object, optional, number, string } from "superstruct";
export const baseTriggerStruct = object({
platform: string(),
id: optional(string()),
});
export const forDictStruct = object({
days: optional(number()),
hours: optional(number()),
minutes: optional(number()),
seconds: optional(number()),
});

View File

@@ -68,7 +68,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
}
let newTrigger: Trigger;
if (!newVal) {
if (newVal === undefined || newVal === "") {
newTrigger = { ...element.trigger };
delete newTrigger[name];
} else {
@@ -291,6 +291,7 @@ export default class HaAutomationTriggerRow extends LitElement {
if (!ev.detail.isValid) {
return;
}
this._warnings = undefined;
fireEvent(this, "value-changed", { value: ev.detail.value });
}

View File

@@ -1,19 +1,41 @@
import "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import {
assert,
assign,
literal,
object,
optional,
string,
union,
} from "superstruct";
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { hasTemplate } from "../../../../../common/string/has-template";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-duration-input";
import { StateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-duration-input";
import { baseTriggerStruct, forDictStruct } from "../../structs";
import {
handleChangeEvent,
TriggerElement,
} from "../ha-automation-trigger-row";
const stateTriggerStruct = assign(
baseTriggerStruct,
object({
platform: literal("state"),
entity_id: optional(string()),
attribute: optional(string()),
from: optional(string()),
to: optional(string()),
for: optional(union([string(), forDictStruct])),
})
);
@customElement("ha-automation-trigger-state")
export class HaStateTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -24,9 +46,16 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
return { entity_id: "" };
}
public willUpdate(changedProperties: PropertyValues) {
public shouldUpdate(changedProperties: PropertyValues) {
if (!changedProperties.has("trigger")) {
return;
return true;
}
if (
this.trigger.for &&
typeof this.trigger.for === "object" &&
this.trigger.for.milliseconds === 0
) {
delete this.trigger.for.milliseconds;
}
// Check for templates in trigger. If found, revert to YAML mode.
if (this.trigger && hasTemplate(this.trigger)) {
@@ -35,7 +64,15 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
"ui-mode-not-available",
Error(this.hass.localize("ui.errors.config.no_template_editor_support"))
);
return false;
}
try {
assert(this.trigger, stateTriggerStruct);
} catch (e: any) {
fireEvent(this, "ui-mode-not-available", e);
return false;
}
return true;
}
protected render() {

View File

@@ -224,7 +224,7 @@ class HaBlueprintOverview extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automations}
.tabs=${configSections.blueprints}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._processedBlueprints(this.blueprints)}
id="entity_id"

View File

@@ -1,4 +1,4 @@
import { mdiCellphoneCog, mdiCloudLock } from "@mdi/js";
import { mdiCloudLock } from "@mdi/js";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
@@ -110,29 +110,10 @@ class HaConfigDashboard extends LitElement {
></ha-config-navigation>
`
: ""}
${this._externalConfig?.hasSettingsScreen
? html`
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.showAdvanced=${this.showAdvanced}
.pages=${[
{
path: "#external-app-configuration",
name: "Companion App",
description: "Location and notifications",
iconPath: mdiCellphoneCog,
iconColor: "#37474F",
core: true,
},
]}
@click=${this._handleExternalAppConfiguration}
></ha-config-navigation>
`
: ""}
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.externalConfig=${this._externalConfig}
.showAdvanced=${this.showAdvanced}
.pages=${configSections.dashboard}
></ha-config-navigation>
@@ -142,13 +123,6 @@ class HaConfigDashboard extends LitElement {
`;
}
private _handleExternalAppConfiguration(ev: Event) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -157,7 +131,7 @@ class HaConfigDashboard extends LitElement {
border-bottom: var(--app-header-border-bottom);
--header-height: 55px;
}
ha-card:last-child {
:host(:not([narrow])) ha-card:last-child {
margin-bottom: 24px;
}
ha-config-section {
@@ -178,7 +152,7 @@ class HaConfigDashboard extends LitElement {
padding-bottom: 0;
}
:host([narrow]) ha-card {
background-color: var(--primary-background-color);
border-radius: 0;
box-shadow: unset;
}

View File

@@ -6,6 +6,7 @@ import { canShowPage } from "../../../common/config/can_show_page";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import { ExternalConfig } from "../../../external_app/external_config";
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../../types";
@@ -19,12 +20,18 @@ class HaConfigNavigation extends LitElement {
@property() public pages!: PageNavigation[];
@property() public externalConfig?: ExternalConfig;
protected render(): TemplateResult {
return html`
${this.pages.map((page) =>
canShowPage(this.hass, page)
(
page.path === "#external-app-configuration"
? this.externalConfig?.hasSettingsScreen
: canShowPage(this.hass, page)
)
? html`
<a href=${page.path} aria-role="option" tabindex="-1">
<a href=${page.path} role="option" tabindex="-1">
<paper-icon-item @click=${this._entryClicked}>
<div
class=${page.iconColor ? "icon-background" : ""}
@@ -36,8 +43,7 @@ class HaConfigNavigation extends LitElement {
<paper-item-body two-line>
${page.name ||
this.hass.localize(
page.translationKey ||
`ui.panel.config.${page.component}.caption`
`ui.panel.config.dashboard.${page.translationKey}.title`
)}
${page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
@@ -61,7 +67,7 @@ class HaConfigNavigation extends LitElement {
<div secondary>
${page.description ||
this.hass.localize(
`ui.panel.config.${page.component}.description`
`ui.panel.config.dashboard.${page.translationKey}.description`
)}
</div>
`}
@@ -77,6 +83,16 @@ class HaConfigNavigation extends LitElement {
private _entryClicked(ev) {
ev.currentTarget.blur();
if (
ev.currentTarget.parentElement.href.endsWith(
"#external-app-configuration"
)
) {
ev.preventDefault();
this.hass.auth.external!.fireMessage({
type: "config_screen/show",
});
}
}
static get styles(): CSSResultGroup {

View File

@@ -66,6 +66,17 @@ export class HaDeviceCard extends LitElement {
</div>
`
: ""}
${this.device.hw_version
? html`
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.hardware",
"version",
this.device.hw_version
)}
</div>
`
: ""}
<slot></slot>
</div>
<slot name="actions"></slot>
@@ -107,9 +118,9 @@ export class HaDeviceCard extends LitElement {
word-wrap: break-word;
}
.manuf,
.entity-id,
.model {
color: var(--secondary-text-color);
word-wrap: break-word;
}
`;
}

View File

@@ -1,6 +1,7 @@
import {
mdiAccount,
mdiBadgeAccountHorizontal,
mdiCellphoneCog,
mdiCog,
mdiDevices,
mdiHomeAssistant,
@@ -48,73 +49,69 @@ export const configSections: { [name: string]: PageNavigation[] } = {
dashboard: [
{
path: "/config/integrations",
name: "Devices & Services",
description: "Integrations, devices, entities and areas",
translationKey: "devices",
iconPath: mdiDevices,
iconColor: "#0D47A1",
core: true,
},
{
path: "/config/automation",
name: "Automations & Scenes",
description: "Automations, blueprints, scenes and scripts",
translationKey: "automations",
iconPath: mdiRobot,
iconColor: "#518C43",
components: ["automation", "blueprint", "scene", "script"],
},
{
path: "/config/helpers",
name: "Automation Helpers",
description: "Elements that help build automations",
iconPath: mdiTools,
iconColor: "#4D2EA4",
core: true,
},
{
path: "/config/blueprint",
translationKey: "blueprints",
iconPath: mdiPaletteSwatch,
iconColor: "#64B5F6",
component: "blueprint",
},
{
path: "/hassio",
name: "Add-ons & Backups (Supervisor)",
description: "Create backups, check logs or reboot your system",
translationKey: "supervisor",
iconPath: mdiHomeAssistant,
iconColor: "#4084CD",
component: "hassio",
},
{
path: "/config/lovelace/dashboards",
name: "Dashboards",
description: "Create customized sets of cards to control your home",
translationKey: "dashboards",
iconPath: mdiViewDashboard,
iconColor: "#B1345C",
component: "lovelace",
},
{
path: "/config/energy",
name: "Energy",
description: "Monitor your energy production and consumption",
translationKey: "energy",
iconPath: mdiLightningBolt,
iconColor: "#F1C447",
component: "energy",
},
{
path: "/config/tags",
name: "Tags",
description:
"Trigger automations when a NFC tag, QR code, etc. is scanned",
translationKey: "tags",
iconPath: mdiNfcVariant,
iconColor: "#616161",
component: "tag",
},
{
path: "/config/person",
name: "People & Zones",
description: "Manage the people and zones that Home Assistant tracks",
translationKey: "people",
iconPath: mdiAccount,
iconColor: "#E48629",
components: ["person", "zone", "users"],
},
{
path: "/config/core",
name: "Settings",
description: "Basic settings, server controls, logs and info",
path: "#external-app-configuration",
translationKey: "companion",
iconPath: mdiCellphoneCog,
iconColor: "#8E24AA",
},
{
path: "/config/server_control",
translationKey: "settings",
iconPath: mdiCog,
iconColor: "#4A5963",
core: true,
@@ -155,13 +152,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
},
],
automations: [
{
component: "blueprint",
path: "/config/blueprint",
translationKey: "ui.panel.config.blueprint.caption",
iconPath: mdiPaletteSwatch,
iconColor: "#518C43",
},
{
component: "automation",
path: "/config/automation",
@@ -183,8 +173,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconPath: mdiScriptText,
iconColor: "#518C43",
},
],
helpers: [
{
component: "helpers",
path: "/config/helpers",
@@ -194,6 +182,15 @@ export const configSections: { [name: string]: PageNavigation[] } = {
core: true,
},
],
blueprints: [
{
component: "blueprint",
path: "/config/blueprint",
translationKey: "ui.panel.config.blueprint.caption",
iconPath: mdiPaletteSwatch,
iconColor: "#518C43",
},
],
tags: [
{
component: "tag",
@@ -447,9 +444,19 @@ class HaPanelConfig extends HassRouterPage {
this.hass.loadBackendTranslation("title");
if (isComponentLoaded(this.hass, "cloud")) {
this._updateCloudStatus();
this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._updateCloudStatus();
}
});
}
if (isComponentLoaded(this.hass, "hassio")) {
this._loadSupervisorUpdates();
this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._loadSupervisorUpdates();
}
});
} else {
this._supervisorUpdates = null;
}

View File

@@ -132,7 +132,7 @@ export class HaConfigHelpers extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.helpers}
.tabs=${configSections.automations}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._stateItems)}
@row-click=${this._openEditDialog}

View File

@@ -1,6 +1,7 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-logo-svg";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
@@ -40,13 +41,14 @@ class HaConfigInfo extends LitElement {
href=${documentationUrl(this.hass, "")}
target="_blank"
rel="noreferrer"
><img
src="/static/icons/favicon-192x192.png"
height="192"
alt=${this.hass.localize(
>
<ha-logo-svg
title=${this.hass.localize(
"ui.panel.config.info.home_assistant_logo"
)}
/></a>
>
</ha-logo-svg>
</a>
<br />
<h2>Home Assistant ${hass.connection.haVersion}</h2>
<p>
@@ -193,6 +195,11 @@ class HaConfigInfo extends LitElement {
margin: 0 auto;
padding-bottom: 16px;
}
ha-logo-svg {
padding: 12px;
height: 180px;
width: 180px;
}
`,
];
}

View File

@@ -95,7 +95,7 @@ class OZWConfigDashboard extends LitElement {
<ha-card>
<a
href="/config/ozw/network/${instance.ozw_instance}"
aria-role="option"
role="option"
tabindex="-1"
>
<paper-icon-item>

View File

@@ -218,9 +218,11 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) {
.device .manuf {
color: var(--secondary-text-color);
margin-bottom: 20px;
word-wrap: break-word;
}
.extra-info {
margin-top: 8px;
word-wrap: break-word;
}
state-badge {
cursor: pointer;

View File

@@ -129,7 +129,11 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
<span
>[[localize('ui.panel.config.zwave.node_management.header')]]</span
>
<ha-icon-button class="toggle-help-icon" on-click="toggleHelp">
<ha-icon-button
class="toggle-help-icon"
on-click="toggleHelp"
label="[[localize('ui.common.help')]]"
>
<ha-icon icon="hass:help-circle"></ha-icon>
</ha-icon-button>
</div>

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button/mwc-button";
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
import "@material/mwc-textfield/mwc-textfield";
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
import "@polymer/paper-input/paper-input";
@@ -45,6 +44,8 @@ export interface ZWaveJSAddNodeDevice {
class DialogZWaveJSAddNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: ZWaveJSAddNodeDialogParams;
@state() private _entryId?: string;
@state() private _status?:
@@ -91,6 +92,7 @@ class DialogZWaveJSAddNode extends LitElement {
}
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
this._params = params;
this._entryId = params.entry_id;
this._status = "loading";
this._checkSmartStartSupport();
@@ -176,21 +178,16 @@ class DialogZWaveJSAddNode extends LitElement {
Search device
</mwc-button>`
: this._status === "qr_scan"
? html`<ha-qr-scanner
? html`${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-qr-scanner
.localize=${this.hass.localize}
@qr-code-scanned=${this._qrCodeScanned}
></ha-qr-scanner>
<p>
If scanning doesn't work, you can enter the QR code value
manually:
</p>
<mwc-textfield
.label=${this.hass.localize(
"ui.panel.config.zwave_js.add_node.enter_qr_code"
)}
.disabled=${this._qrProcessing}
@keydown=${this._qrKeyDown}
></mwc-textfield>`
<mwc-button slot="secondaryAction" @click=${this._startOver}>
${this.hass.localize("ui.panel.config.zwave_js.common.back")}
</mwc-button>`
: this._status === "validate_dsk_enter_pin"
? html`
<p>
@@ -200,9 +197,9 @@ class DialogZWaveJSAddNode extends LitElement {
</p>
${
this._error
? html`<ha-alert alert-type="error"
>${this._error}</ha-alert
>`
? html`<ha-alert alert-type="error">
${this._error}
</ha-alert>`
: ""
}
<div class="flex-container">
@@ -271,7 +268,7 @@ class DialogZWaveJSAddNode extends LitElement {
We have not found any device in inclusion mode. Make sure the
device is active and in inclusion mode.
</p>
<mwc-button slot="primaryAction" @click=${this._startInclusion}>
<mwc-button slot="primaryAction" @click=${this._startOver}>
Retry
</mwc-button>
`
@@ -370,7 +367,7 @@ class DialogZWaveJSAddNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: this._status === "failed"
@@ -507,15 +504,6 @@ class DialogZWaveJSAddNode extends LitElement {
this._status = "qr_scan";
}
private _qrKeyDown(ev: KeyboardEvent) {
if (this._qrProcessing) {
return;
}
if (ev.key === "Enter") {
this._handleQrCodeScanned((ev.target as TextField).value);
}
}
private _qrCodeScanned(ev: CustomEvent): void {
if (this._qrProcessing) {
return;
@@ -562,17 +550,16 @@ class DialogZWaveJSAddNode extends LitElement {
provisioningInfo
);
this._status = "provisioned";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
} catch (err: any) {
this._error = err.message;
this._status = "failed";
}
} else if (provisioningInfo.version === 0) {
this._inclusionStrategy = InclusionStrategy.Security_S2;
// this._startInclusion(provisioningInfo);
this._startInclusion(undefined, undefined, {
dsk: "34673-15546-46480-39591-32400-22155-07715-45994",
security_classes: [0, 1, 7],
});
this._startInclusion(provisioningInfo);
} else {
this._error = "This QR code is not supported";
this._status = "failed";
@@ -632,6 +619,10 @@ class DialogZWaveJSAddNode extends LitElement {
).supported;
}
private _startOver(_ev: Event) {
this._startInclusion();
}
private _startInclusion(
qrProvisioningInformation?: QRProvisioningInformation,
qrCodeString?: string,
@@ -693,6 +684,9 @@ class DialogZWaveJSAddNode extends LitElement {
if (message.event === "interview completed") {
this._unsubscribe();
this._status = "finished";
if (this._params?.addedCallback) {
this._params.addedCallback();
}
}
if (message.event === "interview stage completed") {

View File

@@ -2,6 +2,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDialogParams {
entry_id: string;
addedCallback?: () => void;
}
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");

View File

@@ -411,6 +411,7 @@ class ZWaveJSConfigDashboard extends LitElement {
private async _addNodeClicked() {
showZWaveJSAddNodeDialog(this, {
entry_id: this.configEntryId!,
addedCallback: () => this._fetchData(),
});
}

View File

@@ -327,6 +327,9 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
if (!("states" in item.metadata)) {
return false;
}
if (Object.keys(item.metadata.states).length !== 2) {
return false;
}
if (!(0 in item.metadata.states) || !(1 in item.metadata.states)) {
return false;
}

View File

@@ -1,4 +1,4 @@
import { mdiDelete } from "@mdi/js";
import { mdiCheckCircle, mdiCloseCircleOutline, mdiDelete } from "@mdi/js";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -42,17 +42,42 @@ class ZWaveJSProvisioned extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
included: {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included"
),
type: "icon",
width: "100px",
template: (_info, provisioningEntry: any) =>
provisioningEntry.additional_properties.nodeId
? html`
<ha-svg-icon
.label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.included"
)}
.path=${mdiCheckCircle}
></ha-svg-icon>
`
: html`
<ha-svg-icon
.label=${this.hass.localize(
"ui.panel.config.zwave_js.provisioned.not_included"
)}
.path=${mdiCloseCircleOutline}
></ha-svg-icon>
`,
},
dsk: {
title: this.hass.localize("ui.panel.config.zwave_js.provisioned.dsk"),
sortable: true,
filterable: true,
grows: true,
},
securityClasses: {
security_classes: {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.security_classes"
),
width: "15%",
width: "30%",
hidden: narrow,
filterable: true,
sortable: true,
@@ -60,7 +85,7 @@ class ZWaveJSProvisioned extends LitElement {
securityClasses
.map((secClass) =>
this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}`
`ui.panel.config.zwave_js.security_classes.${SecurityClass[secClass]}.title`
)
)
.join(", "),
@@ -70,6 +95,7 @@ class ZWaveJSProvisioned extends LitElement {
"ui.panel.config.zwave_js.provisioned.unprovison"
),
type: "icon-button",
width: "100px",
template: (_info, provisioningEntry: any) => html`
<ha-icon-button
.label=${this.hass.localize(
@@ -97,6 +123,8 @@ class ZWaveJSProvisioned extends LitElement {
}
private _unprovision = async (ev) => {
const dsk = ev.currentTarget.provisioningEntry.dsk;
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.zwave_js.provisioned.confirm_unprovision_title"
@@ -113,11 +141,8 @@ class ZWaveJSProvisioned extends LitElement {
return;
}
await unprovisionZwaveSmartStartNode(
this.hass,
this.configEntryId,
ev.currentTarget.provisioningEntry.dsk
);
await unprovisionZwaveSmartStartNode(this.hass, this.configEntryId, dsk);
this._fetchData();
};
}

View File

@@ -157,18 +157,6 @@ export class HaConfigServerControl extends LitElement {
"ui.panel.config.server_control.section.server_management.restart"
)}
</ha-call-service-button>
<ha-call-service-button
class="warning"
.hass=${this.hass}
domain="homeassistant"
service="stop"
confirmation=${this.hass.localize(
"ui.panel.config.server_control.section.server_management.confirm_stop"
)}
>${this.hass.localize(
"ui.panel.config.server_control.section.server_management.stop"
)}
</ha-call-service-button>
</div>
</ha-card>

View File

@@ -106,6 +106,7 @@ class DialogUserDetail extends LitElement {
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated}
.checked=${this._localOnly}
@change=${this._localOnlyChanged}
>

View File

@@ -18,6 +18,7 @@ import "../../../components/ha-code-editor";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-checkbox";
import "../../../components/ha-expansion-panel";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
@@ -40,6 +41,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
padding: 16px;
}
ha-expansion-panel {
margin: 0 8px 16px;
}
.inputs {
width: 100%;
max-width: 400px;
@@ -135,72 +140,77 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
padding: 0;
}
</style>
<p>
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
</p>
<div class="state-wrapper flex layout horizontal">
<div class="inputs">
<ha-entity-picker
autofocus
hass="[[hass]]"
value="{{_entityId}}"
on-change="entityIdChanged"
allow-custom-entity
></ha-entity-picker>
<paper-input
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
required
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
value="{{_state}}"
class="state-input"
></paper-input>
<p>
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
</p>
<ha-code-editor
mode="yaml"
value="[[_stateAttributes]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<div class="button-row">
<mwc-button
on-click="handleSetState"
disabled="[[!validJSON]]"
raised
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
>
<ha-icon-button
on-click="entityIdChanged"
label="[[localize('ui.common.refresh')]]"
path="[[refreshIcon()]]"
></ha-icon-button>
</div>
</div>
<div class="info">
<template is="dom-if" if="[[_entity]]">
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
><br />[[lastChangedString(_entity)]]
</p>
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
><br />[[lastUpdatedString(_entity)]]
</p>
</template>
</div>
</div>
<h1>
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
</h1>
<ha-expansion-panel
header="Set state"
outlined
expanded="[[_expanded]]"
on-expanded-changed="expandedChanged"
>
<p>
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
</p>
<div class="state-wrapper flex layout horizontal">
<div class="inputs">
<ha-entity-picker
autofocus
hass="[[hass]]"
value="{{_entityId}}"
on-change="entityIdChanged"
allow-custom-entity
></ha-entity-picker>
<paper-input
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
required
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
value="{{_state}}"
class="state-input"
></paper-input>
<p>
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
</p>
<ha-code-editor
mode="yaml"
value="[[_stateAttributes]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<div class="button-row">
<mwc-button
on-click="handleSetState"
disabled="[[!validJSON]]"
raised
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
>
<ha-icon-button
on-click="entityIdChanged"
label="[[localize('ui.common.refresh')]]"
path="[[refreshIcon()]]"
></ha-icon-button>
</div>
</div>
<div class="info">
<template is="dom-if" if="[[_entity]]">
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
><br />[[lastChangedString(_entity)]]
</p>
<p>
<b
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
><br />[[lastUpdatedString(_entity)]]
</p>
</template>
</div>
</div>
</ha-expansion-panel>
<div class="table-wrapper">
<table class="entities">
<tr>
@@ -348,6 +358,11 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
"computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter)",
},
_expanded: {
type: Boolean,
value: false,
},
narrow: {
type: Boolean,
reflectToAttribute: true,
@@ -371,6 +386,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
this._entity = state;
this._state = state.state;
this._stateAttributes = dump(state.attributes);
this._expanded = true;
ev.preventDefault();
}
@@ -388,6 +404,11 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
this._entity = state;
this._state = state.state;
this._stateAttributes = dump(state.attributes);
this._expanded = true;
}
expandedChanged(ev) {
this._expanded = ev.detail.expanded;
}
entityMoreInfo(ev) {

View File

@@ -26,7 +26,7 @@ import {
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
import { labDarken } from "../../../../common/color/lab";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import {
EnergyData,
getEnergyDataCollection,
@@ -247,10 +247,15 @@ export class HuiEnergyGasGraphCard
const data: ChartDataset<"bar" | "line">[] = [];
const entity = this.hass.states[source.stat_energy_from];
const borderColor =
const modifiedColor =
idx > 0
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx)))
: gasColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(gasColor)), idx)
: labDarken(rgb2lab(hex2rgb(gasColor)), idx)
: undefined;
const borderColor = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: gasColor;
let prevValue: number | null = null;
let prevStart: string | null = null;

View File

@@ -1,9 +1,3 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { classMap } from "lit/directives/class-map";
import "../../../../components/ha-card";
import {
ChartData,
ChartDataset,
@@ -17,16 +11,26 @@ import {
isToday,
startOfToday,
} from "date-fns";
import { HomeAssistant } from "../../../../types";
import { LovelaceCard } from "../../types";
import { EnergySolarGraphCardConfig } from "../types";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import {
hex2rgb,
lab2rgb,
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
import { labDarken } from "../../../../common/color/lab";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { formatTime } from "../../../../common/datetime/format_time";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import {
formatNumber,
numberFormatToLocale,
} from "../../../../common/number/format_number";
import "../../../../components/chart/ha-chart-base";
import "../../../../components/ha-card";
import {
EnergyData,
EnergySolarForecasts,
@@ -34,15 +38,11 @@ import {
getEnergySolarForecasts,
SolarSourceTypeEnergyPreference,
} from "../../../../data/energy";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import "../../../../components/chart/ha-chart-base";
import {
formatNumber,
numberFormatToLocale,
} from "../../../../common/number/format_number";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { FrontendLocaleData } from "../../../../data/translation";
import { formatTime } from "../../../../common/datetime/format_time";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../../types";
import { LovelaceCard } from "../../types";
import { EnergySolarGraphCardConfig } from "../types";
@customElement("hui-energy-solar-graph-card")
export class HuiEnergySolarGraphCard
@@ -258,10 +258,15 @@ export class HuiEnergySolarGraphCard
const data: ChartDataset<"bar" | "line">[] = [];
const entity = this.hass.states[source.stat_energy_from];
const borderColor =
const modifiedColor =
idx > 0
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
: solarColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(solarColor)), idx)
: labDarken(rgb2lab(hex2rgb(solarColor)), idx)
: undefined;
const borderColor = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: solarColor;
let prevValue: number | null = null;
let prevStart: string | null = null;

View File

@@ -17,7 +17,7 @@ import {
rgb2lab,
hex2rgb,
} from "../../../../common/color/convert-color";
import { labDarken } from "../../../../common/color/lab";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/chart/statistics-chart";
@@ -170,12 +170,17 @@ export class HuiEnergySourcesTableCard
this._data!.stats[source.stat_energy_from]
) || 0;
totalSolar += energy;
const color =
const modifiedColor =
idx > 0
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))
)
: solarColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(solarColor)), idx)
: labDarken(rgb2lab(hex2rgb(solarColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: solarColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -229,22 +234,26 @@ export class HuiEnergySourcesTableCard
this._data!.stats[source.stat_energy_to]
) || 0;
totalBattery += energyFrom - energyTo;
const fromColor =
const modifiedFromColor =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
)
)
: batteryFromColor;
const toColor =
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(batteryFromColor)), idx)
: labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
: undefined;
const fromColor = modifiedFromColor
? rgb2hex(lab2rgb(modifiedFromColor))
: batteryFromColor;
const modifiedToColor =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
)
)
: batteryToColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(batteryToColor)), idx)
: labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
: undefined;
const toColor = modifiedToColor
? rgb2hex(lab2rgb(modifiedToColor))
: batteryToColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -331,14 +340,17 @@ export class HuiEnergySourcesTableCard
if (cost !== null) {
totalGridCost += cost;
}
const color =
const modifiedColor =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
)
)
: consumptionColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(consumptionColor)), idx)
: labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: consumptionColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -391,12 +403,17 @@ export class HuiEnergySourcesTableCard
if (cost !== null) {
totalGridCost += cost;
}
const color =
const modifiedColor =
idx > 0
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx))
)
: returnColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(returnColor)), idx)
: labDarken(rgb2lab(hex2rgb(returnColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: returnColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
@@ -473,12 +490,17 @@ export class HuiEnergySourcesTableCard
if (cost !== null) {
totalGasCost += cost;
}
const color =
const modifiedColor =
idx > 0
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx))
)
: gasColor;
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(gasColor)), idx)
: labDarken(rgb2lab(hex2rgb(gasColor)), idx)
: undefined;
const color = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: gasColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div

View File

@@ -1,10 +1,10 @@
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
import {
startOfToday,
addHours,
differenceInDays,
endOfToday,
isToday,
differenceInDays,
addHours,
startOfToday,
} from "date-fns";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
@@ -17,7 +17,7 @@ import {
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
import { labDarken } from "../../../../common/color/lab";
import { labBrighten, labDarken } from "../../../../common/color/lab";
import { formatTime } from "../../../../common/datetime/format_time";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import {
@@ -477,10 +477,16 @@ export class HuiEnergyUsageGraphCard
Object.entries(sources).forEach(([statId, source], idx) => {
const data: ChartDataset<"bar">[] = [];
const entity = this.hass.states[statId];
const borderColor =
const modifiedColor =
idx > 0
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(colors[type])), idx)))
: colors[type];
? this.hass.themes.darkMode
? labBrighten(rgb2lab(hex2rgb(colors[type])), idx)
: labDarken(rgb2lab(hex2rgb(colors[type])), idx)
: undefined;
const borderColor = modifiedColor
? rgb2hex(lab2rgb(modifiedColor))
: colors[type];
data.push({
label:

View File

@@ -25,6 +25,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { domainIcon } from "../../../common/entity/domain_icon";
import { navigate } from "../../../common/navigate";
import { formatNumber } from "../../../common/number/format_number";
import { subscribeOne } from "../../../common/util/subscribe-one";
import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
@@ -83,8 +84,11 @@ export class HuiAreaCard
return document.createElement("hui-area-card-editor");
}
public static getStubConfig(): AreaCardConfig {
return { type: "area", area: "" };
public static async getStubConfig(
hass: HomeAssistant
): Promise<AreaCardConfig> {
const areas = await subscribeOne(hass.connection, subscribeAreaRegistry);
return { type: "area", area: areas[0]?.area_id || "" };
}
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -358,12 +362,12 @@ export class HuiAreaCard
});
let cameraEntityId: string | undefined;
if ("camera" in entitiesByDomain) {
if (this._config.show_camera && "camera" in entitiesByDomain) {
cameraEntityId = entitiesByDomain.camera[0].entity_id;
}
return html`
<ha-card class=${area.picture ? "image" : ""}>
<ha-card class=${area.picture || cameraEntityId ? "image" : ""}>
${area.picture || cameraEntityId
? html`<hui-image
.config=${this._config}

View File

@@ -163,16 +163,16 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
if (!this._config || !this._hass) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const oldHass = changedProps.get("_hass") as HomeAssistant | undefined;
const oldConfig = changedProps.get("_config") as
| EntitiesCardConfig
| undefined;
if (
!oldHass ||
!oldConfig ||
oldHass.themes !== this.hass.themes ||
oldConfig.theme !== this._config.theme
(changedProps.has("_hass") &&
(!oldHass || oldHass.themes !== this._hass.themes)) ||
(changedProps.has("_config") &&
(!oldConfig || oldConfig.theme !== this._config.theme))
) {
applyThemesOnElement(this, this._hass.themes, this._config.theme);
}

View File

@@ -235,6 +235,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
<div>
<ha-icon-button
.path=${mdiDotsVertical}
.label=${this.hass.localize(
"ui.panel.lovelace.cards.show_more_info"
)}
class="more-info"
@click=${this._handleMoreInfo}
></ha-icon-button>

View File

@@ -19,7 +19,7 @@ import {
svg,
TemplateResult,
} from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { UNIT_F } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
@@ -427,6 +427,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
@click=${this._handleAction}
tabindex="0"
.path=${modeIcons[mode]}
.label=${this.hass!.localize(`component.climate.state._.${mode}`)}
>
</ha-icon-button>
`;

View File

@@ -79,6 +79,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
export interface AreaCardConfig extends LovelaceCardConfig {
area: string;
navigation_path?: string;
show_camera?: boolean;
}
export interface ButtonCardConfig extends LovelaceCardConfig {

View File

@@ -50,6 +50,7 @@ export class HuiButtonsBase extends LitElement {
.stateObj=${stateObj}
.overrideIcon=${entityConf.icon}
.overrideImage=${entityConf.image}
class=${name ? "" : "no-text"}
stateColor
slot="icon"
></state-badge>
@@ -85,9 +86,21 @@ export class HuiButtonsBase extends LitElement {
flex-wrap: wrap;
}
state-badge {
display: inline-flex;
line-height: inherit;
text-align: start;
color: var(--secondary-text-color);
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-left: -4px;
margin-top: -2px;
}
state-badge.no-text {
width: 26px;
height: 26px;
margin-left: -3px;
margin-top: -3px;
}
ha-chip {
padding: 4px;

View File

@@ -33,6 +33,13 @@ class HuiGenericEntityRow extends LitElement {
@property({ type: Boolean }) public hideName = false;
// Allows to control if this row should capture the user interaction, e.g. with its
// toggle switch, button or input field. Some domains dynamically decide what to show
// => static determination will not work => the caller has to pass the desired value in.
// Same applies for custom components that want to override the default behavior.
// Default behavior is controlled by DOMAINS_INPUT_ROW.
@property({ type: Boolean }) public catchInteraction?;
protected render(): TemplateResult {
if (!this.hass || !this.config) {
return html``;
@@ -50,8 +57,11 @@ class HuiGenericEntityRow extends LitElement {
}
const domain = computeDomain(this.config.entity);
// By default, we always show a pointer, since if there is no explicit configuration provided,
// the frontend always assumes "more-info" in the action handler. We only need to hide the pointer
// if the tap action is explicitly set to "none".
const pointer = !(
this.config.tap_action && this.config.tap_action.action !== "none"
this.config.tap_action && this.config.tap_action.action === "none"
);
const hasSecondary = this.secondaryText || this.config.secondary_info;
@@ -144,7 +154,7 @@ class HuiGenericEntityRow extends LitElement {
: ""}
</div>`
: html``}
${!DOMAINS_INPUT_ROW.includes(domain)
${this.catchInteraction ?? !DOMAINS_INPUT_ROW.includes(domain)
? html` <div
class="text-content ${classMap({
pointer,

View File

@@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-area-picker";
import { HomeAssistant } from "../../../../types";
@@ -11,6 +11,8 @@ import { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/ha-formfield";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -18,6 +20,7 @@ const cardConfigStruct = assign(
area: optional(string()),
navigation_path: optional(string()),
theme: optional(string()),
show_camera: optional(boolean()),
})
);
@@ -47,6 +50,10 @@ export class HuiAreaCardEditor
return this._config!.theme || "";
}
get _show_camera(): boolean {
return this._config!.show_camera || false;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@@ -64,6 +71,18 @@ export class HuiAreaCardEditor
)}
@value-changed=${this._valueChanged}
></ha-area-picker>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.area.show_camera"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.checked=${this._show_camera}
.configValue=${"show_camera"}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
<paper-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.action-editor.navigation_path"
@@ -88,7 +107,8 @@ export class HuiAreaCardEditor
return;
}
const target = ev.target! as EditorTarget;
const value = ev.detail.value;
const value =
target.checked !== undefined ? target.checked : ev.detail.value;
if (this[`_${target.configValue}`] === value) {
return;

View File

@@ -13,7 +13,7 @@ export const getCardStubConfig = async (
const elClass = await getCardElementClass(type);
if (elClass && elClass.getStubConfig) {
const classStubConfig = elClass.getStubConfig(
const classStubConfig = await elClass.getStubConfig(
hass,
entities,
entitiesFallback

View File

@@ -13,7 +13,7 @@ export const getHeaderFooterStubConfig = async (
const elClass = await getHeaderFooterElementClass(type);
if (elClass && elClass.getStubConfig) {
const classStubConfig = elClass.getStubConfig(
const classStubConfig = await elClass.getStubConfig(
hass,
entities,
entitiesFallback

View File

@@ -91,6 +91,7 @@ export const coreCards: Card[] = [
},
{
type: "area",
showElement: true,
},
{
type: "conditional",

View File

@@ -1,7 +1,7 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { UNAVAILABLE } from "../../../data/entity";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
import { setValue } from "../../../data/input_text";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@@ -67,6 +67,12 @@ class HuiInputTextEntityRow extends LitElement implements LovelaceRow {
const element = this._inputEl;
const stateObj = this.hass!.states[this._config!.entity];
// Filter out invalid text states
if (element.value && UNAVAILABLE_STATES.includes(element.value)) {
element.value = stateObj.state;
return;
}
if (element.value !== stateObj.state) {
setValue(this.hass!, stateObj.entity_id, element.value!);
}

View File

@@ -30,6 +30,7 @@ import "../../../components/ha-slider";
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import {
computeMediaDescription,
ControlButton,
MediaPlayerEntity,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
@@ -108,6 +109,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
}
const entityState = stateObj.state;
const controlButton = this._computeControlButton(stateObj);
const buttons = html`
${!this._narrow &&
@@ -116,6 +118,9 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiSkipPrevious}
.label=${this.hass.localize(
"ui.card.media_player.media_previous_track"
)}
@click=${this._previousTrack}
></ha-icon-button>
`
@@ -130,7 +135,10 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
supportsFeature(stateObj, SUPPORT_PAUSE)))
? html`
<ha-icon-button
.path=${this._computeControlIcon(stateObj)}
.path=${controlButton.icon}
.label=${this.hass.localize(
`ui.card.media_player.${controlButton.action}`
)}
@click=${this._playPauseStop}
></ha-icon-button>
`
@@ -140,6 +148,9 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiSkipNext}
.label=${this.hass.localize(
"ui.card.media_player.media_next_track"
)}
@click=${this._nextTrack}
></ha-icon-button>
`
@@ -162,6 +173,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiPower}
.label=${this.hass.localize("ui.card.media_player.turn_on")}
@click=${this._togglePower}
></ha-icon-button>
`
@@ -175,6 +187,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiPower}
.label=${this.hass.localize("ui.card.media_player.turn_off")}
@click=${this._togglePower}
></ha-icon-button>
`
@@ -193,6 +206,13 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
.path=${stateObj.attributes.is_volume_muted
? mdiVolumeOff
: mdiVolumeHigh}
.label=${this.hass.localize(
`ui.card.media_player.${
stateObj.attributes.is_volume_muted
? "media_volume_mute"
: "media_volume_unmute"
}`
)}
@click=${this._toggleMute}
></ha-icon-button>
`
@@ -214,10 +234,16 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
? html`
<ha-icon-button
.path=${mdiVolumeMinus}
.label=${this.hass.localize(
"ui.card.media_player.media_volume_down"
)}
@click=${this._volumeDown}
></ha-icon-button>
<ha-icon-button
.path=${mdiVolumePlus}
.label=${this.hass.localize(
"ui.card.media_player.media_volume_up"
)}
@click=${this._volumeUp}
></ha-icon-button>
`
@@ -249,14 +275,14 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
this._veryNarrow = (this.clientWidth || 0) < 225;
}
private _computeControlIcon(stateObj: HassEntity): string {
private _computeControlButton(stateObj: HassEntity): ControlButton {
return stateObj.state === "on"
? mdiPlayPause
? { icon: mdiPlayPause, action: "media_play_pause" }
: stateObj.state !== "playing"
? mdiPlay
? { icon: mdiPlay, action: "media_play" }
: supportsFeature(stateObj, SUPPORT_PAUSE)
? mdiPause
: mdiStop;
? { icon: mdiPause, action: "media_pause" }
: { icon: mdiStop, action: "media_stop" };
}
private _togglePower(): void {

View File

@@ -41,11 +41,18 @@ class HuiToggleEntityRow extends LitElement implements LovelaceRow {
`;
}
const showToggle =
stateObj.state === "on" ||
stateObj.state === "off" ||
UNAVAILABLE_STATES.includes(stateObj.state);
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
${stateObj.state === "on" ||
stateObj.state === "off" ||
UNAVAILABLE_STATES.includes(stateObj.state)
<hui-generic-entity-row
.hass=${this.hass}
.config=${this._config}
.catchInteraction=${!showToggle}
>
${showToggle
? html`
<ha-entity-toggle
.hass=${this.hass}

View File

@@ -698,7 +698,7 @@ class HUIRoot extends LitElement {
private _navigateToView(path: string | number, replace?: boolean) {
if (!this.lovelace!.editMode) {
navigate(`${this.route!.prefix}/${path}`, { replace });
navigate(`${this.route!.prefix}/${path}${location.search}`, { replace });
return;
}
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {

View File

@@ -95,6 +95,7 @@ export const derivedStyles = {
"mdc-theme-text-disabled-on-light": "var(--disabled-text-color)",
"mdc-theme-text-primary-on-background": "var(--primary-text-color)",
"mdc-theme-text-secondary-on-background": "var(--secondary-text-color)",
"mdc-theme-text-hint-on-background": "var(--secondary-text-color)",
"mdc-theme-text-icon-on-background": "var(--secondary-text-color)",
"mdc-theme-error": "var(--error-color)",
"app-header-text-color": "var(--text-primary-color)",

View File

@@ -0,0 +1,15 @@
import { Constructor } from "../types";
import { HassBaseEl } from "./hass-base-mixin";
export const ExternalMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
) =>
class extends superClass {
protected hassConnected() {
super.hassConnected();
if (this.hass!.auth.external) {
this.hass!.auth.external.connection = this.hass!.connection;
}
}
};

View File

@@ -6,6 +6,7 @@ import DisconnectToastMixin from "./disconnect-toast-mixin";
import { hapticMixin } from "./haptic-mixin";
import { HassBaseEl } from "./hass-base-mixin";
import { loggingMixin } from "./logging-mixin";
import { ExternalMixin } from "./external-mixin";
import MoreInfoMixin from "./more-info-mixin";
import NotificationMixin from "./notification-mixin";
import { panelTitleMixin } from "./panel-title-mixin";
@@ -31,4 +32,5 @@ export class HassElement extends ext(HassBaseEl, [
hapticMixin,
panelTitleMixin,
loggingMixin,
ExternalMixin,
]) {}

View File

@@ -131,5 +131,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
(themeMeta.getAttribute("default-content") as string);
themeMeta.setAttribute("content", themeColor);
}
this.hass!.auth.external?.fireMessage({ type: "theme-update" });
}
};

View File

@@ -195,8 +195,14 @@
"turn_off": "Turn off",
"media_play": "Play",
"media_play_pause": "Play/pause",
"media_next_track": "Next",
"media_previous_track": "Previous",
"media_pause": "Pause",
"media_stop": "Stop",
"media_next_track": "Next track",
"media_previous_track": "Previous track",
"media_volume_up": "Volume up",
"media_volume_down": "Volume down",
"media_volume_mute": "Volume mute",
"media_volume_unmute": "Volume unmute",
"text_to_speak": "Text to speak"
},
"persistent_notification": {
@@ -291,12 +297,14 @@
"undo": "Undo",
"move": "Move",
"save": "Save",
"submit": "Submit",
"rename": "Rename",
"yes": "Yes",
"no": "No",
"not_now": "Not now",
"skip": "Skip",
"menu": "Menu",
"overflow_menu": "Overflow menu",
"help": "Help",
"successfully_saved": "Successfully saved",
"successfully_deleted": "Successfully deleted",
@@ -421,6 +429,7 @@
}
},
"related-filter-menu": {
"filter": "Filter",
"filter_by_entity": "Filter by entity",
"filter_by_device": "Filter by device",
"filter_by_area": "Filter by area",
@@ -538,6 +547,13 @@
},
"attributes": {
"expansion_header": "Attributes"
},
"qr-scanner": {
"select_camera": "Select camera",
"only_https_supported": "You can only use your camera to scan a QR code when using HTTPS.",
"not_supported": "Your browser doesn't support QR scanning.",
"manual_input": "You can scan the QR code with another QR scanner and paste the code in the input below",
"enter_qr_code": "Enter QR code value"
}
},
"dialogs": {
@@ -678,8 +694,8 @@
"open_cover": "Open cover",
"close_cover": "Close cover",
"open_tilt_cover": "Open cover tilt",
"close_tile_cover": "Close cover tilt",
"stop_cover": "Stop cover from moving"
"close_tilt_cover": "Close cover tilt",
"stop_cover": "Stop cover"
}
},
"entity_registry": {
@@ -878,8 +894,7 @@
"key_missing": "Required key ''{key}'' is missing.",
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
"no_template_editor_support": "Templates not supported in visual editor",
"no_state_array_support": "Multiple state values not supported in visual editor"
"no_template_editor_support": "Templates not supported in visual editor"
},
"supervisor": {
"title": "Could not load the Supervisor panel!",
@@ -913,6 +928,7 @@
"dismiss": "Dismiss"
},
"sidebar": {
"external_app_configuration": "App Configuration",
"sidebar_toggle": "Sidebar Toggle",
"done": "Done",
"hide_panel": "Hide panel",
@@ -929,9 +945,47 @@
},
"config": {
"header": "Configure Home Assistant",
"advanced_mode": {
"hint_enable": "Missing config options? Enable advanced mode on",
"link_profile_page": "your profile page"
"dashboard": {
"devices": {
"title": "Devices & Services",
"description": "Integrations, devices, entities and areas"
},
"automations": {
"title": "Automations & Scenes",
"description": "Manage automations, scenes, scripts and helpers"
},
"blueprints": {
"title": "Blueprints",
"description": "Pre-made automations and scripts by the community"
},
"supervisor": {
"title": "Add-ons, Backups & Supervisor",
"description": "Create backups, check logs or reboot your system"
},
"dashboards": {
"title": "Dashboards",
"description": "Create customized sets of cards to control your home"
},
"energy": {
"title": "Energy",
"description": "Monitor your energy production and consumption"
},
"tags": {
"title": "Tags",
"description": "Trigger automations when a NFC tag, QR code, etc. is scanned"
},
"people": {
"title": "People & Zones",
"description": "Manage the people and zones that Home Assistant tracks"
},
"companion": {
"title": "Companion App",
"description": "Location and notifications"
},
"settings": {
"title": "Settings",
"description": "Basic settings, server controls, logs and info"
}
},
"common": {
"editor": {
@@ -1109,7 +1163,7 @@
"cost_number": "Use a static price",
"cost_number_input": "Price per {unit}",
"gas_usage": "Gas usage",
"m3_or_kWh": " or kWh"
"m3_or_kWh": "ft³, m³, Wh, kWh or MWh"
}
},
"device_consumption": {
@@ -1150,19 +1204,19 @@
},
"entity_unexpected_unit_energy": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'kWh' or 'Wh':"
"description": "The following entities do not have the expected units of measurement 'Wh', 'kWh' or 'MWh':"
},
"entity_unexpected_unit_gas": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement 'kWh', 'm³' or 'ft³':"
"description": "The following entities do not have the expected units of measurement 'Wh', 'kWh' or 'MWh' for an energy sensor or 'm³' or 'ft³' for a gas sensor:"
},
"entity_unexpected_unit_energy_price": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'' or ''{currency}/Wh'':"
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'' or ''{currency}/MWh'':"
},
"entity_unexpected_unit_gas_price": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/m³'' or ''{currency}/ft³'':"
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'', ''{currency}/Wh'', ''{currency}/MWh'', ''{currency}/m³'' or ''{currency}/ft³'':"
},
"entity_unexpected_state_class": {
"title": "Unexpected state class",
@@ -1365,7 +1419,7 @@
},
"server_control": {
"caption": "Server Controls",
"description": "Restart and stop the Home Assistant server",
"description": "Validate and restart the Home Assistant server",
"section": {
"validation": {
"heading": "Configuration validation",
@@ -1407,7 +1461,8 @@
"telegram": "Telegram notify services",
"smtp": "SMTP notify services",
"mqtt": "Manually configured MQTT entities",
"rpi_gpio": "Raspberry Pi GPIO entities"
"rpi_gpio": "Raspberry Pi GPIO entities",
"timer": "Timers"
},
"server_management": {
"heading": "Server management",
@@ -1514,7 +1569,7 @@
"extra_fields": {
"above": "Above",
"below": "Below",
"for": "Duration",
"for": "Duration (optional)",
"zone": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]"
}
},
@@ -1537,9 +1592,9 @@
"state": {
"label": "State",
"attribute": "Attribute (optional)",
"from": "From",
"for": "For",
"to": "To"
"from": "From (optional)",
"for": "For (optional)",
"to": "To (optional)"
},
"homeassistant": {
"label": "Home Assistant",
@@ -2390,6 +2445,7 @@
"manuf": "by {manufacturer}",
"via": "Connected via",
"firmware": "Firmware: {version}",
"hardware": "Hardware: {version}",
"unnamed_entry": "Unnamed entry",
"unknown_via_device": "Unknown device",
"area": "In {area}",
@@ -2478,7 +2534,7 @@
"admin": "Administrator",
"group": "Group",
"active": "Active",
"local_only": "Can only login from the local network",
"local_only": "Can only log in from the local network",
"system_generated": "System generated",
"system_generated_users_not_removable": "Unable to remove system generated users.",
"system_generated_users_not_editable": "Unable to update system generated users.",
@@ -2814,7 +2870,7 @@
"node_id": "Device ID",
"home_id": "Home ID",
"source": "Source",
"close": "Close",
"back": "Back",
"add_node": "Add device",
"remove_node": "Remove device",
"reconfigure_server": "Re-configure Server",
@@ -2883,8 +2939,6 @@
"qr_code": "QR Code",
"qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.",
"scan_qr_code": "Scan QR code",
"enter_qr_code": "Enter QR code value",
"select_camera": "Select camera",
"inclusion_failed": "The device could not be added.",
"check_logs": "Please check the logs for more information.",
"inclusion_finished": "The device has been added.",
@@ -2897,6 +2951,8 @@
"dsk": "DSK",
"security_classes": "Security classes",
"unprovison": "Unprovison",
"included": "Included",
"not_included": "Not Included",
"confirm_unprovision_title": "Are you sure you want to unprovision the device?",
"confirm_unprovision_text": "If you unprovision the device it will not be added to Home Assistant when it is powered on. If it is already added to Home Assistant, removing the provisioned device will not remove it from Home Assistant."
},
@@ -2976,7 +3032,8 @@
"title": "Z-Wave JS Logs",
"log_level": "Log Level",
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages…",
"log_level_changed": "Log Level changed to: {level}"
"log_level_changed": "Log Level changed to: {level}",
"download_logs": "Download logs"
}
}
},
@@ -3255,7 +3312,8 @@
},
"area": {
"name": "Area",
"description": "The Area card automatically displays entities of a specific area."
"description": "The Area card automatically displays entities of a specific area.",
"show_camera": "Show camera feed instead of area picture"
},
"calendar": {
"name": "Calendar",
@@ -4220,7 +4278,8 @@
"create_backup": "Create backup before updating",
"description": "You have {version} installed. Click update to update to version {newest_version}",
"updating": "Updating {name} to version {version}",
"creating_backup": "Creating backup of {name}"
"creating_backup": "Creating backup of {name}",
"no_update": "No update available for {name}"
},
"confirm": {
"restart": {
@@ -4408,7 +4467,8 @@
"repositories": {
"title": "Manage add-on repositories",
"add": "Add",
"remove": "Remove"
"remove": "Remove",
"used": "Repository is in use for installed add-ons and can't be removed."
},
"restart_addon": {
"confirm_text": "Restart add-on",

View File

@@ -7,9 +7,9 @@ export const removeLaunchScreen = () => {
}
};
export const renderLaunchScreenInfoBox = (element: TemplateResult) => {
export const renderLaunchScreenInfoBox = (content: TemplateResult) => {
const infoBoxElement = document.getElementById("ha-launch-screen-info-box");
if (infoBoxElement) {
render(element, infoBoxElement);
render(content, infoBoxElement);
}
};

View File

@@ -2,16 +2,16 @@ import { assert } from "chai";
import {
ExternalMessaging,
InternalMessage,
EMMessage,
} from "../../src/external_app/external_messaging";
// @ts-ignore
global.__DEV__ = true;
class MockExternalMessaging extends ExternalMessaging {
public mockSent: InternalMessage[] = [];
public mockSent: EMMessage[] = [];
protected _sendExternal(msg: InternalMessage) {
protected _sendExternal(msg: EMMessage) {
this.mockSent.push(msg);
}
}

View File

@@ -9113,7 +9113,7 @@ fsevents@^1.2.7:
gulp-rename: ^2.0.0
gulp-zopfli-green: ^3.0.1
hls.js: ^1.0.11
home-assistant-js-websocket: ^5.11.3
home-assistant-js-websocket: ^6.0.1
html-minifier: ^4.0.0
husky: ^1.3.1
idb-keyval: ^5.1.3
@@ -9184,10 +9184,10 @@ fsevents@^1.2.7:
languageName: unknown
linkType: soft
"home-assistant-js-websocket@npm:^5.11.3":
version: 5.11.3
resolution: "home-assistant-js-websocket@npm:5.11.3"
checksum: 3ab90e5105c5f379d77fb23ab53eaec2789be7bf1fd507a7520d9cf329d36942b8e978a591b822cff96100630d43bd036a4e25e2f49c40d0c56a111808fb90a5
"home-assistant-js-websocket@npm:^6.0.1":
version: 6.0.1
resolution: "home-assistant-js-websocket@npm:6.0.1"
checksum: 566d6de6a4eb0e05ca434a45433cfe6fdd6b5cb2008e9a165709e08335df1c9b70903564c479ab8d48c6f5468a9784f47697192f9023170d2d86d43a461d6126
languageName: node
linkType: hard