Compare commits

...

255 Commits

Author SHA1 Message Date
Bram Kragten
ee9ca16eb5 Merge pull request #13138 from home-assistant/dev 2022-07-07 15:29:24 +02:00
Raman Gupta
399efca411 Only show firmware update warning if no firmware update is in progress (#13068) 2022-07-07 15:03:11 +02:00
Bram Kragten
f8bccf9e79 Bumped version to 20220707.0 2022-07-07 15:02:08 +02:00
Bram Kragten
87aab72b63 opti search params history 2022-07-07 15:01:29 +02:00
Bram Kragten
24688ba18e fix reload history when no selection made (#13137) 2022-07-07 14:58:51 +02:00
Zack Barett
e8086b6a6f Refactor History Panel Code a bit (#13129)
Co-authored-by: D3v01dZA <caltona1@gmail.com>
2022-07-07 14:27:28 +02:00
Bram Kragten
4358437278 Merge pull request #13126 from home-assistant/dev
20220706.0
2022-07-06 18:56:12 +02:00
Zack Barett
e0a9c57a54 Bumped version to 20220706.0 (#13125) 2022-07-06 18:55:52 +02:00
Bram Kragten
e63953ecbc Fix scene editor (#13123) 2022-07-06 13:50:37 +00:00
Bram Kragten
72af200190 Remove localstorage from history, use url (#13122) 2022-07-06 08:44:28 -05:00
Zack Barett
2094ae534b Fix History Panel when no entities are found (#13103) 2022-07-06 01:05:40 +02:00
Bram Kragten
f6d6fd179f Merge pull request #13099 from home-assistant/dev
Bump to 20220705.0
2022-07-05 18:55:51 +02:00
Zack Barett
153ebb2a20 Bumped version to 20220705.0 (#13098) 2022-07-05 18:40:26 +02:00
Michael Irigoyen
5d58e52eea Update MDI to v6.9.96 (#13096) 2022-07-05 18:38:24 +02:00
Zack Barett
5038f9c3c6 Some Updates to the History Panel (#13095) 2022-07-05 15:31:17 +00:00
Zack Barett
b285fda61b Move Sign out back to Account Card for Cloud (#13094) 2022-07-05 17:15:09 +02:00
D3v01dZA
6cd38472cd Multiple entities on history panel bugfix and additional improvements (#13045)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-07-05 09:23:38 -05:00
Erik Montnemery
8fd5f53f96 Exclude config and diagnostic entities from scenes (#13072)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-05 08:49:05 -05:00
Zack Barett
b70eee77ef Remove config Path from about (#13049)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-05 09:41:40 +00:00
Raman Gupta
4148b8c7aa Get rid of dupe HTML in zwave_js firmware upload dialog (#13067) 2022-07-05 11:26:38 +02:00
Zack Barett
e22dd0c49d Fix Energy Compare Translations (#13055)
* Fix Energy Compare Translations

* Fix alert
2022-07-05 11:23:46 +02:00
dependabot[bot]
30a254f98f Bump actions/checkout from 2 to 3 (#13075)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:45:41 +02:00
dependabot[bot]
8fcb3a017b Bump actions/setup-python from 2 to 4 (#13078)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:28:53 +02:00
dependabot[bot]
5e29c7efa9 Bump dessant/lock-threads from 2.0.1 to 3.0.0 (#13077)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:26:46 +02:00
dependabot[bot]
b6cc3e3ef0 Bump actions/setup-node from 2 to 3 (#13080)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:04:22 +02:00
dependabot[bot]
184bdc0c85 Bump github/codeql-action from 1 to 2 (#13081)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:04:02 +02:00
Sven Serlier
f7fb731dc8 Add dependabot (#13073)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2022-07-05 09:40:57 +02:00
Raman Gupta
77977f64a3 Don't check zwave_js node firmware update capabilities (#13066) 2022-07-03 13:45:19 -07:00
Franck Nijhof
e8da573ba2 Only upload nighlty wheel build artifact (#13064) 2022-07-02 08:49:27 -05:00
Franck Nijhof
07332bf155 Fix version detection in build env to allow for nightly builds (#13062) 2022-07-02 00:22:54 +02:00
Franck Nijhof
f3c7583bf7 Add nightly frontend builds (#13061) 2022-07-01 14:30:40 -07:00
Paulus Schoutsen
1cc02415d3 Fix rendering config entry titles (#13060)
* Fix rendering config entry titles

* Fix location height

* Only fetch installation type when user not created yet
2022-07-01 13:52:56 -05:00
Paulus Schoutsen
6ca3f06ea0 Merge pull request #13057 from home-assistant/hide-sun-onboarding
Do not show the sun during onboarding
2022-07-01 11:05:45 -07:00
Paulus Schoutsen
198e2b7bdf Do not show the sun during onboarding 2022-07-01 10:23:23 -07:00
Zack Barett
5a68e2c977 Merge pull request #13053 from home-assistant/improve-autocompletion
improve autocompletion
2022-06-30 14:57:42 -05:00
Sven Serlier
d9d29db560 Fix demo labels (#13033) 2022-06-30 21:51:23 +02:00
Zack Barett
124c6dc2b8 Merge pull request #13048 from emufan/patch-1 2022-06-30 14:49:52 -05:00
Bram Kragten
0f3886e053 improve autocompletion 2022-06-30 21:41:58 +02:00
Paulus Schoutsen
68bb3558b4 Merge pull request #13051 from home-assistant/dev 2022-06-30 10:22:17 -07:00
Zack Barett
b8bd15aa33 Bumped version to 20220630.0 (#13050) 2022-06-30 10:17:43 -07:00
Zack Barett
ed39aa6a7c Merge pull request #13046 from Nardol/no_fix_em_dash 2022-06-30 08:20:41 -05:00
Zack Barett
405cae9b5f Merge pull request #13047 from piitaya/feat/ha-code-editor-autocomplete-icon-option 2022-06-30 08:19:54 -05:00
emufan
3ca2cbb3f9 Update ha-config-integrations.ts 2022-06-30 15:03:34 +02:00
piitaya
c295ae56ab separate autocomplete options in ha-code-editor 2022-06-30 11:27:04 +02:00
piitaya
830364721b separate autocomplete options in ha-code-editor 2022-06-30 11:22:30 +02:00
Patrick ZAJDA
19089213e3 Add em dash instead of blank when no fix is needed in statistics
Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
2022-06-30 11:12:35 +02:00
Zack Barett
b633067e5c Merge pull request #13041 from home-assistant/Fix-About-Page 2022-06-29 17:50:43 -05:00
Zack
1b8874cbd4 Fix Translation on About Page 2022-06-29 15:27:54 -05:00
Zack Barett
a5f8ce85ba Merge pull request #13032 from home-assistant/dev 2022-06-29 11:25:03 -05:00
Bram Kragten
56eacf5733 20220629.0 (#13030)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: wizmo2 <wizmo.home@yahoo.com>
Co-authored-by: Yosi Levy <37745463+yosilevy@users.noreply.github.com>
Co-authored-by: RoboMagus <68224306+RoboMagus@users.noreply.github.com>
Co-authored-by: loeffelpan <34661317+loeffelpan@users.noreply.github.com>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Josh McCarty <josh@joshmccarty.com>
Co-authored-by: imgbot[bot] <31301654+imgbot[bot]@users.noreply.github.com>
Co-authored-by: Brandon Rothweiler <brandonrothweiler@gmail.com>
Co-authored-by: James Baker <j.baker@outlook.com>
Co-authored-by: Sven Serlier <85389871+wrt54g@users.noreply.github.com>
Co-authored-by: Alessandro Ghedini <alessandro@ghedini.me>
Co-authored-by: Emanuele <55278049+elax46@users.noreply.github.com>
Co-authored-by: Pascal Vizeli <pascal.vizeli@syshack.ch>
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Co-authored-by: Kristján Bjarni <kristjanbjarni@gmail.com>
Co-authored-by: D3v01dZA <caltona1@gmail.com>
2022-06-29 11:22:17 -05:00
Bram Kragten
9324061d05 Add auto completion for mdi icons to code editor (#13022)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-06-29 16:12:16 +00:00
Bram Kragten
eafcbdc65b Merge branch 'master' into dev 2022-06-29 18:02:42 +02:00
Zack Barett
0175522c17 Bumped version to 20220629.0 (#13029) 2022-06-29 18:00:50 +02:00
D3v01dZA
cff3f51d34 Multiple entities on history panel (#9946)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-06-29 10:39:38 -05:00
Kristján Bjarni
0f580a91c9 Add optional label for gauge segment (#12960)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-06-29 10:36:18 -05:00
Erik Montnemery
389f50b29a Merge pull request #13028 from home-assistant/add_wind_speed_units
Support knots and ft/s in weather wind speed
2022-06-29 16:34:53 +02:00
J. Nick Koston
b689bb8fcf Pause the logbook stream when scrolled (#13026) 2022-06-29 13:49:44 +00:00
Erik
36f067ede4 Support knots and ft/s in weather wind speed 2022-06-29 15:47:28 +02:00
Zack Barett
c2178622dd Add Switch as X Icon and Threshold Icon (#13024) 2022-06-29 09:03:04 +02:00
Zack Barett
014448e7ea Update about page (#12653)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2022-06-29 08:56:30 +02:00
Zack Barett
08eff0509a Fix General Config Zone update on Mobile (#13011)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-06-29 08:54:19 +02:00
Marc Mueller
62d0882e82 Support editable installs (#12838)
* Support editable installs

* Update setup.cfg
2022-06-28 17:25:21 -07:00
Sven Serlier
86a574dbbd Fix demo labes (#13025) 2022-06-28 18:22:31 +00:00
Yosi Levy
71ac4620c5 RTL Fixes (#13023) 2022-06-28 09:28:34 -05:00
Zack Barett
f611049517 Remove Restart Moved Tip (#13009) 2022-06-27 10:55:12 +02:00
Zack Barett
45fa8c272f Remove TTS moved Tip (#13010) 2022-06-27 10:54:49 +02:00
Yosi Levy
28a1c97571 Various card RTL fixes (#13006) 2022-06-24 10:18:39 -05:00
Zack Barett
d9a5ae0cf1 Bumped version to 20220624.0 (#13008) 2022-06-24 03:10:20 +00:00
Raman Gupta
c03849d30b Only show zwave_js firmware action if no other updates in progress (#13002)
* Only show zwave_js firmware update action if no other updates in progress

* readability
2022-06-23 19:19:20 -05:00
Pascal Vizeli
535fe2686b Use new wheels builder (#13001) 2022-06-23 14:11:06 +02:00
Emanuele
709bc87a36 Fix missing translatable energy texts (#12877)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>
2022-06-23 11:02:02 +02:00
Raman Gupta
2812b467ec Add manual firmware update support for zwave-js devices (#12910)
* Subscribe to zwave_js node status updates in device panel

* Add typing for message

* Add manual firmware update support for zwave-js devices

* Tweaks based on upstream changes

* Tweaks

* remove unused CSS

* Update zwave_js.ts

* Tweaks after somet esting

* Bold device name instead of italic, catch abort errors and show the message

* Incorporate new commands tweak the UI and messaging

* Add a warning about firmware updates potentially bricking a device, and use Promise.all where possible

* Better typing so we can clean up code

* Additional tweaks

* Remove commented out code

* change style a bit

* prettier

* Be more precise with progress because it always helps the user if they can see progress

* nit

* Update src/translations/en.json

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Bram's review

* Only ask for firmware target if there is more than one available

* Only offer another firmware update if the original firmware update failed

* Only offer firmware upgrade if node is ready and pass firmware capabilities into dialog so we don't have to make call again

* Use ha-form

* Add comment

* Switch schema name

* Import icon

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-06-23 10:51:52 +02:00
Erik Montnemery
7d118a5715 Allow customizing weather units (#12947)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
2022-06-23 10:48:39 +02:00
Alessandro Ghedini
8bd7370a02 Show moisture/flood alerts in the Area card (#12978) 2022-06-22 19:26:32 -05:00
Alessandro Ghedini
9fa8a96d09 Show humidity sensor values in Area card (#12976) 2022-06-22 10:45:38 -05:00
Alessandro Ghedini
508d1fffef Show icon for temperature values in Area card (#12977) 2022-06-22 10:42:39 -05:00
J. Nick Koston
3633daa814 Use websocket endpoint to fetch config entries (#12964) 2022-06-21 13:10:39 -05:00
Sven Serlier
05346ae9fc Fix demo labels (#12984) 2022-06-21 10:36:42 -05:00
Yosi Levy
ea667cf0b9 RTL safari fix (#12963) 2022-06-21 11:03:22 +02:00
James Baker
048ac3965e Fix grammar in NFC settings tab description (#12979) 2022-06-20 17:24:07 +00:00
Brandon Rothweiler
276b6f4d1f Fix a bug in the climate entity more info card (#12973) 2022-06-20 14:29:42 +02:00
Paulus Schoutsen
e765d7749c Update text around updating cloud entity exposed defaults (#12954) 2022-06-20 14:28:32 +02:00
imgbot[bot]
9a3b4d6df2 [ImgBot] Optimize images (#12985)
*Total -- 298.28kb -> 241.37kb (19.08%)

/gallery/public/images/logo-with-text.png -- 66.64kb -> 46.13kb (30.79%)
/gallery/public/images/clearspace.png -- 43.46kb -> 31.99kb (26.39%)
/gallery/public/images/using-our-logo.png -- 32.47kb -> 24.92kb (23.24%)
/gallery/public/images/logo-variants.png -- 34.86kb -> 26.78kb (23.18%)
/gallery/public/images/logo.png -- 27.30kb -> 21.50kb (21.25%)
/gallery/public/images/sunflowers.jpg -- 93.54kb -> 90.05kb (3.73%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2022-06-20 14:27:18 +02:00
Raman Gupta
529e27992e Subscribe to zwave_js node status updates in device panel (#12916)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-06-19 14:22:02 -05:00
Bram Kragten
6c5cf2a0ec Fix energy panel in Demo (#12906) 2022-06-16 10:00:52 -05:00
Josh McCarty
a4cb270f09 Ensures inputmode is set properly for alarm code inputs (#12953) 2022-06-16 09:58:29 -05:00
Bram Kragten
5160a1f55c Don't make dialog boxes fullscreen on mobile (#12928) 2022-06-14 11:04:09 -07:00
Allen Porter
6a3a0db338 Fix application credentials description when loaded from config flow (#12940) 2022-06-14 11:03:40 -07:00
Erik Montnemery
765d4eb3b4 Revert Use unit system definitions for weather units (#10657) (#12946) 2022-06-14 11:03:05 -07:00
Erik Montnemery
cc09e24d66 Fix customizing sensor units (#12948) 2022-06-14 11:02:15 -07:00
Joakim Sørensen
e7848262ea Split store and installed calls (#12921)
* Split store and installed calls

* Fix issue when installing

* Remove supervisor.addons usage

* one more

* Update core

* Comments
2022-06-11 11:04:54 +02:00
Paulus Schoutsen
0926202eca Clean up unused var (#12930) 2022-06-10 21:10:33 -07:00
Yosi Levy
e83af02410 Use ha-list-item in config updates (#12922) 2022-06-10 16:43:31 +02:00
Raman Gupta
74d6a52fa9 Remove unused zwave_js functions (#12915) 2022-06-10 16:43:04 +02:00
Allen Porter
5baa975632 Add application credentials description placeholder (#12869) 2022-06-10 16:42:20 +02:00
Joakim Sørensen
4ad49ef07f Move to supervisor store API (#12911)
* Move to supervisor store API

* Add supervisorApiCall helper to simplify functions

* Do not consider ESPHome as custom repository

* Home Assistant Community Add-ons is not custom
2022-06-08 15:28:40 +02:00
Yosi Levy
bc47ecaa57 Various RTL fixes (#12857) 2022-06-08 10:46:39 +02:00
Steve Repsher
2bd617ce6e Add container list and ARIA to create helper listbox (#12885) 2022-06-07 19:52:48 +02:00
loeffelpan
dbaf955525 Fix extra space in energy-dist-card (#12905) 2022-06-07 15:18:52 +00:00
RoboMagus
578ff5b53f Energy Dashboard: Align total cost with 'previous cost' column. (#12883)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-06-07 15:11:47 +00:00
Yosi Levy
e386942ea7 Quick bar keyboard shortcut international support (#12892) 2022-06-07 17:07:22 +02:00
Paulus Schoutsen
2fdd50f45f Add announce: true when sending TTS from media browser (#12866) 2022-06-07 16:43:34 +02:00
wizmo2
4b36770adf Fix overlapped tiles and hidden title for 2:3 aspect ratio media classes (#12853) 2022-06-07 16:42:37 +02:00
Raman Gupta
54377225ec Add zwave_js device statistics (#12794)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-06-07 16:40:28 +02:00
Philip Allgaier
f020add6be Make translation README a bit clearer (#12901) 2022-06-07 16:36:19 +02:00
J. Nick Koston
b1a3996cf1 Fix multiple races in logbook subscriptions (#12878)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-06-06 22:23:56 -07:00
J. Nick Koston
a47a0ed716 Fix history charts not auto refreshing with cached history (#12873)
* Fix history charts refreshing with cached history

Fixes #12859

* return a new array

* Revert "return a new array"

This reverts commit 2b0e265185.
2022-06-06 16:05:22 -07:00
J. Nick Koston
91cd584b4b Request tiny thumbnails for cameras in the entity selector to reduce memory pressure (#12880) 2022-06-05 22:12:54 -07:00
J. Nick Koston
75562efb79 Add counter to logbook continuous domains (#12888) 2022-06-05 21:46:56 -07:00
J. Nick Koston
f464bcfc14 Filter entities that will never have entries in the logbook card editor (#12876) 2022-06-05 21:45:58 -07:00
Bram Kragten
f8af66d310 20220601.0 (#12843) 2022-06-01 21:57:23 +02:00
Bram Kragten
4922e575f8 Bumped version to 20220601.0 2022-06-01 21:43:38 +02:00
Bram Kragten
ac08daa64e Don't fix width of label when not virtualized (#12842)
Don't fix width of label when not virtualized
2022-06-01 21:42:27 +02:00
Bram Kragten
97f082a384 Merge pull request #12835 from home-assistant/dev 2022-05-31 16:09:03 +02:00
Bram Kragten
ced37aab4c Make hardware item non interactive 2022-05-31 15:57:19 +02:00
Bram Kragten
1938fb89e6 Bumped version to 20220531.0 2022-05-31 15:51:20 +02:00
Bram Kragten
6842c479d6 Add compare data to individual devices graph (#12829) 2022-05-31 08:36:04 -05:00
Bram Kragten
881f6b0531 Use icon button for compare on narrow (#12831) 2022-05-31 08:29:32 -05:00
Pawel
a564ceb9e3 Add proper label for gas energy stats (#12828)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-31 08:51:27 +00:00
J. Nick Koston
077fa3f6b2 Fix live logbook starting empty (#12833) 2022-05-30 22:03:51 -07:00
J. Nick Koston
ceda911670 Virtualize history panel (#12824) 2022-05-30 10:30:04 -10:00
Bram Kragten
afd41e79f0 Make blueprint picker wider (#12830) 2022-05-30 12:53:39 -05:00
Yosi Levy
10f63180eb RTL Auth fix (#12746) 2022-05-30 17:57:37 +02:00
Zack Barett
e54802bd87 Actually add Cloud URL Translation.... (#12813) 2022-05-30 17:56:24 +02:00
wizmo2
c1d6b51065 Scale oversized brand thumbnails in media browser (#12820)
* resize brand icons

* add newline JIC

* Now Prettier

* created brand-image style
2022-05-30 17:19:48 +02:00
Joakim Sørensen
ab65ce819f Fallback to 0 for undefined offsets (#12823) 2022-05-30 09:57:28 -05:00
Xor
1e011bfe34 Fix color of plant entity when state is problem (#12821) 2022-05-30 09:57:00 -05:00
Joakim Sørensen
5951f5c5c4 Prefer CSS variables in custom panel entrypoint (#12818) 2022-05-30 09:56:13 -05:00
Joakim Sørensen
0183e32267 Use supervisor envs instead of hassio (#12812) 2022-05-30 12:46:43 +02:00
Raman Gupta
588fd87654 Update style of zwave_js controller statistics (#12810) 2022-05-27 15:33:07 -05:00
Philip Allgaier
e2944b098d Align "Browse Media" button while being wrapped (#12800) 2022-05-27 00:37:43 +00:00
Bram Kragten
cbb962f084 Fix width of application creds page (#12806) 2022-05-26 23:59:26 +02:00
Paulus Schoutsen
93f4ae1bea Add redirects to cast to catch some common mistakes in custom cards (#12808) 2022-05-26 23:58:54 +02:00
Bram Kragten
d810cae194 20220526.0 (#12807)
* Fix setting _externalAccess (#12584)

* Fix enter key support for generic dialog box (#12600)

* Revert #10991 (#12618)

* Replace host-context with css properties

* Remove "Lovelace" from Github issue templates (#12614)

* Remove "Lovelace" from Github issue templates

* Changes from review

* Get full core logs from core (#12639)

* Move YAML to first tab of Developer Tools (#12589)

* Add configuration panel for Application Credentials (#12344)

Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Zack <zackbarett@hey.com>

* Add label for Fix issue column header in statistics developer tools (#12597)

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* RTL settings clickable list item fix (#12595)

* Update src/state/translations-mixin.ts

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

* Update following review

* Update Translations to create helper (#12656)

* Hide Cloud URL - Add Copy Icon (#12655)

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

* Show script traces in logbook (#12643)

* Rtl menu fix (#12561)

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

* Use  /

* Update var name

* Use FabBase

* Update ha-fab.ts

* Switch logbook calls to use the new websocket (#12665)

* Add calendar trigger offsets in automation editor (#12486)

* Add calendar trigger offsets in automation editor

* Use duration selector for offset

* Fix typing for offsets/duratons

* Fix strict error handling in Markdown card templates (#12661)

* Add missing label to search icon (#12671)

* Update Lokalise URL (#12684)

* Teach logbook about additional context data (#12667)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Guard for missing backup integration (#12696)

* Rtl changes (#12693)

* Return focus after dialogs close (#11999)

* Bumped version to 20220516.0

* Fix float-end for LTR (#12707)

* Add my support for Application Credentials (#12709)

* Show manage cloud link to config (#12673)

* Add guard logic from PR home-assistant#12181 to input select row (#12703)

* Refactor logbook data fetch logic into reusable class (#12701)

* Add logbook to device info page (#12714)

* Add logbook to area info page (#12715)

* Add missing labels in energy dashboard settings (#12722)

Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>

* Delete focus targets for replaced dialogs (#12724)

* Convert history calls to use new websocket endpoint (#12662)

* Add support for OAuth2 callback via My (#12718)

* Support requesting translations for multiple integrations in one request (#12704)

* Support requesting translations for multiple integrations in one request

- Requires https://github.com/home-assistant/core/pull/71979

* onboarding as well

* integrations -> integration

* fix cache

* short return if they are all loaded

* reduce

* reduce

* reduce

* Show the integration brand icon when there is no entity in logbook (#12713)

* Support requesting multiple integration manifests in a single request (#12706)

* Support requesting multiple integration manifests in a single request

* only fetch if there are some to actually fetch

* handle empty

* not truthy, wrong language

* Do not copy params

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

* Import all date-fns from modules (#12717)

* Fix 'loading_log' string (#12712)

* Bumped version to 20220518.0

* Fix python to js timestamp conversions in logbook traces (#12677)

- The websocket version needs the time converted from
  where python stores the decimal

* Update Material Design Icons to v6.7.96 (#12111)

* Various RTL fixes (#12721)

* Select + target picker Rtl fixes (#12711)

* Add error handling for application credentials removal (#12686)

* Update zwave_js data collection URL (#12666)

* Use device_id instead of config entry id and node id for zwave_js (#12658)

* Use device_id instead of config entry id and node id for zwave_js

* Add additional cleanup from #12642

* Revert removal of multiple config entries check

* Update src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Update src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Get attributes from first state when using minimal responses (#12732)

* Pass device ID to logbook if available (#12728)

* Compute the icon based on the logbook state and not the current state (#12725)

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

* Add option to compare energy graphs with previous period (#12723)

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

* Add an application credentials display name (#12720)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Fixes logbook (#12740)

* Bumped version to 20220521.0

* Stop closed event when selecting datadisk (#12749)

* Use logbook livestream when requesting a time window that includes the future (#12744)

* Fetch supervisor info directly (#12751)

* Remove kernel and agent versions from about page (#12750)

* Move `preload_stream` setting to entity settings (#12730)

Co-authored-by: Zack <zackbarett@hey.com>

* Use new localized context state and source in logbook (#12742)

* Use new logbook streaming websocket api for cases where we need end_time (#12753)

* Open Application Credentials from integration configuration flow (#12708)

* Make entities and devices independent in the scene editor (#11046)

Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Erik <erik@montnemery.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Allow setting `device_class` "outlet" again through entity settings (#12669)

* Allow setting `device_class` "outlet" again through UI

* Fixes

* Null check deviceClass and adjust used translation

* Bumped version to 20220523.0 (#12756)

* Adjust logbook stream consumer to handle new metadata (#12755)

* Adjust path to version info in issue template (#12760)

* Add My HA link to about page to Github issue template (#12761)

* RTL updates (#12745)

* Add compare to energy sources table (#12762)

* Fix (#12764)

* Change service_data to just data (#12628)

* Simplify OAuth2 authorize callback URL (#12765)

* Hide hidden media player entities in media panel (#12766)

* Add integration filter to Device Selector (#12680)

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

* Integration filter for Area Selector (#12682)

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

* Bump Version to 20220524.0 (#12769)

* Move zwave_js node comments from device config to info page (#12625)

Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Various RTL fixes

* Add controller statistics to zwave_js config dashboard (#12668)

* Move Logbook and make device page better (#12763)

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

* Move metadata to pyproject.toml (#12770)

* Add aria-haspopup to button menus (#12758)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Hardware MVP (#12773)

* Fix "unavailable" handling for climate state rendering (#12778)

* Ensure state is vertically centered in more-info (#12780)

* Update zwave_js/network_status WS API (#12735)

* Bumped version to 20220525.0 (#12779)

* Use dynamic weather domain icon + icon alignment fix weather more-info (#12781)

* Fix typo in credentials removal dialog (#12784)

* Handle history api being passed entity ids as CSV (#12787)

* Fix history cache when there is cacheConfig (#12788)

* Use Hardware Integration for System Menu (#12789)

* Fix Media Player More info cramped controls (#12790)

* Fix Switch as X unable to change to a new type (#12797)

* Fix Zwave Alerts on device page (#12785)

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

* Set Error if entity is unavailable (#12791)

* Dynamically determine the correct action config struct (#12798)

* Remove import

* Hide Cloud information a bit more (#12802)

* Add dynamic header/footer config determination and update struct (#12795)

* Bump HAWS to 7.1.0 (#12804)

* Update Narrow Order on Device Page (#12801)

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

* Fix combo box inside dialog (#12805)

* Bumped version to 20220526.0

Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Yosi Levy <yosilevy@gmail.com>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: Patrick ZAJDA <patrick@zajda.fr>
Co-authored-by: Yosi Levy <37745463+yosilevy@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Sven <85389871+wrt54g@users.noreply.github.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: breakthestatic <breakthestatic@gmail.com>
Co-authored-by: RoboMagus <68224306+RoboMagus@users.noreply.github.com>
Co-authored-by: Michael Irigoyen <michael@irigoyen.dev>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: David F. Mulcahey <david.mulcahey@me.com>
Co-authored-by: Erik <erik@montnemery.com>
Co-authored-by: Thomas Lovén <thomasloven@gmail.com>
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
2022-05-26 23:41:27 +02:00
Bram Kragten
6797e17fc8 Merge branch 'master' into dev 2022-05-26 23:23:54 +02:00
Bram Kragten
6e58cd5d12 Bumped version to 20220526.0 2022-05-26 23:21:56 +02:00
Bram Kragten
a72fd19b73 Fix combo box inside dialog (#12805) 2022-05-26 20:28:05 +00:00
Zack Barett
41c61a2895 Update Narrow Order on Device Page (#12801)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-26 22:05:17 +02:00
Paulus Schoutsen
f35af9ed98 Bump HAWS to 7.1.0 (#12804) 2022-05-26 22:03:07 +02:00
Philip Allgaier
abf7cb7a74 Add dynamic header/footer config determination and update struct (#12795) 2022-05-26 21:02:44 +02:00
Zack Barett
6ec2e32241 Hide Cloud information a bit more (#12802) 2022-05-26 11:33:37 -07:00
Yosi Levy
b7cdd9a22f Merge pull request #12772 from yosilevy/RTL-switch
Various RTL fixes
2022-05-26 21:04:50 +03:00
Yosi Levy
6278eefc5d Remove import 2022-05-26 20:50:52 +03:00
Philip Allgaier
73cf0b54c9 Dynamically determine the correct action config struct (#12798) 2022-05-26 19:26:25 +02:00
Zack Barett
00dcecabb7 Set Error if entity is unavailable (#12791) 2022-05-26 19:25:00 +02:00
Zack Barett
c9df93bc54 Fix Zwave Alerts on device page (#12785)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-26 10:15:24 -05:00
Zack Barett
3550a8c263 Fix Switch as X unable to change to a new type (#12797) 2022-05-26 10:15:14 -05:00
Zack Barett
c0d30c56d6 Fix Media Player More info cramped controls (#12790) 2022-05-26 16:59:59 +02:00
Zack Barett
10813d06b6 Use Hardware Integration for System Menu (#12789) 2022-05-26 12:57:14 +02:00
J. Nick Koston
d0ead1fdb8 Fix history cache when there is cacheConfig (#12788) 2022-05-25 20:39:55 -05:00
J. Nick Koston
b0e6c41238 Handle history api being passed entity ids as CSV (#12787) 2022-05-25 23:05:56 +00:00
Philip Allgaier
2c1550b10f Fix typo in credentials removal dialog (#12784) 2022-05-25 18:09:15 +00:00
Philip Allgaier
ffc4ca5b56 Use dynamic weather domain icon + icon alignment fix weather more-info (#12781) 2022-05-25 19:39:34 +02:00
Zack Barett
85ad6619b7 Bumped version to 20220525.0 (#12779) 2022-05-25 11:49:40 -05:00
Raman Gupta
7358faf88e Update zwave_js/network_status WS API (#12735) 2022-05-25 11:49:25 -05:00
Philip Allgaier
19d014307a Ensure state is vertically centered in more-info (#12780) 2022-05-25 11:40:04 -05:00
Philip Allgaier
5217f5c50c Fix "unavailable" handling for climate state rendering (#12778) 2022-05-25 15:47:11 +00:00
Zack Barett
c4624faa71 Hardware MVP (#12773) 2022-05-25 17:11:15 +02:00
Steve Repsher
b35ba4d673 Add aria-haspopup to button menus (#12758)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-05-25 16:05:43 +02:00
Marc Mueller
f8303bff76 Move metadata to pyproject.toml (#12770) 2022-05-25 08:16:09 -05:00
Zack Barett
e61aa266a6 Move Logbook and make device page better (#12763)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-25 12:55:08 +00:00
Raman Gupta
d7971c69ad Add controller statistics to zwave_js config dashboard (#12668) 2022-05-25 11:36:27 +02:00
Yosi Levy
d65e45ecfd Various RTL fixes 2022-05-25 06:01:40 +03:00
Raman Gupta
966a624ef6 Move zwave_js node comments from device config to info page (#12625)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-24 21:16:07 -05:00
Zack Barett
7cc576a616 Bump Version to 20220524.0 (#12769) 2022-05-24 19:08:56 -05:00
Zack Barett
2dec8e70ec Integration filter for Area Selector (#12682)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-05-24 23:26:14 +00:00
Zack Barett
97663aef42 Add integration filter to Device Selector (#12680)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-05-24 23:25:09 +00:00
Paulus Schoutsen
3f1a2526b3 Hide hidden media player entities in media panel (#12766) 2022-05-24 21:07:22 +00:00
Franck Nijhof
e7517a8b61 Simplify OAuth2 authorize callback URL (#12765) 2022-05-24 13:32:33 -07:00
Thomas Lovén
e3d394eb32 Change service_data to just data (#12628) 2022-05-24 10:49:07 -05:00
Yosi Levy
536ea822b3 Fix (#12764) 2022-05-24 15:25:50 +00:00
Bram Kragten
8e4e22b6f8 Add compare to energy sources table (#12762) 2022-05-24 17:20:16 +02:00
Yosi Levy
2eaa246a03 RTL updates (#12745) 2022-05-24 15:14:11 +00:00
Philip Allgaier
e841bf89be Add My HA link to about page to Github issue template (#12761) 2022-05-24 08:42:34 -05:00
Philip Allgaier
36e1203fb1 Adjust path to version info in issue template (#12760) 2022-05-24 13:10:26 +00:00
J. Nick Koston
3acab5a39c Adjust logbook stream consumer to handle new metadata (#12755) 2022-05-23 22:37:45 -07:00
Zack Barett
49cfde1fe7 Bumped version to 20220523.0 (#12756) 2022-05-23 16:26:00 -07:00
Philip Allgaier
49c018c000 Allow setting device_class "outlet" again through entity settings (#12669)
* Allow setting `device_class` "outlet" again through UI

* Fixes

* Null check deviceClass and adjust used translation
2022-05-23 18:18:08 -05:00
David F. Mulcahey
b71b230bfd Make entities and devices independent in the scene editor (#11046)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Erik <erik@montnemery.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-05-23 23:08:44 +00:00
Allen Porter
e1fd7244a5 Open Application Credentials from integration configuration flow (#12708) 2022-05-23 15:46:34 -07:00
J. Nick Koston
067c2fdfa8 Use new logbook streaming websocket api for cases where we need end_time (#12753) 2022-05-23 15:40:05 -07:00
J. Nick Koston
a02b817d7f Use new localized context state and source in logbook (#12742) 2022-05-23 14:32:11 -05:00
Bram Kragten
7db6e0b779 Move preload_stream setting to entity settings (#12730)
Co-authored-by: Zack <zackbarett@hey.com>
2022-05-23 18:30:57 +00:00
Joakim Sørensen
1d5cc91a2d Remove kernel and agent versions from about page (#12750) 2022-05-23 13:01:59 -05:00
Joakim Sørensen
0623e7dce4 Fetch supervisor info directly (#12751) 2022-05-23 13:00:16 -05:00
J. Nick Koston
da106d278c Use logbook livestream when requesting a time window that includes the future (#12744) 2022-05-23 12:58:50 -05:00
Joakim Sørensen
51c5ab33f0 Stop closed event when selecting datadisk (#12749) 2022-05-23 12:58:36 +02:00
Paulus Schoutsen
8ac4a6d900 Bumped version to 20220521.0 2022-05-20 17:28:06 -07:00
Paulus Schoutsen
fae1bcf0e0 Fixes logbook (#12740) 2022-05-20 11:25:19 -07:00
Allen Porter
9a9eec40b2 Add an application credentials display name (#12720)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-05-19 21:27:43 -07:00
Bram Kragten
6ab19d66d5 Add option to compare energy graphs with previous period (#12723)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-05-20 04:20:18 +00:00
J. Nick Koston
a0a7ce014f Compute the icon based on the logbook state and not the current state (#12725)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-19 21:12:17 -07:00
Paulus Schoutsen
bfeb90780f Pass device ID to logbook if available (#12728) 2022-05-20 04:09:33 +00:00
J. Nick Koston
1f105b6c15 Get attributes from first state when using minimal responses (#12732) 2022-05-19 20:56:11 -07:00
Raman Gupta
5b7b0ea326 Use device_id instead of config entry id and node id for zwave_js (#12658)
* Use device_id instead of config entry id and node id for zwave_js

* Add additional cleanup from #12642

* Revert removal of multiple config entries check

* Update src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Update src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-05-19 10:23:16 -07:00
Raman Gupta
32a991989f Update zwave_js data collection URL (#12666) 2022-05-19 17:05:31 +02:00
Allen Porter
788f76ab9c Add error handling for application credentials removal (#12686) 2022-05-19 16:51:33 +02:00
Yosi Levy
f6411dce66 Select + target picker Rtl fixes (#12711) 2022-05-19 16:28:56 +02:00
Yosi Levy
6f19ea1d84 Various RTL fixes (#12721) 2022-05-19 16:25:30 +02:00
Michael Irigoyen
448609533f Update Material Design Icons to v6.7.96 (#12111) 2022-05-19 16:21:00 +02:00
J. Nick Koston
6c48ace41e Fix python to js timestamp conversions in logbook traces (#12677)
- The websocket version needs the time converted from
  where python stores the decimal
2022-05-18 12:36:08 -07:00
Paulus Schoutsen
c41e100c1c Bumped version to 20220518.0 2022-05-18 12:10:42 -07:00
RoboMagus
8216b522c2 Fix 'loading_log' string (#12712) 2022-05-18 12:09:31 -07:00
Paulus Schoutsen
82035d587a Import all date-fns from modules (#12717) 2022-05-18 12:09:25 -07:00
J. Nick Koston
2796c3570a Support requesting multiple integration manifests in a single request (#12706)
* Support requesting multiple integration manifests in a single request

* only fetch if there are some to actually fetch

* handle empty

* not truthy, wrong language

* Do not copy params

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-05-18 12:09:09 -07:00
J. Nick Koston
f4f51e1de5 Show the integration brand icon when there is no entity in logbook (#12713) 2022-05-18 12:01:09 -07:00
J. Nick Koston
af6b0d3266 Support requesting translations for multiple integrations in one request (#12704)
* Support requesting translations for multiple integrations in one request

- Requires https://github.com/home-assistant/core/pull/71979

* onboarding as well

* integrations -> integration

* fix cache

* short return if they are all loaded

* reduce

* reduce

* reduce
2022-05-18 11:37:47 -07:00
Paulus Schoutsen
7d1c77a38f Add support for OAuth2 callback via My (#12718) 2022-05-18 11:18:43 -07:00
J. Nick Koston
f807618f75 Convert history calls to use new websocket endpoint (#12662) 2022-05-18 10:20:38 -07:00
Steve Repsher
4cfb6713cb Delete focus targets for replaced dialogs (#12724) 2022-05-18 16:18:22 +00:00
Patrick ZAJDA
d32f84f28d Add missing labels in energy dashboard settings (#12722)
Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
2022-05-18 18:17:31 +02:00
Paulus Schoutsen
5fb1504211 Add logbook to area info page (#12715) 2022-05-17 12:20:49 -07:00
Paulus Schoutsen
c37e1f0c9d Add logbook to device info page (#12714) 2022-05-17 11:02:23 -07:00
Paulus Schoutsen
90c234ffad Refactor logbook data fetch logic into reusable class (#12701) 2022-05-17 08:53:22 -07:00
breakthestatic
dd3a3ec586 Add guard logic from PR home-assistant#12181 to input select row (#12703) 2022-05-17 10:25:32 +00:00
Zack Barett
6f67da09c0 Show manage cloud link to config (#12673) 2022-05-17 12:14:43 +02:00
Franck Nijhof
ba27c184f6 Add my support for Application Credentials (#12709) 2022-05-17 12:13:46 +02:00
Paulus Schoutsen
b37f97128a Fix float-end for LTR (#12707) 2022-05-17 08:20:19 +02:00
Bram Kragten
ee0de942f7 Bumped version to 20220516.0 2022-05-16 20:37:50 +02:00
Steve Repsher
ae2d48f2f4 Return focus after dialogs close (#11999) 2022-05-16 17:10:41 +02:00
Yosi Levy
1bd760b455 Rtl changes (#12693) 2022-05-16 15:57:14 +02:00
Joakim Sørensen
3d66a68791 Guard for missing backup integration (#12696) 2022-05-16 13:39:41 +02:00
J. Nick Koston
01a53439c4 Teach logbook about additional context data (#12667)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-05-15 21:25:55 -07:00
Sven
09ee8dbeb6 Update Lokalise URL (#12684) 2022-05-15 17:59:31 +02:00
Philip Allgaier
f36c91550d Add missing label to search icon (#12671) 2022-05-13 18:58:01 -04:00
Franck Nijhof
6be6c711d0 Fix strict error handling in Markdown card templates (#12661) 2022-05-13 13:17:56 +02:00
Allen Porter
72a36fb1cd Add calendar trigger offsets in automation editor (#12486)
* Add calendar trigger offsets in automation editor

* Use duration selector for offset

* Fix typing for offsets/duratons
2022-05-12 07:42:15 -05:00
J. Nick Koston
4c982b3323 Switch logbook calls to use the new websocket (#12665) 2022-05-11 22:28:18 -05:00
Yosi Levy
c9c3be71cc Merge pull request #12620 from yosilevy/RTL-no-host-context
Replace host-context with css properties - after session with Bram
2022-05-11 16:30:21 +03:00
Bram Kragten
f1b965dcc5 Update ha-fab.ts 2022-05-11 15:19:03 +02:00
Bram Kragten
a08a23a93d Use FabBase 2022-05-11 14:25:43 +02:00
Bram Kragten
2040a49458 Update var name 2022-05-11 14:21:02 +02:00
Bram Kragten
df94f4f907 Merge branch 'dev' into RTL-no-host-context 2022-05-11 14:18:26 +02:00
Bram Kragten
96d375cb84 Use / 2022-05-11 14:16:44 +02:00
Yosi Levy
7a9c2f56c5 Rtl menu fix (#12561)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-11 11:01:45 +02:00
J. Nick Koston
5ec7193e5c Show script traces in logbook (#12643) 2022-05-10 23:32:09 +02:00
Zack Barett
d89e4337f2 Hide Cloud URL - Add Copy Icon (#12655)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-10 19:37:31 +00:00
Zack Barett
2e192d5021 Update Translations to create helper (#12656) 2022-05-10 21:25:03 +02:00
Yosi Levy
7db28c0156 Update following review 2022-05-10 19:31:23 +03:00
Yosi Levy
f09c842981 Update src/state/translations-mixin.ts
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-10 18:25:28 +03:00
Yosi Levy
b295bbd706 RTL settings clickable list item fix (#12595) 2022-05-10 16:57:18 +02:00
Patrick ZAJDA
8d3132fefc Add label for Fix issue column header in statistics developer tools (#12597)
Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
2022-05-09 17:14:59 +02:00
Allen Porter
00c5d3dbbb Add configuration panel for Application Credentials (#12344)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Zack <zackbarett@hey.com>
2022-05-09 17:03:59 +02:00
Zack Barett
ca37aff47d Move YAML to first tab of Developer Tools (#12589) 2022-05-09 08:07:17 -05:00
Joakim Sørensen
9ed069ef6a Get full core logs from core (#12639) 2022-05-09 08:07:01 -05:00
Paulus Schoutsen
1b158d8310 Merge pull request #12619 from home-assistant/cherry-pick-search
20220504.1
2022-05-07 14:14:39 -07:00
Yosi Levy
6c73ae5bf7 Replace host-context with css properties 2022-05-07 06:39:39 +03:00
Zack
9d2fcec458 Version Bump 2022-05-06 22:30:34 -05:00
Zack Barett
60cd6c65f0 Revert #10991 (#12618) 2022-05-06 22:00:01 -05:00
Bram Kragten
a39af9c307 Merge pull request #12582 from home-assistant/dev 2022-05-04 13:28:04 +02:00
Paulus Schoutsen
02af4c2156 Bump Master to 20220503.0 (#12567)
* Use selectors for add-on configurations (#12234)

* replace ToggleSwitch with new LightSwitch (#12218)

* Fix statistics chart for sum stat without state (#12238)

* Use selectors for add-on network configuration (#12235)

* Use selectors for add-on network configuration

* Show container port as UOM if advanced user

* adjust

* Only show "required" indicator if we have a selector label (#12241)

* Lineup sidebar badges

* Exclude hidden entities from area card

* Fix entity and device selector with `multiple: true`

* Adjust import

* Guard for partial translations (#12296)

* Fix add-on security rating range (#12300)

* Use more text selector types for add-on configuration (#12303)

* Prevent empty brackets if no manufacturer during config entry creation (#12288)

* Fix endless loading screen in zwave-js config (#12295)

* Update cloud text (#12305)

* Select default mode if none set (#12306)

* Decode view path URL (#12310)

* Always render title field (#12319)

* Use new mdi icons for smoke and co detection (#12323)

* Split only on first comma in media browser (#12331)

* Allow tapping on the name on a picture entity card (#12332)

* RTL calendar fix - arrows fix and views fix (#12314)

* RTL calendar fix - arrows fix and views fix

* Removed path attributes

* Quickly search for entities from the Overview Dashboard (#12324)

* Allow selecting multiple entities for state trigger (#12334)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add Template selector (#12348)

* Add basic frontend support for siren (#12345)

* Fix strict error handling in developer tools templates (#12352)

* Bump HAWS to 7.0.3 (#12358)

* Add clear skipped to update more-info dialog (#12361)

* Adding blueprint input description markdown/multi-line support (#12291)

* Github no longer supports the (insecure) git protocol (#12359)

* Add if/else automation/script action (#12301)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add stop script/automation action (#12299)

* Getting started on Configuration Changes (#12309)

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

* Config menu updates to get it ready for nightly (#12368)

* Bumped version to 20220420.0 (#12369)

* Use template selector in wait_template (#12366)

* Add jinja2 editor to template triggers/conditions (#12365)

Co-authored-by: Zack <zackbarett@hey.com>

* Fix for monetary entities (#12378)

* Add entity search tip to dev-tools set state (#12355)

* Added ability to retry on initialization errors. (#12103)

* RTL fixes (#12367)

* zwave_js: Add title tag to config box heading (#12387)

* Accept new value when hitting ENTER to close a prompt dialog (#12360)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* RTL reading orders and alignments in system log (#12388)

* Add automation editor for calendar trigger (#12343)

* Add calendar event end trigger to automation editor (#12389)

* Show vacuum state in more-info dialog for StateVacuumEntity (#12391)

* Add Empty list item for None (#12356)

* Force LTR on time & number inputs (#12393)

* Fix Dashboard URLs (#12394)

* Update zwavejs controller model (#12390)

* Configuration Menu Updates 3 (#12377)

* Bumped version to 20220424.0

* Config Menu: Addressing Comments in #12377 (#12399)

* Add shorthand condition to the gallery (#12400)

* Virtualize Media Player Grid (#11898)

* Hide supervisor only config, fix backup config page (#12401)

* Fix broken cards being able to crash entire view (#11440)

* Add supervisor network interface settings (#12403)

* Fix zones (#12409)

* Add supervisor hostname config (#12407)

* Add Hardware Page to Configuration System Menu (#12405)

* Add Supervisor logs to core page (#12410)

* Allow Showing Skipped Updates on Updates Page (#12415)

* Configuration Menu Cleanup items (#12413)

* Backup Page - Will load which is available (#12414)

* Move System Health to a page (#12412)

* Show what updates are skipped (#12418)

* Don't show tabs in supervisor (#12417)

* Better gauge segment coloring (#11570)

* Move Data Disk Moving to Storage (#12416)

* Add supervisor, OS version info to about page (#12421)

* Add supervisor, OS version info to about page

* description

* description

* Allow for checking for updates (#12422)

* Fix title and description for menu step in options flow (#12420)

* link to updates page (#12423)

* Show usage stats in System Health (#12424)

* Bumped version to 20220425.0 (#12425)

* Format sensors with state class duration (#12426)

* Guard against non OS installation (#12427)

* Typo in en.json (#12428)

* Move unsupported and unhealthy alerts (#12431)

* Fix log syntax highlight when fetching logs from supervisor (#12430)

* Resources lovelace should just go back (#12432)

* Redirect hassio system my links to new locations (#12429)

* Fix backup back path (#12435)

* Add join/leave beta to updates panel (#12436)

* Fix settings row width (#12438)

* Dont show tabs when less than 2 (#12439)

* Set border radius in config to 8px (#12437)

* Fix incorrect text if no backups are found (#12441)

* Add header to supervisor backups page (#12444)

* Fix content display for `ha-network` after #12438 (#12445)

* Fix content display for `ha-network` after #12438

* Add var default

* Add title to backups config page (#12442)

* Fix integration page on mobile (#12447)

* Add "m" keyboard shortcut to get to the create my link page (#12451)

* Terms based entities search (#10991)

* Small edits on config menu (#12440)

* Fix for backup overflow (#12454)

* Update the hint for key C (#12458)

* Fix when creating new area in picker #11392 (#12457)

* Fix more info input number #12396 (#12456)

* Update Configuration badge color to be accent color to match (#12455)

* Move Provider Selection to Menu on top header (#12443)

* Move the analytics link (#12459)

* Fix Updates Page Toast - Move to overflow (#12453)

* Move Zones Edit to General config + add general config page (#12452)

* Move Zones Edit to General config + add general

* Update src/translations/en.json

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

* add paper tooltip back for yaml

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

* Tweak menu descriptions (#12460)

* Fix my link for config dashboard and profile (#12461)

* Fix my link for config dashboard and profile

* add server control redirect

Co-authored-by: Zack <zackbarett@hey.com>

* Fix icon alignment in nav list (#12463)

* Add a tip for my shortcut (#12462)

* Move Restart to Overflow and yaml config advanced (#12446)

* Move Restart to Overflow and yaml config advanced

* Move around YAML Config page

* Move to developer tools

* Make card actions

* Update Translations

* Bumped version to 20220427.0

* Use correct label for update config menu (#12465)

* Make helper option button more user friendly (#12468)

* Add hass-quick-bar-trigger event to trigger quickbar from supervisor (#12467)

* Use startsWith for m shortcut for partial match (#12464)

* Add supervisor redirects to m keyboard shortcut (#12466)

* Safeguard against non-existant area in device handling (#12475)

* RTL fix for log buttons (#12474)

* Fix YAML Config Invalid button (#12476)

* Small config fixes (#12472)

* Visual tweaks to YAML validation results (#12479)

* Add some bottom padding to YAML conf dev tools page (#12477)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Fix Restarting Home Assistant (#12480)

* Fix Restarting Home ASsistant

* Update src/panels/config/core/ha-config-system-navigation.ts

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

* Update src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts

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

* reviews

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

* Move General Up in the system menu (#12483)

* Media panel fix (#12485)

* add my redirects for new config pages (#12481)

* Add template editor to Markdown card editor (#12490)

* Address minor comments about config menu (#12492)

* Hide and sort secondary device automations (#12496)

* Evaluate condition shorthands in editors (#12473)

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

* Add support for enabling/disabling trigger/condition/action (#12493)

* Add support for enabling/disabling trigger/condition/action

* Add more visual indication of disabled

* review

* margin

* Dont make overflow transparent

* Change color of bar

* Add parallel automation/script action (#12491)

* Add Board Names, Move All Hardware (#12484)

Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Change Restart to be a button, update dialogs (#12499)

* Bumped version to 20220428.0 (#12501)

* Fix Wrap menu and remove menu title (#12505)

* form-string password fix (#12507)

* Use media query for config menu mobile (#12510)

* Fix incorrect 3-dot menu labels (config hardware & storage) (#12512)

* Media browser RTL fixes (#12506)

* Fix `continue_on_timeout` default on `wait_template` automation visual editor (#12511)

* Support shorthand logical operators in script sequences (#12509)

* Only show Card Content if OS exist (#12513)

* Add condition shorthand to action types (#12514)

* Fix for external url not logged into cloud (#12516)

* Restart Home ASsistant button - Make less red and less big (#12515)

* Add actions to design gallery (#12518)

* Add actions to design gallery

* Update describe-action.ts

* Move integrations to System Health (#12504)

* Add if, parallel and stop action to trace graph (#12520)

* Bumped version to 20220429.0 (#12521)

* Change color of persons for real this time (#12527)

* Ignore modifier keys when forwarding events to quickbar (#12525)

* Add optional repository_url to supervisor_addon my link (#12524)

* Calendar-card fix (#12532)

* Handle condition shorthands in trace graphs (#12533)

* Make the "Aborted: Reauthentication successful" more user friendly (#12530)

Replace the "Aborted" in the title with the integration name to make the user error
messages more user friendly. The message itself ("Reauthentication successful" or "Missing configuraiton, etc) error
message is descriptive enought that we can replace the title with the integration
name and still preserve the meeting. The advance is that this doesn't confuse users
who are surprised by it saying "Aborted" when things were successful

https://github.com/home-assistant/core/issues/47135

* Prevent color temp selector mired exception (#12536)

* Fix some issues and feedback with About and system health (#12537)

* Add descriptions for actions (#12541)

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

* Add repeat to trace timeline (#12547)

* Change name to Settings (#12548)

* Add trace timeline for if (#12543)

* Fix script graph parallel (#12545)

* Handle if in repeat (#12544)

* Add parallel action to trace timeline (#12549)

* Indicate things are disabled in trace graph (#12550)

* Indicate things are disabled in trace graph

* Update hat-script-graph.ts

* Bumped version to 20220502.0

* Add add-on logs to log selector (#12556)

* Fix Webhook Overflow (#12551)

* Search in Overflow on Mobile (#12552)

* Use ha-tip for yaml move tip (#12559)

* Update Quickbar Section Logic to include all (#12553)

* Use outline for cards on config pages (#12558)

* Add supervisor redirects to quickbar (#12557)

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

* Fix searching in hassio logs (#12560)

* Add disabled support to trace timeline and step details (#12555)

* Add new system menu descriptions (#12564)

* Add missing outlined to supervisor panel (#12565)

* Bumped version to 20220503.0 (#12566)

Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Marius <33354141+Mariusthvdb@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Kuba Wolanin <hi@kubawolanin.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Yosi Levy <37745463+yosilevy@users.noreply.github.com>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: Simon Vallières <simon@vallieres.ca>
Co-authored-by: Eric Stern <stormalong@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Wesley Vos <17592840+Wesley-Vos@users.noreply.github.com>
Co-authored-by: Mark Lopez <m@silvenga.com>
Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: yangqian <yanyangqian@gmail.com>
Co-authored-by: Thomas Lovén <thomasloven@gmail.com>
Co-authored-by: Netzwerkfehler <16437929+Netzwerkfehler@users.noreply.github.com>
Co-authored-by: Artem Sorokin <artem@sorokin.pp.ru>
Co-authored-by: Jaroslav Hanslík <kukulich@kukulich.cz>
Co-authored-by: Johann Vanackere <johann.vanackere@gmail.com>
Co-authored-by: Bruno Maia <bruno.mm.maia@gmail.com>
2022-05-03 11:14:32 -07:00
Zack Barett
d02cd122a9 Merge pull request #12233 from home-assistant/dev
Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-04-05 18:02:02 -05:00
Joakim Sørensen
8e962fdecb Use installed_version for update entities (#12194) 2022-04-01 19:29:26 +02:00
Bram Kragten
1f65193a97 Merge pull request #12193 from home-assistant/dev 2022-04-01 18:56:12 +02:00
Bram Kragten
24484d0e74 20220330.0 (#12165)
* Add a docs icon to the config flow dialog

* Use same help icon everywhere

* Fix quickbar overlaying, fix click handling (#11900)

* Supervisor mobile click accessibility (#11915)

* Convert objects to string in config flow error (#11908)

* Fix datepicker triangle (#11920)

* Always show tab labels (#11919)

* Remove zwave and ozw panels (#11911)

Remove zwave and ozw panels

* Convert lovelace config dialogs to ha-form (#11910)

* Guard setting up config flow for an unsupported domain (#11937)

* Show triggered vars on click (#11924)

* Allow marking YAML editor as read only (#11960)

* Convert inputs (#11907)

* Convert inputs

* Update dialog-thingtalk.ts

* imports

* Remove some additional old zwave code (#11941)

* Correct media upload error + add file name (#11949)

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

* Fix humidifier more info mode dropdown (#11964)

* Make min width of select configurable (#11965)

* Fix for Statistics Editor (#11942)

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

* A11y expansion panel (#11967)

* Convert file upload to mdc (#11906)

* Add location selector, convert zone editor (#11902)

* Add systemd_resolved unsupported reason (#11971)

* replace default switch icon 

to  make it stand out against a power entity which uses the same mdiFlash https://github.com/home-assistant/core/issues/67620#issuecomment-1061949527

suggest the Outline version, so create a subtle difference with the on/off icons.

* Allow selecting multiple entities (#11986)

* Fix theme setting (#11977)

* Update Style of Design Page (#11982)

* change icon to mimic physical device

and follow comments

* Use entities-picker in entity selector (#11990)

* #11971 Change order of alarm panel buttons (#11998)

* Fix zwave_js 'add/remove device' disabled bug (#12000)

* Fix zwave_js 'add/remove device' disabled bug

* revert extra change

* Fix zwave_js set config dropdown default value (#11974)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Fix changing cost number in energy settings (#12009)

* Fix Dashboard Editing (#12011)

* Fix For Selecting Device Class (#12010)

* Fix: Allow for deleting Input_select options (#12007)

* Script ID update with Alias (#12008)

* HAWS 6.1 (#12016)

* Bumped version to 20220301.1

* Bumped version to 20220301.2

* Bumped version to 20220312.0

* Add shade to device class overrides (#11874)

* Fix: Changing Blueprint Automation Name (#12036)

* Fix @changed where using ev.detail (#12043)

* Add all cover device classes (#12042)

* Rename Lovelace Dashboard to just Dashboard (#12044)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add Color Temp Selector (#12041)

* Utilize Hide Hidden Entities

* Reviews

* add to demo

* Add `Brand` folder and `Our story` page (#11978)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add HA to public folder and show in markdown

* Update Translations

* Disabled by

* remove 1

* Add Description of chosen

* Add icons and buttons

* Add Color RGB Selector (#12039)

* Add Date Selector

* Add ha-form context (#12062)

* test condition (#11925)

* Revamp URL form (#12060)

* Add support for menu data entry flow option (#12055)

* Add translation

* add to basic editor and update advanced style

* clean up

* Entity Status

* Add Devices Picker (#12056)

* Remvoe redunency

* Bumped version to 20220316.0

* Bump HAWS to 7.0.0 (#12067)

* Create new Logo page

* Add files via upload

* Ignore diagnostics not found exceptions (#12066)

* Bump HAWS to 7.0.1

* Update lock

* Add Date Time Selector (#12070)

* Add radio Form Logic to Select Selector (#12063)

* Bumped version to 20220317.0 (#12074)

* Update gallery/src/pages/brand/logo.markdown

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Update gallery/src/pages/brand/logo.markdown

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Update logo.markdown

* Fetch history with `no_attributes` for entities that do not need them (#12082)

* Update required version of MDI to 6.6.95

* Upload release assets (#11566)

Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>

* Update styles for hui-editor

Update the background-color and text-color of the app-toolbar in
hui-editor to match the styles of hui-root while in edit-mode.

Previously, these properties were set using undefined css variables that
could not be changed via themes (--dark-background-color and
--dark-text-color).

* Fix gas energy graph units if stats added by external source (#11892)

* Change Netlify preview URL (#12095)

* Update src/dialogs/config-flow/dialog-data-entry-flow.ts

* Stack Action Inputs in the Button Editor (#12076)

* Stack Action Inputs in the Button Editor

* update style

* Update for other editors

* Add support for update entities (#12059)

* Add support for update entities

* Apply suggestions from code review

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add to gallery

* implement xx%

* Adjustments for skipped

* Add progress bar

* Add UPDATE_SUPPORT_INSTALL

* Allow skipping without install support

* Add version to service call if supported

* Adjust changelog link

* Use Installing

* adjustments

* Use unavailable

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add support for integration type (#12077)

* Update When entity can change enabled or hidden (#12096)

* Add entity include and exclude to selector (#12078)

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

* change from hidden to not shown (#12097)

* Add statistic adjust dialog (#12101)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Fix Duration Selector Default (#12098)

* Fix Duration Default

* USe initial form data function

* Bumped version to 20220322.0 (#12102)

* Create user types page and rename the category (#12089)

Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Remove `setup.py` (#11593)

* Fix selecting 0 with number selector

* Update lock file with MDI updates

* Use update entities for showing updates on configuration panel (#12100)

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

* Fix loading traces for automation with custom id (#12112)

* Only show docs link when showing a form

* Exclude restored automations from dashboard (#12113)

* Support descriptions in flow menu steps (#12108)

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

* Sort selectors (#12120)

* Update type for backend (#12122)

* Fix issue where theme select does not appear when user's theme is deleted (#12104)

* Fix possibility to enable entity disabled by integration (#12121)

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Allow rendering helper text from strings.json (#12119)

* Allow rendering helper text from strings.json

* Persistent helpers

* Update src/components/ha-base-time-input.ts

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Update src/components/ha-base-time-input.ts

Co-authored-by: Zack Barett <zackbarett@hey.com>

* Add Day to duration selector (#12125)

* Add variables to automation trigger type

* Fix z-index map, always set icon for location selector (#12137)

* Make padding on settings row content consistent (#12139)

* Add Area Multiple Selector option (#12138)

* break theme picker out of lovelace (#12140)

* Allow binary sensor device class updates (#12124)

* Add selector initial values (#12142)

* Add badge to configuration sidebar to indicate pending updates (#12146)

* Bumped version to 20220329.0 (#12152)

* Add entity source API (#12149)

* Update adjust statistic dialog (#12118)

* Update text for adjust statistic dialog

* Change everything

* Import type

* Max show 5

* Revert back the API change

* Hide adjust button if no sum

* Adjustments

* Update src/panels/developer-tools/statistics/developer-tools-statistics.ts

* Render optional

Co-authored-by: Zack <zackbarett@hey.com>

* Fetch release notes for update entities that provides it (#12148)

* Fetch release notes for update entities that provides it

* lint

* Add support for new timer properties (#11940)

* Fix theme settings on design page (#12154)

* Allow ha-alert to be used in our markdown render (#12153)

* Allow device_tracker entities to use state_color (#12127)

* Automation description text overflow (#12040)

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

* Update Pickers and selectors with required (#12151)

* Update Pickers and selectors with required

* Use native * for device and entity

* Add support for my links to create a helper config entry (#12155)

* Use brand icon instead of domain icon for helpers (#12157)

* Import components that are allowed to be defined in markdown (#12158)

* Add options to selectors gallery (#12156)

* Add helpers to list when searching in add integration (#12159)

* List Selector (#12099)

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

* Add shuffle and repeat-mode of media_player to UI (#12052)

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

* Add panel to Backup integration (#11671)

Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Fix for Mobile View of Entities Table (#12160)

* Allow Sensor Units to be updated via Entity Registry (#12143)

* Add switch as x to entity settings (#12161)

Co-authored-by: Zack <zackbarett@hey.com>

* Bumped version to 20220330.0 (#12164)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
Co-authored-by: Robin Wittebol <robinwittebol@live.nl>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>
Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
Co-authored-by: Marius <33354141+Mariusthvdb@users.noreply.github.com>
Co-authored-by: Emil Stjerneman <emil@stjerneman.com>
Co-authored-by: Charles Garwood <cgarwood@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: jpearl <jpearl@users.noreply.github.com>
Co-authored-by: Matthias de Baat <matthias.debaat@nabucasa.com>
Co-authored-by: Matthias de Baat <hello@matthiasdebaat.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Michael Irigoyen <michael@irigoyen.dev>
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
Co-authored-by: Nick Iacullo <duckycrayfish@gmail.com>
Co-authored-by: Pawel <pszafer@gmail.com>
Co-authored-by: Erik <erik@montnemery.com>
Co-authored-by: Brynley McDonald <brynley+github@zephire.nz>
Co-authored-by: blair <1585872+blairun@users.noreply.github.com>
Co-authored-by: NachtaktiverHalbaffe <57433516+NachtaktiverHalbaffe@users.noreply.github.com>
2022-03-30 20:49:00 +02:00
291 changed files with 10205 additions and 4393 deletions

View File

@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!-- <!--
Provide details about the versions you are using, which helps us reproducing Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the and finding the issue quicker. Version information is found in the
Home Assistant frontend: Configuration -> Info. Home Assistant frontend: Settings -> About.
Browser version and operating system is important! Please try to replicate Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings. your issue in a different browser and be sure to include your findings.

View File

@@ -64,7 +64,7 @@ body:
label: What version of Home Assistant Core has the issue? label: What version of Home Assistant Core has the issue?
placeholder: core- placeholder: core-
description: > description: >
Can be found in the Configuration panel -> Info. Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
- type: input - type: input
attributes: attributes:
label: What was the last working version of Home Assistant Core? label: What was the last working version of Home Assistant Core?

8
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10

View File

@@ -19,9 +19,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -43,9 +43,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -62,9 +62,9 @@ jobs:
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -81,9 +81,9 @@ jobs:
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@@ -9,7 +9,7 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v2.0.1 - uses: dessant/lock-threads@v3.0.0
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-lock-inactive-days: "30" issue-lock-inactive-days: "30"

63
.github/workflows/nightly.yaml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 1 * * *"
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
permissions:
actions: none
jobs:
nightly:
name: Nightly
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Bump version
run: script/version_bump.js nightly
- name: Build nightly Python wheels
run: |
pip install build
yarn install
script/build_frontend
rm -rf dist home_assistant_frontend.egg-info
python3 -m build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error

View File

@@ -24,18 +24,18 @@ jobs:
contents: write # Required to upload release assets contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -74,33 +74,11 @@ jobs:
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Upload requirements.txt
uses: actions/upload-artifact@v2
with:
name: requirements
path: ./requirements.txt
build-wheels:
name: Build wheels for ${{ matrix.arch }}
needs: wheels-init
runs-on: ubuntu-latest
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.14"
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
with:
name: requirements
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@master uses: home-assistant/wheels@2022.06.7
with: with:
tag: ${{ matrix.tag }} abi: cp310
arch: ${{ matrix.arch }} tag: musllinux_1_2
wheels-host: ${{ secrets.WHEELS_HOST }} arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt" requirements: "requirements.txt"

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Upload Translations - name: Upload Translations
run: | run: |

2
.vscode/tasks.json vendored
View File

@@ -181,7 +181,7 @@
{ {
"label": "Run HA Core for Supervisor in devcontainer", "label": "Run HA Core for Supervisor in devcontainer",
"type": "shell", "type": "shell",
"command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core", "command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core",
"isBackground": true, "isBackground": true,
"group": { "group": {
"kind": "build", "kind": "build",

View File

@@ -26,8 +26,8 @@ module.exports = {
}, },
version() { version() {
const version = fs const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8") .readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W(\d{8}\.\d)/); .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
if (!version) { if (!version) {
throw Error("Version not found"); throw Error("Version not found");
} }

View File

@@ -156,3 +156,12 @@ gulp.task("gen-icons-json", (done) => {
done(); done();
}); });
gulp.task("gen-dummy-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
done();
});

View File

@@ -9,6 +9,7 @@ require("./compress.js");
require("./rollup.js"); require("./rollup.js");
require("./gather-static.js"); require("./gather-static.js");
require("./translations.js"); require("./translations.js");
require("./gen-icons-json.js");
gulp.task( gulp.task(
"develop-hassio", "develop-hassio",
@@ -17,6 +18,7 @@ gulp.task(
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean-hassio", "clean-hassio",
"gen-dummy-icons-json",
"gen-index-hassio-dev", "gen-index-hassio-dev",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
@@ -33,6 +35,7 @@ gulp.task(
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean-hassio", "clean-hassio",
"gen-dummy-icons-json",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data", "build-locale-data",

9
cast/public/_redirects Normal file
View File

@@ -0,0 +1,9 @@
# These redirects are handled by Netlify
#
# Some custom cards are not prefixing the instance URL when fetching data
# and can end up fetching the data from the Cast domain instead of HA.
# This will make sure that some common ones are replaced with a placeholder.
/api/camera_proxy/* /images/google-nest-hub.png
/api/camera_proxy_stream/* /images/google-nest-hub.png
/api/media_player_proxy/* /images/google-nest-hub.png

View File

@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "state-icon", type: "state-icon",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "group.downstairs_lights", entity_id: "group.downstairs_lights",
}, },
service: "homeassistant.toggle", service: "homeassistant.toggle",

View File

@@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
hidden: true, hidden: true,
radius: 50, radius: 50,
friendly_name: "Skolan", friendly_name: "School",
icon: "mdi:school", icon: "mdi:school",
}, },
}, },
@@ -137,7 +137,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "73", state: "73",
attributes: { attributes: {
unit_of_measurement: "%", unit_of_measurement: "%",
friendly_name: "oskar batteri", friendly_name: "Oskar battery",
device_class: "battery", device_class: "battery",
}, },
}, },
@@ -146,7 +146,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "88", state: "88",
attributes: { attributes: {
unit_of_measurement: "%", unit_of_measurement: "%",
friendly_name: "bella batteri", friendly_name: "Bella battery",
device_class: "battery", device_class: "battery",
}, },
}, },
@@ -154,7 +154,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.unifi_camera", entity_id: "binary_sensor.unifi_camera",
state: "off", state: "off",
attributes: { attributes: {
friendly_name: "R\u00f6relsesensor kamera", friendly_name: "Motion sensor camera",
icon: "mdi:walk", icon: "mdi:walk",
}, },
}, },
@@ -707,7 +707,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
}, },
], ],
cloudiness: 25, cloudiness: 25,
friendly_name: "V\u00e4der", friendly_name: "Weather",
}, },
}, },
"binary_sensor.ubiquiti_switch": { "binary_sensor.ubiquiti_switch": {
@@ -731,7 +731,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
round_trip_time_max: "0.626", round_trip_time_max: "0.626",
round_trip_time_mdev: "", round_trip_time_mdev: "",
round_trip_time_min: "0.358", round_trip_time_min: "0.358",
friendly_name: "Entr\u00e9 kamera", friendly_name: "Entrance camera",
device_class: "connectivity", device_class: "connectivity",
icon: "mdi:cctv", icon: "mdi:cctv",
}, },
@@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 34, battery_level: 34,
on: true, on: true,
friendly_name: "altan_motion_sensor", friendly_name: "Porch motion sensor",
device_class: "motion", device_class: "motion",
}, },
}, },
@@ -807,7 +807,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 88, battery_level: 88,
on: true, on: true,
friendly_name: "Altand\u00f6rren sensor", friendly_name: "Back door sensor",
device_class: "opening", device_class: "opening",
icon: "mdi:door", icon: "mdi:door",
}, },
@@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 74, battery_level: 74,
on: true, on: true,
friendly_name: "badrumssensor", friendly_name: "Bathroom motion sensor",
device_class: "motion", device_class: "motion",
}, },
}, },
@@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47, battery_level: 47,
on: true, on: true,
dark: true, dark: true,
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1", friendly_name: "Basement motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@@ -841,7 +841,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60, battery_level: 60,
on: true, on: true,
dark: true, dark: true,
friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan", friendly_name: "Laundy room motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: { attributes: {
battery_level: 60, battery_level: 60,
on: true, on: true,
friendly_name: "R\u00f6relsesensor skafferiet", friendly_name: "Pantry motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60, battery_level: 60,
on: true, on: true,
dark: true, dark: true,
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2", friendly_name: "Stair motion sensor",
device_class: "motion", device_class: "motion",
icon: "mdi:walk", icon: "mdi:walk",
}, },
@@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47, battery_level: 47,
on: true, on: true,
dark: true, dark: true,
friendly_name: "B\u00e4nksensor", friendly_name: "Bench sensor",
device_class: "motion", device_class: "motion",
}, },
}, },

View File

@@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
], ],
show_header_toggle: false, show_header_toggle: false,
type: "entities", type: "entities",
title: "Bandbredd", title: "Bandwidth",
}, },
// { // {
// title: "Updater", // title: "Updater",

View File

@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed", name: "AC bed",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "script.air_cleaner_quiet", entity_id: "script.air_cleaner_quiet",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed", name: "AC bed",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "script.air_cleaner_auto", entity_id: "script.air_cleaner_auto",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed", name: "AC bed",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "script.air_cleaner_turbo", entity_id: "script.air_cleaner_turbo",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC", name: "AC",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "script.ac_off", entity_id: "script.ac_off",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC", name: "AC",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "script.ac_on", entity_id: "script.ac_on",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.morning_lights", entity: "scene.morning_lights",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "scene.morning_lights", entity_id: "scene.morning_lights",
}, },
service: "scene.turn_on", service: "scene.turn_on",
@@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.movie_time", entity: "scene.movie_time",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "scene.movie_time", entity_id: "scene.movie_time",
}, },
service: "scene.turn_on", service: "scene.turn_on",
@@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.downstairs_lights", entity: "light.downstairs_lights",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "light.downstairs_lights", entity_id: "light.downstairs_lights",
}, },
service: "light.toggle", service: "light.toggle",
@@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.upstairs_lights", entity: "light.upstairs_lights",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
service_data: { data: {
entity_id: "light.upstairs_lights", entity_id: "light.upstairs_lights",
}, },
service: "light.toggle", service: "light.toggle",

View File

@@ -1,7 +1,7 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => { export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [ hass.mockAPI("config/config_entries/entry?domain=co2signal", () => [
{ {
entry_id: "co2signal", entry_id: "co2signal",
domain: "co2signal", domain: "co2signal",

View File

@@ -1,4 +1,4 @@
import { format, startOfToday, startOfTomorrow } from "date-fns"; import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
import { EnergySolarForecasts } from "../../../src/data/energy"; import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";

View File

@@ -4,7 +4,7 @@ import {
addMonths, addMonths,
differenceInHours, differenceInHours,
endOfDay, endOfDay,
} from "date-fns"; } from "date-fns/esm";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history"; import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
@@ -466,6 +466,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results; return results;
} }
); );
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
mockHass.mockWS("history/list_statistic_ids", () => []); mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS( mockHass.mockWS(
"history/statistics_during_period", "history/statistics_during_period",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
service_data: {}, data: {},
target: { target: {
entity_id: ["input_boolean.toggle_4"], entity_id: ["input_boolean.toggle_4"],
}, },
@@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
service_data: {}, data: {},
target: { target: {
entity_id: ["input_boolean.toggle_2"], entity_id: ["input_boolean.toggle_2"],
}, },
@@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
service_data: {}, data: {},
target: { target: {
entity_id: ["input_boolean.toggle_3"], entity_id: ["input_boolean.toggle_3"],
}, },
@@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
service_data: {}, data: {},
target: { target: {
entity_id: ["input_boolean.toggle_4"], entity_id: ["input_boolean.toggle_4"],
}, },
@@ -298,11 +298,11 @@ export const basicTrace: DemoTrace = {
source: "state of input_boolean.toggle_1", source: "state of input_boolean.toggle_1",
entity_id: "automation.toggle_toggles", entity_id: "automation.toggle_toggles",
context_id: "6cfcae368e7b3686fad6c59e83ae76c9", context_id: "6cfcae368e7b3686fad6c59e83ae76c9",
when: "2021-03-25T04:36:51.240832+00:00", when: 1616647011.240832,
domain: "automation", domain: "automation",
}, },
{ {
when: "2021-03-25T04:36:51.249828+00:00", when: 1616647011.249828,
name: "Toggle 4", name: "Toggle 4",
state: "on", state: "on",
entity_id: "input_boolean.toggle_4", entity_id: "input_boolean.toggle_4",
@@ -313,7 +313,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode", context_name: "Ensure Party mode",
}, },
{ {
when: "2021-03-25T04:36:51.258947+00:00", when: 1616647011.258947,
name: "Toggle 2", name: "Toggle 2",
state: "on", state: "on",
entity_id: "input_boolean.toggle_2", entity_id: "input_boolean.toggle_2",
@@ -324,7 +324,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode", context_name: "Ensure Party mode",
}, },
{ {
when: "2021-03-25T04:36:51.261806+00:00", when: 1616647011.261806,
name: "Toggle 3", name: "Toggle 3",
state: "off", state: "off",
entity_id: "input_boolean.toggle_3", entity_id: "input_boolean.toggle_3",
@@ -335,7 +335,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode", context_name: "Ensure Party mode",
}, },
{ {
when: "2021-03-25T04:36:51.265246+00:00", when: 1616647011.265246,
name: "Toggle 4", name: "Toggle 4",
state: "off", state: "off",
entity_id: "input_boolean.toggle_4", entity_id: "input_boolean.toggle_4",

View File

@@ -185,11 +185,11 @@ export const motionLightTrace: DemoTrace = {
"has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use", "has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use",
source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use", source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
entity_id: "automation.auto_elgato", entity_id: "automation.auto_elgato",
when: "2021-03-14T06:07:01.768492+00:00", when: 1615702021.768492,
domain: "automation", domain: "automation",
}, },
{ {
when: "2021-03-14T06:07:01.872187+00:00", when: 1615702021.872187,
name: "Elgato Key Light Air", name: "Elgato Key Light Air",
state: "on", state: "on",
entity_id: "light.elgato_key_light_air", entity_id: "light.elgato_key_light_air",
@@ -200,7 +200,7 @@ export const motionLightTrace: DemoTrace = {
context_name: "Auto Elgato", context_name: "Auto Elgato",
}, },
{ {
when: "2021-03-14T06:07:53.284505+00:00", when: 1615702073.284505,
name: "Elgato Key Light Air", name: "Elgato Key Light Air",
state: "off", state: "off",
entity_id: "light.elgato_key_light_air", entity_id: "light.elgato_key_light_air",

View File

@@ -249,7 +249,7 @@ const CONFIGS = [
name: Bed light name: Bed light
action_name: Toggle light action_name: Toggle light
service: light.toggle service: light.toggle
service_data: data:
entity_id: light.bed_light entity_id: light.bed_light
- type: section - type: section
label: Links label: Links

View File

@@ -199,7 +199,7 @@ const CONFIGS = [
tap_action: tap_action:
action: call-service action: call-service
service: light.turn_on service: light.turn_on
service_data: data:
entity_id: light.ceiling_lights entity_id: light.ceiling_lights
- entity: sun.sun - entity: sun.sun
name: Regular name: Regular

View File

@@ -40,7 +40,7 @@ const CONFIGS = [
left: 90% left: 90%
padding: 0px padding: 0px
service: light.turn_off service: light.turn_off
service_data: data:
entity_id: group.all_lights entity_id: group.all_lights
- type: icon - type: icon
icon: mdi:cctv icon: mdi:cctv
@@ -88,7 +88,7 @@ const CONFIGS = [
left: 90% left: 90%
padding: 0px padding: 0px
service: light.turn_off service: light.turn_off
service_data: data:
entity_id: group.all_lights entity_id: group.all_lights
- type: icon - type: icon
icon: mdi:cctv icon: mdi:cctv

View File

@@ -6,10 +6,8 @@ import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { import { HassioAddonRepository } from "../../../src/data/hassio/addon";
HassioAddonInfo, import { StoreAddon } from "../../../src/data/supervisor/store";
HassioAddonRepository,
} from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
@@ -23,20 +21,16 @@ class HassioAddonRepositoryEl extends LitElement {
@property({ attribute: false }) public repo!: HassioAddonRepository; @property({ attribute: false }) public repo!: HassioAddonRepository;
@property({ attribute: false }) public addons!: HassioAddonInfo[]; @property({ attribute: false }) public addons!: StoreAddon[];
@property() public filter!: string; @property() public filter!: string;
private _getAddons = memoizeOne( private _getAddons = memoizeOne((addons: StoreAddon[], filter?: string) => {
(addons: HassioAddonInfo[], filter?: string) => { if (filter) {
if (filter) { return filterAndSort(addons, filter);
return filterAndSort(addons, filter);
}
return addons.sort((a, b) =>
caseInsensitiveStringCompare(a.name, b.name)
);
} }
); return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name));
});
protected render(): TemplateResult { protected render(): TemplateResult {
const repo = this.repo; const repo = this.repo;

View File

@@ -14,15 +14,15 @@ import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import "../../../src/components/search-input";
import { extractSearchParam } from "../../../src/common/url/search-params"; import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-icon-button";
import "../../../src/components/search-input";
import { import {
HassioAddonInfo,
HassioAddonRepository, HassioAddonRepository,
reloadHassioAddons, reloadHassioAddons,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
@@ -66,10 +66,10 @@ class HassioAddonStore extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
let repos: TemplateResult[] = []; let repos: TemplateResult[] = [];
if (this.supervisor.addon.repositories) { if (this.supervisor.store.repositories) {
repos = this.addonRepositories( repos = this.addonRepositories(
this.supervisor.addon.repositories, this.supervisor.store.repositories,
this.supervisor.addon.addons, this.supervisor.store.addons,
this._filter this._filter
); );
} }
@@ -145,7 +145,7 @@ class HassioAddonStore extends LitElement {
private addonRepositories = memoizeOne( private addonRepositories = memoizeOne(
( (
repositories: HassioAddonRepository[], repositories: HassioAddonRepository[],
addons: HassioAddonInfo[], addons: StoreAddon[],
filter?: string filter?: string
) => ) =>
repositories.sort(sortRepos).map((repo) => { repositories.sort(sortRepos).map((repo) => {

View File

@@ -12,12 +12,17 @@ import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params"; import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { import {
fetchAddonInfo,
fetchHassioAddonInfo, fetchHassioAddonInfo,
fetchHassioAddonsInfo, fetchHassioAddonsInfo,
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../src/data/hassio/supervisor"; import {
addStoreRepository,
fetchSupervisorStore,
StoreAddonDetails,
} from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-error-screen"; import "../../../src/layouts/hass-error-screen";
@@ -42,7 +47,9 @@ class HassioAddonDashboard extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public addon?: HassioAddonDetails; @property({ attribute: false }) public addon?:
| HassioAddonDetails
| StoreAddonDetails;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@@ -169,38 +176,35 @@ class HassioAddonDashboard extends LitElement {
if (this.route.path === "") { if (this.route.path === "") {
const requestedAddon = extractSearchParam("addon"); const requestedAddon = extractSearchParam("addon");
const requestedAddonRepository = extractSearchParam("repository_url"); const requestedAddonRepository = extractSearchParam("repository_url");
if ( if (requestedAddonRepository) {
requestedAddonRepository && const storeInfo = await fetchSupervisorStore(this.hass);
!this.supervisor.supervisor.addons_repositories.find(
(repo) => repo === requestedAddonRepository
)
) {
if ( if (
!(await showConfirmationDialog(this, { !storeInfo.repositories.find(
title: this.supervisor.localize("my.add_addon_repository_title"), (repo) => repo.source === requestedAddonRepository
text: this.supervisor.localize( )
"my.add_addon_repository_description",
{ addon: requestedAddon, repository: requestedAddonRepository }
),
confirmText: this.supervisor.localize("common.add"),
dismissText: this.supervisor.localize("common.cancel"),
}))
) { ) {
this._error = this.supervisor.localize( if (
"my.error_repository_not_found" !(await showConfirmationDialog(this, {
); title: this.supervisor.localize("my.add_addon_repository_title"),
return; text: this.supervisor.localize(
} "my.add_addon_repository_description",
{ addon: requestedAddon, repository: requestedAddonRepository }
),
confirmText: this.supervisor.localize("common.add"),
dismissText: this.supervisor.localize("common.cancel"),
}))
) {
this._error = this.supervisor.localize(
"my.error_repository_not_found"
);
return;
}
try { try {
await setSupervisorOption(this.hass, { await addStoreRepository(this.hass, requestedAddonRepository);
addons_repositories: [ } catch (err: any) {
...this.supervisor.supervisor.addons_repositories, this._error = extractApiErrorMessage(err);
requestedAddonRepository, }
],
});
} catch (err: any) {
this._error = extractApiErrorMessage(err);
} }
} }
@@ -240,6 +244,8 @@ class HassioAddonDashboard extends LitElement {
if (path === "uninstall") { if (path === "uninstall") {
window.history.back(); window.history.back();
} else if (path === "install") {
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
} else { } else {
await this._routeDataChanged(); await this._routeDataChanged();
} }
@@ -257,8 +263,7 @@ class HassioAddonDashboard extends LitElement {
return; return;
} }
try { try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon); this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
this.addon = addoninfo;
} catch (err: any) { } catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined; this.addon = undefined;

View File

@@ -1,5 +1,6 @@
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { HassioAddonDetails } from "../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { StoreAddonDetails } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {
HassRouterPage, HassRouterPage,
@@ -20,7 +21,9 @@ class HassioAddonRouter extends HassRouterPage {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!:
| HassioAddonDetails
| StoreAddonDetails;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
defaultPage: "info", defaultPage: "info",

View File

@@ -59,7 +59,10 @@ import {
fetchHassioStats, fetchHassioStats,
HassioStats, HassioStats,
} from "../../../../src/data/hassio/common"; } from "../../../../src/data/hassio/common";
import { StoreAddon } from "../../../../src/data/supervisor/store"; import {
StoreAddon,
StoreAddonDetails,
} from "../../../../src/data/supervisor/store";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
@@ -100,7 +103,9 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!:
| HassioAddonDetails
| StoreAddonDetails;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@@ -143,7 +148,7 @@ class HassioAddonInfo extends LitElement {
></update-available-card> ></update-available-card>
` `
: ""} : ""}
${!this.addon.protected ${"protected" in this.addon && !this.addon.protected
? html` ? html`
<ha-alert <ha-alert
alert-type="error" alert-type="error"
@@ -518,7 +523,7 @@ class HassioAddonInfo extends LitElement {
: ""} : ""}
</div> </div>
<div> <div>
${this.addon.state === "started" ${this.addon.version && this.addon.state === "started"
? html`<ha-settings-row ?three-line=${this.narrow}> ? html`<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading"> <span slot="heading">
${this.supervisor.localize("addon.dashboard.hostname")} ${this.supervisor.localize("addon.dashboard.hostname")}
@@ -669,7 +674,7 @@ class HassioAddonInfo extends LitElement {
} }
private async _loadData(): Promise<void> { private async _loadData(): Promise<void> {
if (this.addon.state === "started") { if ("state" in this.addon && this.addon.state === "started") {
this._metrics = await fetchHassioStats( this._metrics = await fetchHassioStats(
this.hass, this.hass,
`addons/${this.addon.slug}` `addons/${this.addon.slug}`
@@ -717,18 +722,22 @@ class HassioAddonInfo extends LitElement {
} }
private get _computeIsRunning(): boolean { private get _computeIsRunning(): boolean {
return this.addon?.state === "started"; return (this.addon as HassioAddonDetails)?.state === "started";
} }
private get _pathWebui(): string | null { private get _pathWebui(): string | null {
return ( return (this.addon as HassioAddonDetails).webui!.replace(
this.addon.webui && "[HOST]",
this.addon.webui.replace("[HOST]", document.location.hostname) document.location.hostname
); );
} }
private get _computeShowWebUI(): boolean | "" | null { private get _computeShowWebUI(): boolean | "" | null {
return !this.addon.ingress && this.addon.webui && this._computeIsRunning; return (
!this.addon.ingress &&
(this.addon as HassioAddonDetails).webui &&
this._computeIsRunning
);
} }
private _openIngress(): void { private _openIngress(): void {
@@ -754,7 +763,8 @@ class HassioAddonInfo extends LitElement {
private async _startOnBootToggled(): Promise<void> { private async _startOnBootToggled(): Promise<void> {
this._error = undefined; this._error = undefined;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
boot: this.addon.boot === "auto" ? "manual" : "auto", boot:
(this.addon as HassioAddonDetails).boot === "auto" ? "manual" : "auto",
}; };
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -776,7 +786,7 @@ class HassioAddonInfo extends LitElement {
private async _watchdogToggled(): Promise<void> { private async _watchdogToggled(): Promise<void> {
this._error = undefined; this._error = undefined;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
watchdog: !this.addon.watchdog, watchdog: !(this.addon as HassioAddonDetails).watchdog,
}; };
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -798,7 +808,7 @@ class HassioAddonInfo extends LitElement {
private async _autoUpdateToggled(): Promise<void> { private async _autoUpdateToggled(): Promise<void> {
this._error = undefined; this._error = undefined;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
auto_update: !this.addon.auto_update, auto_update: !(this.addon as HassioAddonDetails).auto_update,
}; };
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -820,7 +830,7 @@ class HassioAddonInfo extends LitElement {
private async _protectionToggled(): Promise<void> { private async _protectionToggled(): Promise<void> {
this._error = undefined; this._error = undefined;
const data: HassioAddonSetSecurityParams = { const data: HassioAddonSetSecurityParams = {
protected: !this.addon.protected, protected: !(this.addon as HassioAddonDetails).protected,
}; };
try { try {
await setHassioAddonSecurity(this.hass, this.addon.slug, data); await setHassioAddonSecurity(this.hass, this.addon.slug, data);
@@ -842,7 +852,7 @@ class HassioAddonInfo extends LitElement {
private async _panelToggled(): Promise<void> { private async _panelToggled(): Promise<void> {
this._error = undefined; this._error = undefined;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
ingress_panel: !this.addon.ingress_panel, ingress_panel: !(this.addon as HassioAddonDetails).ingress_panel,
}; };
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -870,7 +880,7 @@ class HassioAddonInfo extends LitElement {
showHassioMarkdownDialog(this, { showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"), title: this.supervisor.localize("addon.dashboard.changelog"),
content: extractChangelog(this.addon, content), content: extractChangelog(this.addon as HassioAddonDetails, content),
}); });
} catch (err: any) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {

View File

@@ -98,9 +98,8 @@ export class HassioBackups extends LitElement {
if (backup.content.addons.length !== 0) { if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) { for (const addon of backup.content.addons) {
content.push( content.push(
this.supervisor.supervisor.addons.find( this.supervisor.addon.addons.find((entry) => entry.slug === addon)
(entry) => entry.slug === addon ?.name || addon
)?.name || addon
); );
} }
} }

View File

@@ -1,8 +1,8 @@
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { HassioAddonInfo } from "../../../src/data/hassio/addon"; import { StoreAddon } from "../../../src/data/supervisor/store";
export function filterAndSort(addons: HassioAddonInfo[], filter: string) { export function filterAndSort(addons: StoreAddon[], filter: string) {
const options: Fuse.IFuseOptions<HassioAddonInfo> = { const options: Fuse.IFuseOptions<StoreAddon> = {
keys: ["name", "description", "slug"], keys: ["name", "description", "slug"],
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: 2, minMatchCharLength: 2,

View File

@@ -96,7 +96,7 @@ export class SupervisorBackupContent extends LitElement {
: ["ssl", "share", "media", "addons/local"] : ["ssl", "share", "media", "addons/local"]
); );
this.addons = _computeAddons( this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons this.backup ? this.backup.addons : this.supervisor?.addon.addons
); );
this.backupType = this.backup?.type || "full"; this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || ""; this.backupName = this.backup?.name || "";

View File

@@ -24,7 +24,7 @@ class HassioAddons extends LitElement {
? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> ` ? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> `
: ""} : ""}
<div class="card-group"> <div class="card-group">
${!this.supervisor.supervisor.addons?.length ${!this.supervisor.addon.addons.length
? html` ? html`
<ha-card outlined> <ha-card outlined>
<div class="card-content"> <div class="card-content">
@@ -34,7 +34,7 @@ class HassioAddons extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: this.supervisor.supervisor.addons : this.supervisor.addon.addons
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
.map( .map(
(addon) => html` (addon) => html`

View File

@@ -15,15 +15,18 @@ import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button"; import "../../../../src/components/ha-icon-button";
import { import {
fetchHassioAddonsInfo,
HassioAddonInfo, HassioAddonInfo,
HassioAddonRepository, HassioAddonRepository,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories"; import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
import {
addStoreRepository,
fetchStoreRepositories,
removeStoreRepository,
} from "../../../../src/data/supervisor/store";
@customElement("dialog-hassio-repositories") @customElement("dialog-hassio-repositories")
class HassioRepositoriesDialog extends LitElement { class HassioRepositoriesDialog extends LitElement {
@@ -58,7 +61,13 @@ class HassioRepositoriesDialog extends LitElement {
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) => private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
repos repos
.filter((repo) => repo.slug !== "core" && repo.slug !== "local") .filter(
(repo) =>
repo.slug !== "core" && // The core add-ons repository
repo.slug !== "local" && // Locally managed add-ons
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
repo.slug !== "5c53de3b" // The ESPHome repository
)
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
); );
@@ -78,7 +87,7 @@ class HassioRepositoriesDialog extends LitElement {
const repositories = this._filteredRepositories(this._repositories); const repositories = this._filteredRepositories(this._repositories);
const usedRepositories = this._filteredUsedRepositories( const usedRepositories = this._filteredUsedRepositories(
repositories, repositories,
this._dialogParams.supervisor.supervisor.addons this._dialogParams.supervisor.addon.addons
); );
return html` return html`
<ha-dialog <ha-dialog
@@ -215,9 +224,7 @@ class HassioRepositoriesDialog extends LitElement {
private async _loadData(): Promise<void> { private async _loadData(): Promise<void> {
try { try {
const addonsinfo = await fetchHassioAddonsInfo(this.hass); this._repositories = await fetchStoreRepositories(this.hass);
this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err: any) { } catch (err: any) {
@@ -231,14 +238,9 @@ class HassioRepositoriesDialog extends LitElement {
return; return;
} }
this._processing = true; this._processing = true;
const repositories = this._filteredRepositories(this._repositories!);
const newRepositories = repositories.map((repo) => repo.source);
newRepositories.push(input.value);
try { try {
await setSupervisorOption(this.hass, { await addStoreRepository(this.hass, input.value);
addons_repositories: newRepositories,
});
await this._loadData(); await this._loadData();
input.value = ""; input.value = "";
@@ -250,19 +252,8 @@ class HassioRepositoriesDialog extends LitElement {
private async _removeRepository(ev: Event) { private async _removeRepository(ev: Event) {
const slug = (ev.currentTarget as any).slug; const slug = (ev.currentTarget as any).slug;
const repositories = this._filteredRepositories(this._repositories!);
const repository = repositories.find((repo) => repo.slug === slug);
if (!repository) {
return;
}
const newRepositories = repositories
.map((repo) => repo.source)
.filter((repo) => repo !== repository.source);
try { try {
await setSupervisorOption(this.hass, { await removeStoreRepository(this.hass, slug);
addons_repositories: newRepositories,
});
await this._loadData(); await this._loadData();
} catch (err: any) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);

View File

@@ -72,8 +72,8 @@
"@material/mwc-textfield": "0.25.3", "@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3", "@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0", "@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.6.95", "@mdi/js": "6.9.96",
"@mdi/svg": "6.6.95", "@mdi/svg": "6.9.96",
"@polymer/app-layout": "^3.1.0", "@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1", "@polymer/iron-icon": "^3.0.1",
@@ -89,8 +89,8 @@
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1", "@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.4", "@thomasloven/round-slider": "0.5.4",
"@vaadin/combo-box": "^22.0.4", "@vaadin/combo-box": "^23.0.10",
"@vaadin/vaadin-themable-mixin": "^22.0.4", "@vaadin/vaadin-themable-mixin": "^23.0.10",
"@vibrant/color": "^3.2.1-alpha.1", "@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
@@ -108,7 +108,7 @@
"fuse.js": "^6.0.0", "fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^1.1.5", "hls.js": "^1.1.5",
"home-assistant-js-websocket": "^7.0.3", "home-assistant-js-websocket": "^7.1.0",
"idb-keyval": "^5.1.3", "idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1", "intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",

View File

@@ -1,3 +1,30 @@
[build-system] [build-system]
requires = ["setuptools~=60.5", "wheel~=0.37.1"] requires = ["setuptools~=62.3", "wheel~=0.37.1"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20220707.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
authors = [
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
]
requires-python = ">=3.4.0"
[project.urls]
"Homepage" = "https://github.com/home-assistant/frontend"
[tool.setuptools]
platforms = ["any"]
zip-safe = false
include-package-data = true
[tool.setuptools.packages.find]
include = ["hass_frontend*"]
[tool.mypy]
python_version = 3.4
show_error_codes = true
strict = true

View File

@@ -24,10 +24,15 @@ function auto(version) {
return patch(version); return patch(version);
} }
function nightly() {
return `${today()}.dev`;
}
const methods = { const methods = {
patch, patch,
today, today,
auto, auto,
nightly,
}; };
async function main(args) { async function main(args) {
@@ -50,14 +55,18 @@ async function main(args) {
return; return;
} }
const setup = fs.readFileSync("setup.cfg", "utf8"); const setup = fs.readFileSync("pyproject.toml", "utf8");
const version = setup.match(/\d{8}\.\d+/)[0]; const version = setup.match(/version\W+=\W"(\d{8}\.\d)"/)[1];
const newVersion = method(version); const newVersion = method(version);
console.log("Current version:", version); console.log("Current version:", version);
console.log("New version:", newVersion); console.log("New version:", newVersion);
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8"); fs.writeFileSync(
"pyproject.toml",
setup.replace(version, newVersion),
"utf-8"
);
if (!commit) { if (!commit) {
return; return;

View File

@@ -1,26 +1,2 @@
[metadata] # Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
name = home-assistant-frontend # Keep this file until it does!
version = 20220504.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0
platforms = any
description = The Home Assistant frontend
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/home-assistant/frontend
[options]
packages = find:
zip_safe = False
include_package_data = True
python_requires = >= 3.4.0
[options.packages.find]
include =
hass_frontend*
[mypy]
python_version = 3.4
show_error_codes = True
strict = True

View File

@@ -76,7 +76,11 @@ class Storage {
public setValue(storageKey: string, value: any): any { public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value; this._storage[storageKey] = value;
try { try {
window.localStorage.setItem(storageKey, JSON.stringify(value)); if (value === undefined) {
window.localStorage.removeItem(storageKey);
} else {
window.localStorage.setItem(storageKey, JSON.stringify(value));
}
} catch (err: any) { } catch (err: any) {
// Safari in private mode doesn't allow localstorage // Safari in private mode doesn't allow localstorage
} }

View File

@@ -0,0 +1,41 @@
const DEFAULT_OWN = true;
// Finds the closest ancestor of an element that has a specific optionally owned property,
// traversing slot and shadow root boundaries until the body element is reached
export const closestWithProperty = (
element: Element | null,
property: string | symbol,
own = DEFAULT_OWN
) => {
if (!element || element === document.body) return null;
element = element.assignedSlot ?? element;
if (element.parentElement) {
element = element.parentElement;
} else {
const root = element.getRootNode();
element = root instanceof ShadowRoot ? root.host : null;
}
if (
own
? Object.prototype.hasOwnProperty.call(element, property)
: element && property in element
)
return element;
return closestWithProperty(element, property, own);
};
// Finds the set of all such ancestors and includes starting element as first in the set
export const ancestorsWithProperty = (
element: Element | null,
property: string | symbol,
own = DEFAULT_OWN
) => {
const ancestors: Set<Element> = new Set();
while (element) {
ancestors.add(element);
element = closestWithProperty(element, property, own);
}
return ancestors;
};

View File

@@ -1,6 +1,11 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE_STATES } from "../../data/entity";
export const computeActiveState = (stateObj: HassEntity): string => { export const computeActiveState = (stateObj: HassEntity): string => {
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
return stateObj.state;
}
const domain = stateObj.entity_id.split(".")[0]; const domain = stateObj.entity_id.split(".")[0];
let state = stateObj.state; let state = stateObj.state;

View File

@@ -2,67 +2,74 @@ import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { import {
updateIsInstalling,
UpdateEntity,
UPDATE_SUPPORT_PROGRESS, UPDATE_SUPPORT_PROGRESS,
updateIsInstallingFromAttributes,
} from "../../data/update"; } from "../../data/update";
import { formatDate } from "../datetime/format_date"; import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time"; import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time"; import { formatTime } from "../datetime/format_time";
import { formatNumber, isNumericState } from "../number/format_number"; import { formatNumber, isNumericFromAttributes } from "../number/format_number";
import { LocalizeFunc } from "../translations/localize"; import { LocalizeFunc } from "../translations/localize";
import { computeStateDomain } from "./compute_state_domain"; import { supportsFeatureFromAttributes } from "./supports-feature";
import { supportsFeature } from "./supports-feature";
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
import { computeDomain } from "./compute_domain";
export const computeStateDisplay = ( export const computeStateDisplay = (
localize: LocalizeFunc, localize: LocalizeFunc,
stateObj: HassEntity, stateObj: HassEntity,
locale: FrontendLocaleData, locale: FrontendLocaleData,
state?: string state?: string
): string => { ): string =>
const compareState = state !== undefined ? state : stateObj.state; computeStateDisplayFromEntityAttributes(
localize,
locale,
stateObj.entity_id,
stateObj.attributes,
state !== undefined ? state : stateObj.state
);
if (compareState === UNKNOWN || compareState === UNAVAILABLE) { export const computeStateDisplayFromEntityAttributes = (
return localize(`state.default.${compareState}`); localize: LocalizeFunc,
locale: FrontendLocaleData,
entityId: string,
attributes: any,
state: string
): string => {
if (state === UNKNOWN || state === UNAVAILABLE) {
return localize(`state.default.${state}`);
} }
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
if (isNumericState(stateObj)) { if (isNumericFromAttributes(attributes)) {
// state is duration // state is duration
if ( if (
stateObj.attributes.device_class === "duration" && attributes.device_class === "duration" &&
stateObj.attributes.unit_of_measurement && attributes.unit_of_measurement &&
UNIT_TO_SECOND_CONVERT[stateObj.attributes.unit_of_measurement] UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement]
) { ) {
try { try {
return formatDuration( return formatDuration(state, attributes.unit_of_measurement);
compareState,
stateObj.attributes.unit_of_measurement
);
} catch (_err) { } catch (_err) {
// fallback to default // fallback to default
} }
} }
if (stateObj.attributes.device_class === "monetary") { if (attributes.device_class === "monetary") {
try { try {
return formatNumber(compareState, locale, { return formatNumber(state, locale, {
style: "currency", style: "currency",
currency: stateObj.attributes.unit_of_measurement, currency: attributes.unit_of_measurement,
minimumFractionDigits: 2, minimumFractionDigits: 2,
}); });
} catch (_err) { } catch (_err) {
// fallback to default // fallback to default
} }
} }
return `${formatNumber(compareState, locale)}${ return `${formatNumber(state, locale)}${
stateObj.attributes.unit_of_measurement attributes.unit_of_measurement ? " " + attributes.unit_of_measurement : ""
? " " + stateObj.attributes.unit_of_measurement
: ""
}`; }`;
} }
const domain = computeStateDomain(stateObj); const domain = computeDomain(entityId);
if (domain === "input_datetime") { if (domain === "input_datetime") {
if (state !== undefined) { if (state !== undefined) {
@@ -97,36 +104,32 @@ export const computeStateDisplay = (
} else { } else {
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format. // If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
let date: Date; let date: Date;
if (stateObj.attributes.has_date && stateObj.attributes.has_time) { if (attributes.has_date && attributes.has_time) {
date = new Date( date = new Date(
stateObj.attributes.year, attributes.year,
stateObj.attributes.month - 1, attributes.month - 1,
stateObj.attributes.day, attributes.day,
stateObj.attributes.hour, attributes.hour,
stateObj.attributes.minute attributes.minute
); );
return formatDateTime(date, locale); return formatDateTime(date, locale);
} }
if (stateObj.attributes.has_date) { if (attributes.has_date) {
date = new Date( date = new Date(attributes.year, attributes.month - 1, attributes.day);
stateObj.attributes.year,
stateObj.attributes.month - 1,
stateObj.attributes.day
);
return formatDate(date, locale); return formatDate(date, locale);
} }
if (stateObj.attributes.has_time) { if (attributes.has_time) {
date = new Date(); date = new Date();
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute); date.setHours(attributes.hour, attributes.minute);
return formatTime(date, locale); return formatTime(date, locale);
} }
return stateObj.state; return state;
} }
} }
if (domain === "humidifier") { if (domain === "humidifier") {
if (compareState === "on" && stateObj.attributes.humidity) { if (state === "on" && attributes.humidity) {
return `${stateObj.attributes.humidity} %`; return `${attributes.humidity} %`;
} }
} }
@@ -136,7 +139,7 @@ export const computeStateDisplay = (
domain === "number" || domain === "number" ||
domain === "input_number" domain === "input_number"
) { ) {
return formatNumber(compareState, locale); return formatNumber(state, locale);
} }
// state of button is a timestamp // state of button is a timestamp
@@ -144,12 +147,12 @@ export const computeStateDisplay = (
domain === "button" || domain === "button" ||
domain === "input_button" || domain === "input_button" ||
domain === "scene" || domain === "scene" ||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp") (domain === "sensor" && attributes.device_class === "timestamp")
) { ) {
try { try {
return formatDateTime(new Date(compareState), locale); return formatDateTime(new Date(state), locale);
} catch (_err) { } catch (_err) {
return compareState; return state;
} }
} }
@@ -160,30 +163,28 @@ export const computeStateDisplay = (
// When the latest version is skipped, show the latest version // When the latest version is skipped, show the latest version
// When update is not available, show "Up-to-date" // When update is not available, show "Up-to-date"
// When update is not available and there is no latest_version show "Unavailable" // When update is not available and there is no latest_version show "Unavailable"
return compareState === "on" return state === "on"
? updateIsInstalling(stateObj as UpdateEntity) ? updateIsInstallingFromAttributes(attributes)
? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS) ? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS)
? localize("ui.card.update.installing_with_progress", { ? localize("ui.card.update.installing_with_progress", {
progress: stateObj.attributes.in_progress, progress: attributes.in_progress,
}) })
: localize("ui.card.update.installing") : localize("ui.card.update.installing")
: stateObj.attributes.latest_version : attributes.latest_version
: stateObj.attributes.skipped_version === : attributes.skipped_version === attributes.latest_version
stateObj.attributes.latest_version ? attributes.latest_version ?? localize("state.default.unavailable")
? stateObj.attributes.latest_version ??
localize("state.default.unavailable")
: localize("ui.card.update.up_to_date"); : localize("ui.card.update.up_to_date");
} }
return ( return (
// Return device class translation // Return device class translation
(stateObj.attributes.device_class && (attributes.device_class &&
localize( localize(
`component.${domain}.state.${stateObj.attributes.device_class}.${compareState}` `component.${domain}.state.${attributes.device_class}.${state}`
)) || )) ||
// Return default translation // Return default translation
localize(`component.${domain}.state._.${compareState}`) || localize(`component.${domain}.state._.${state}`) ||
// We don't know! Return the raw state. // We don't know! Return the raw state.
compareState state
); );
}; };

View File

@@ -1,7 +1,13 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { computeObjectId } from "./compute_object_id"; import { computeObjectId } from "./compute_object_id";
export const computeStateNameFromEntityAttributes = (
entityId: string,
attributes: { [key: string]: any }
): string =>
attributes.friendly_name === undefined
? computeObjectId(entityId).replace(/_/g, " ")
: attributes.friendly_name || "";
export const computeStateName = (stateObj: HassEntity): string => export const computeStateName = (stateObj: HassEntity): string =>
stateObj.attributes.friendly_name === undefined computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);
? computeObjectId(stateObj.entity_id).replace(/_/g, " ")
: stateObj.attributes.friendly_name || "";

View File

@@ -8,6 +8,7 @@ import {
mdiCalendar, mdiCalendar,
mdiCast, mdiCast,
mdiCastConnected, mdiCastConnected,
mdiChartSankey,
mdiCheckCircleOutline, mdiCheckCircleOutline,
mdiClock, mdiClock,
mdiCloseCircleOutline, mdiCloseCircleOutline,
@@ -24,12 +25,14 @@ import {
mdiPowerPlug, mdiPowerPlug,
mdiPowerPlugOff, mdiPowerPlugOff,
mdiRestart, mdiRestart,
mdiSwapHorizontal,
mdiToggleSwitchVariant, mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff, mdiToggleSwitchVariantOff,
mdiWeatherNight, mdiWeatherNight,
} from "@mdi/js"; } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { updateIsInstalling, UpdateEntity } from "../../data/update"; import { UpdateEntity, updateIsInstalling } from "../../data/update";
import { weatherIcon } from "../../data/weather";
/** /**
* Return the icon to be used for a domain. * Return the icon to be used for a domain.
* *
@@ -46,6 +49,20 @@ export const domainIcon = (
stateObj?: HassEntity, stateObj?: HassEntity,
state?: string state?: string
): string => { ): string => {
const icon = domainIconWithoutDefault(domain, stateObj, state);
if (icon) {
return icon;
}
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};
export const domainIconWithoutDefault = (
domain: string,
stateObj?: HassEntity,
state?: string
): string | undefined => {
const compareState = state !== undefined ? state : stateObj?.state; const compareState = state !== undefined ? state : stateObj?.state;
switch (domain) { switch (domain) {
@@ -87,6 +104,15 @@ export const domainIcon = (
? mdiCheckCircleOutline ? mdiCheckCircleOutline
: mdiCloseCircleOutline; : mdiCloseCircleOutline;
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "lock": case "lock":
switch (compareState) { switch (compareState) {
case "unlocked": case "unlocked":
@@ -124,33 +150,31 @@ export const domainIcon = (
break; break;
} }
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "sun": case "sun":
return stateObj?.state === "above_horizon" return stateObj?.state === "above_horizon"
? FIXED_DOMAIN_ICONS[domain] ? FIXED_DOMAIN_ICONS[domain]
: mdiWeatherNight; : mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update": case "update":
return compareState === "on" return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity) ? updateIsInstalling(stateObj as UpdateEntity)
? mdiPackageDown ? mdiPackageDown
: mdiPackageUp : mdiPackageUp
: mdiPackage; : mdiPackage;
case "weather":
return weatherIcon(stateObj?.state);
} }
if (domain in FIXED_DOMAIN_ICONS) { if (domain in FIXED_DOMAIN_ICONS) {
return FIXED_DOMAIN_ICONS[domain]; return FIXED_DOMAIN_ICONS[domain];
} }
// eslint-disable-next-line return undefined;
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
}; };

View File

@@ -3,6 +3,13 @@ import { HassEntity } from "home-assistant-js-websocket";
export const supportsFeature = ( export const supportsFeature = (
stateObj: HassEntity, stateObj: HassEntity,
feature: number feature: number
): boolean => supportsFeatureFromAttributes(stateObj.attributes, feature);
export const supportsFeatureFromAttributes = (
attributes: {
[key: string]: any;
},
feature: number
): boolean => ): boolean =>
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
(stateObj.attributes.supported_features! & feature) !== 0; (attributes.supported_features! & feature) !== 0;

View File

@@ -5,6 +5,6 @@ export const clamp = (value: number, min: number, max: number) =>
export const conditionalClamp = (value: number, min?: number, max?: number) => { export const conditionalClamp = (value: number, min?: number, max?: number) => {
let result: number; let result: number;
result = min ? Math.max(value, min) : value; result = min ? Math.max(value, min) : value;
result = max ? Math.min(value, max) : value; result = max ? Math.min(result, max) : result;
return result; return result;
}; };

View File

@@ -7,8 +7,11 @@ import { round } from "./round";
* @param stateObj The entity state object * @param stateObj The entity state object
*/ */
export const isNumericState = (stateObj: HassEntity): boolean => export const isNumericState = (stateObj: HassEntity): boolean =>
!!stateObj.attributes.unit_of_measurement || isNumericFromAttributes(stateObj.attributes);
!!stateObj.attributes.state_class;
export const isNumericFromAttributes = (attributes: {
[key: string]: any;
}): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
export const numberFormatToLocale = ( export const numberFormatToLocale = (
localeOptions: FrontendLocaleData localeOptions: FrontendLocaleData

View File

@@ -70,7 +70,9 @@ export const iconColorCSS = css`
} }
} }
ha-state-icon[data-domain="plant"][data-state="problem"], ha-state-icon[data-domain="plant"][data-state="problem"] {
color: var(--state-icon-error-color);
}
/* Color the icon if unavailable */ /* Color the icon if unavailable */
ha-state-icon[data-state="unavailable"] { ha-state-icon[data-state="unavailable"] {

View File

@@ -1,3 +1,4 @@
import { LitElement } from "lit";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
export function computeRTL(hass: HomeAssistant) { export function computeRTL(hass: HomeAssistant) {
@@ -15,3 +16,21 @@ export function computeRTLDirection(hass: HomeAssistant) {
export function emitRTLDirection(rtl: boolean) { export function emitRTLDirection(rtl: boolean) {
return rtl ? "rtl" : "ltr"; return rtl ? "rtl" : "ltr";
} }
export function computeDirectionStyles(isRTL: boolean, element: LitElement) {
const direction: string = emitRTLDirection(isRTL);
setDirectionStyles(direction, element);
}
export function setDirectionStyles(direction: string, element: LitElement) {
element.style.direction = direction;
element.style.setProperty("--direction", direction);
element.style.setProperty(
"--float-start",
direction === "ltr" ? "left" : "right"
);
element.style.setProperty(
"--float-end",
direction === "ltr" ? "right" : "left"
);
}

View File

@@ -13,7 +13,7 @@ export const throttle = <T extends any[]>(
) => { ) => {
let timeout: number | undefined; let timeout: number | undefined;
let previous = 0; let previous = 0;
return (...args: T): void => { const throttledFunc = (...args: T): void => {
const later = () => { const later = () => {
previous = leading === false ? 0 : Date.now(); previous = leading === false ? 0 : Date.now();
timeout = undefined; timeout = undefined;
@@ -35,4 +35,10 @@ export const throttle = <T extends any[]>(
timeout = window.setTimeout(later, remaining); timeout = window.setTimeout(later, remaining);
} }
}; };
throttledFunc.cancel = () => {
clearTimeout(timeout);
timeout = undefined;
previous = 0;
};
return throttledFunc;
}; };

View File

@@ -34,7 +34,7 @@ import {
endOfMonth, endOfMonth,
endOfQuarter, endOfQuarter,
endOfYear, endOfYear,
} from "date-fns"; } from "date-fns/esm";
import { import {
formatDate, formatDate,
formatDateMonth, formatDateMonth,

View File

@@ -11,6 +11,8 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { clamp } from "../../common/number/clamp"; import { clamp } from "../../common/number/clamp";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
interface Tooltip extends TooltipModel<any> { interface Tooltip extends TooltipModel<any> {
top: string; top: string;
left: string; left: string;
@@ -37,6 +39,26 @@ export default class HaChartBase extends LitElement {
@state() private _hiddenDatasets: Set<number> = new Set(); @state() private _hiddenDatasets: Set<number> = new Set();
private _releaseCanvas() {
// release the canvas memory to prevent
// safari from running out of memory.
if (this.chart) {
this.chart.destroy();
}
}
public disconnectedCallback() {
this._releaseCanvas();
super.disconnectedCallback();
}
public connectedCallback() {
super.connectedCallback();
if (this.hasUpdated) {
this._setupChart();
}
}
protected firstUpdated() { protected firstUpdated() {
this._setupChart(); this._setupChart();
this.data.datasets.forEach((dataset, index) => { this.data.datasets.forEach((dataset, index) => {
@@ -304,6 +326,9 @@ export default class HaChartBase extends LitElement {
width: 16px; width: 16px;
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box; box-sizing: border-box;
margin-inline-end: 6px;
margin-inline-start: initial;
direction: var(--direction);
} }
.chartTooltip .bullet { .chartTooltip .bullet {
align-self: baseline; align-self: baseline;
@@ -312,6 +337,9 @@ export default class HaChartBase extends LitElement {
:host([rtl]) .chartTooltip .bullet { :host([rtl]) .chartTooltip .bullet {
margin-right: inherit; margin-right: inherit;
margin-left: 6px; margin-left: 6px;
margin-inline-end: inherit;
margin-inline-start: 6px;
direction: var(--direction);
} }
.chartTooltip { .chartTooltip {
padding: 8px; padding: 8px;

View File

@@ -8,7 +8,7 @@ import {
} from "../../common/number/format_number"; } from "../../common/number/format_number";
import { LineChartEntity, LineChartState } from "../../data/history"; import { LineChartEntity, LineChartState } from "../../data/history";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "./ha-chart-base"; import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
const safeParseFloat = (value) => { const safeParseFloat = (value) => {
const parsed = parseFloat(value); const parsed = parseFloat(value);
@@ -28,11 +28,13 @@ class StateHistoryChartLine extends LitElement {
@property({ type: Boolean }) public isSingleDevice = false; @property({ type: Boolean }) public isSingleDevice = false;
@property({ attribute: false }) public endTime?: Date; @property({ attribute: false }) public endTime!: Date;
@state() private _chartData?: ChartData<"line">; @state() private _chartData?: ChartData<"line">;
@state() private _chartOptions?: ChartOptions<"line">; @state() private _chartOptions?: ChartOptions;
private _chartTime: Date = new Date();
protected render() { protected render() {
return html` return html`
@@ -57,6 +59,7 @@ class StateHistoryChartLine extends LitElement {
locale: this.hass.locale, locale: this.hass.locale,
}, },
}, },
suggestedMax: this.endTime,
ticks: { ticks: {
maxRotation: 0, maxRotation: 0,
sampleSize: 5, sampleSize: 5,
@@ -120,7 +123,13 @@ class StateHistoryChartLine extends LitElement {
locale: numberFormatToLocale(this.hass.locale), locale: numberFormatToLocale(this.hass.locale),
}; };
} }
if (changedProps.has("data")) { if (
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
) {
// If the line is more than 5 minutes old, re-gen it
// so the X axis grows even if there is no new data
this._generateData(); this._generateData();
} }
} }
@@ -130,28 +139,12 @@ class StateHistoryChartLine extends LitElement {
const computedStyles = getComputedStyle(this); const computedStyles = getComputedStyle(this);
const entityStates = this.data; const entityStates = this.data;
const datasets: ChartDataset<"line">[] = []; const datasets: ChartDataset<"line">[] = [];
let endTime: Date;
if (entityStates.length === 0) { if (entityStates.length === 0) {
return; return;
} }
endTime = this._chartTime = new Date();
this.endTime || const endTime = this.endTime;
// Get the highest date from the last date of each device
new Date(
Math.max(
...entityStates.map((devSts) =>
new Date(
devSts.states[devSts.states.length - 1].last_changed
).getTime()
)
)
);
if (endTime > new Date()) {
endTime = new Date();
}
const names = this.names || {}; const names = this.names || {};
entityStates.forEach((states) => { entityStates.forEach((states) => {
const domain = states.domain; const domain = states.domain;

View File

@@ -9,7 +9,7 @@ import { numberFormatToLocale } from "../../common/number/format_number";
import { computeRTL } from "../../common/util/compute_rtl"; import { computeRTL } from "../../common/util/compute_rtl";
import { TimelineEntity } from "../../data/history"; import { TimelineEntity } from "../../data/history";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "./ha-chart-base"; import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
import type { TimeLineData } from "./timeline-chart/const"; import type { TimeLineData } from "./timeline-chart/const";
/** Binary sensor device classes for which the static colors for on/off are NOT inverted. /** Binary sensor device classes for which the static colors for on/off are NOT inverted.
@@ -83,6 +83,8 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ attribute: false }) public data: TimelineEntity[] = []; @property({ attribute: false }) public data: TimelineEntity[] = [];
@property() public narrow!: boolean;
@property() public names: boolean | Record<string, string> = false; @property() public names: boolean | Record<string, string> = false;
@property() public unit?: string; @property() public unit?: string;
@@ -91,12 +93,18 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ type: Boolean }) public isSingleDevice = false; @property({ type: Boolean }) public isSingleDevice = false;
@property({ attribute: false }) public endTime?: Date; @property({ type: Boolean }) public chunked = false;
@property({ attribute: false }) public startTime!: Date;
@property({ attribute: false }) public endTime!: Date;
@state() private _chartData?: ChartData<"timeline">; @state() private _chartData?: ChartData<"timeline">;
@state() private _chartOptions?: ChartOptions<"timeline">; @state() private _chartOptions?: ChartOptions<"timeline">;
private _chartTime: Date = new Date();
protected render() { protected render() {
return html` return html`
<ha-chart-base <ha-chart-base
@@ -110,6 +118,7 @@ export class StateHistoryChartTimeline extends LitElement {
public willUpdate(changedProps: PropertyValues) { public willUpdate(changedProps: PropertyValues) {
if (!this.hasUpdated) { if (!this.hasUpdated) {
const narrow = this.narrow;
this._chartOptions = { this._chartOptions = {
maintainAspectRatio: false, maintainAspectRatio: false,
parsing: false, parsing: false,
@@ -123,6 +132,8 @@ export class StateHistoryChartTimeline extends LitElement {
locale: this.hass.locale, locale: this.hass.locale,
}, },
}, },
suggestedMin: this.startTime,
suggestedMax: this.endTime,
ticks: { ticks: {
autoSkip: true, autoSkip: true,
maxRotation: 0, maxRotation: 0,
@@ -153,11 +164,18 @@ export class StateHistoryChartTimeline extends LitElement {
drawTicks: false, drawTicks: false,
}, },
ticks: { ticks: {
display: this.data.length !== 1, display:
this.chunked || !this.isSingleDevice || this.data.length !== 1,
}, },
afterSetDimensions: (y) => { afterSetDimensions: (y) => {
y.maxWidth = y.chart.width * 0.18; y.maxWidth = y.chart.width * 0.18;
}, },
afterFit: (scaleInstance) => {
if (this.chunked) {
// ensure all the chart labels are the same width
scaleInstance.width = narrow ? 105 : 185;
}
},
position: computeRTL(this.hass) ? "right" : "left", position: computeRTL(this.hass) ? "right" : "left",
}, },
}, },
@@ -195,7 +213,13 @@ export class StateHistoryChartTimeline extends LitElement {
locale: numberFormatToLocale(this.hass.locale), locale: numberFormatToLocale(this.hass.locale),
}; };
} }
if (changedProps.has("data")) { if (
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
) {
// If the line is more than 5 minutes old, re-gen it
// so the X axis grows even if there is no new data
this._generateData(); this._generateData();
} }
} }
@@ -208,34 +232,9 @@ export class StateHistoryChartTimeline extends LitElement {
stateHistory = []; stateHistory = [];
} }
const startTime = new Date( this._chartTime = new Date();
stateHistory.reduce( const startTime = this.startTime;
(minTime, stateInfo) => const endTime = this.endTime;
Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()),
new Date().getTime()
)
);
// end time is Math.max(startTime, last_event)
let endTime =
this.endTime ||
new Date(
stateHistory.reduce(
(maxTime, stateInfo) =>
Math.max(
maxTime,
new Date(
stateInfo.data[stateInfo.data.length - 1].last_changed
).getTime()
),
startTime.getTime()
)
);
if (endTime > new Date()) {
endTime = new Date();
}
const labels: string[] = []; const labels: string[] = [];
const datasets: ChartDataset<"timeline">[] = []; const datasets: ChartDataset<"timeline">[] = [];
const names = this.names || {}; const names = this.names || {};

View File

@@ -1,3 +1,4 @@
import "@lit-labs/virtualizer";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@@ -6,12 +7,29 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, state, eventOptions } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { HistoryResult } from "../../data/history"; import {
HistoryResult,
LineChartUnit,
TimelineEntity,
} from "../../data/history";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "./state-history-chart-line"; import "./state-history-chart-line";
import "./state-history-chart-timeline"; import "./state-history-chart-timeline";
import { restoreScroll } from "../../common/decorators/restore-scroll";
const CANVAS_TIMELINE_ROWS_CHUNK = 10; // Split up the canvases to avoid hitting the render limit
const chunkData = (inputArray: any[], chunks: number) =>
inputArray.reduce((results, item, idx) => {
const chunkIdx = Math.floor(idx / chunks);
if (!results[chunkIdx]) {
results[chunkIdx] = [];
}
results[chunkIdx].push(item);
return results;
}, []);
@customElement("state-history-charts") @customElement("state-history-charts")
class StateHistoryCharts extends LitElement { class StateHistoryCharts extends LitElement {
@@ -19,8 +37,13 @@ class StateHistoryCharts extends LitElement {
@property({ attribute: false }) public historyData!: HistoryResult; @property({ attribute: false }) public historyData!: HistoryResult;
@property() public narrow!: boolean;
@property({ type: Boolean }) public names = false; @property({ type: Boolean }) public names = false;
@property({ type: Boolean, attribute: "virtualize", reflect: true })
public virtualize = false;
@property({ attribute: false }) public endTime?: Date; @property({ attribute: false }) public endTime?: Date;
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false; @property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
@@ -29,59 +52,104 @@ class StateHistoryCharts extends LitElement {
@property({ type: Boolean }) public isLoadingData = false; @property({ type: Boolean }) public isLoadingData = false;
@state() private _computedStartTime!: Date;
@state() private _computedEndTime!: Date;
// @ts-ignore
@restoreScroll(".container") private _savedScrollPos?: number;
@eventOptions({ passive: true })
protected render(): TemplateResult { protected render(): TemplateResult {
if (!isComponentLoaded(this.hass, "history")) { if (!isComponentLoaded(this.hass, "history")) {
return html` <div class="info"> return html`<div class="info">
${this.hass.localize("ui.components.history_charts.history_disabled")} ${this.hass.localize("ui.components.history_charts.history_disabled")}
</div>`; </div>`;
} }
if (this.isLoadingData && !this.historyData) { if (this.isLoadingData && !this.historyData) {
return html` <div class="info"> return html`<div class="info">
${this.hass.localize("ui.components.history_charts.loading_history")} ${this.hass.localize("ui.components.history_charts.loading_history")}
</div>`; </div>`;
} }
if (this._isHistoryEmpty()) { if (this._isHistoryEmpty()) {
return html` <div class="info"> return html`<div class="info">
${this.hass.localize("ui.components.history_charts.no_history_found")} ${this.hass.localize("ui.components.history_charts.no_history_found")}
</div>`; </div>`;
} }
const computedEndTime = this.upToNow const now = new Date();
? new Date()
: this.endTime || new Date();
return html` this._computedEndTime =
${this.historyData.timeline.length this.upToNow || !this.endTime || this.endTime > now ? now : this.endTime;
? html`
<state-history-chart-timeline this._computedStartTime = new Date(
.hass=${this.hass} this.historyData.timeline.reduce(
.data=${this.historyData.timeline} (minTime, stateInfo) =>
.endTime=${computedEndTime} Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()),
.noSingle=${this.noSingle} new Date().getTime()
.names=${this.names} )
></state-history-chart-timeline> );
`
: html``} const combinedItems = this.historyData.timeline.length
${this.historyData.line.map( ? (this.virtualize
(line) => html` ? chunkData(this.historyData.timeline, CANVAS_TIMELINE_ROWS_CHUNK)
<state-history-chart-line : [this.historyData.timeline]
.hass=${this.hass} ).concat(this.historyData.line)
.unit=${line.unit} : this.historyData.line;
.data=${line.data}
.identifier=${line.identifier} return this.virtualize
.isSingleDevice=${!this.noSingle && ? html`<div class="container ha-scrollbar" @scroll=${this._saveScrollPos}>
line.data && <lit-virtualizer
line.data.length === 1} scroller
.endTime=${computedEndTime} class="ha-scrollbar"
.names=${this.names} .items=${combinedItems}
></state-history-chart-line> .renderItem=${this._renderHistoryItem}
` >
)} </lit-virtualizer>
`; </div>`
: html`${combinedItems.map((item, index) =>
this._renderHistoryItem(item, index)
)}`;
} }
private _renderHistoryItem = (
item: TimelineEntity[] | LineChartUnit,
index: number
): TemplateResult => {
if (!item || index === undefined) {
return html``;
}
if (!Array.isArray(item)) {
return html`<div class="entry-container">
<state-history-chart-line
.hass=${this.hass}
.unit=${item.unit}
.data=${item.data}
.identifier=${item.identifier}
.isSingleDevice=${!this.noSingle &&
this.historyData.line?.length === 1}
.endTime=${this._computedEndTime}
.names=${this.names}
></state-history-chart-line>
</div> `;
}
return html`<div class="entry-container">
<state-history-chart-timeline
.hass=${this.hass}
.data=${item}
.startTime=${this._computedStartTime}
.endTime=${this._computedEndTime}
.isSingleDevice=${!this.noSingle &&
this.historyData.timeline?.length === 1}
.names=${this.names}
.narrow=${this.narrow}
.chunked=${this.virtualize}
></state-history-chart-timeline>
</div> `;
};
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {
return !(changedProps.size === 1 && changedProps.has("hass")); return !(changedProps.size === 1 && changedProps.has("hass"));
} }
@@ -96,6 +164,11 @@ class StateHistoryCharts extends LitElement {
return !this.isLoadingData && historyDataEmpty; return !this.isLoadingData && historyDataEmpty;
} }
@eventOptions({ passive: true })
private _saveScrollPos(e: Event) {
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
@@ -103,11 +176,48 @@ class StateHistoryCharts extends LitElement {
/* height of single timeline chart = 60px */ /* height of single timeline chart = 60px */
min-height: 60px; min-height: 60px;
} }
:host([virtualize]) {
height: 100%;
}
.info { .info {
text-align: center; text-align: center;
line-height: 60px; line-height: 60px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.container {
max-height: var(--history-max-height);
}
.entry-container {
width: 100%;
}
.entry-container:hover {
z-index: 1;
}
:host([virtualize]) .entry-container {
padding-left: 1px;
padding-right: 1px;
}
.container,
lit-virtualizer {
height: 100%;
width: 100%;
}
lit-virtualizer {
contain: size layout !important;
}
state-history-chart-timeline,
state-history-chart-line {
width: 100%;
}
`; `;
} }
} }

View File

@@ -269,8 +269,8 @@ export class HaDataTable extends LitElement {
@change=${this._handleHeaderRowCheckboxClick} @change=${this._handleHeaderRowCheckboxClick}
.indeterminate=${this._checkedRows.length && .indeterminate=${this._checkedRows.length &&
this._checkedRows.length !== this._checkableRowsCount} this._checkedRows.length !== this._checkableRowsCount}
.checked=${this._checkedRows.length === .checked=${this._checkedRows.length &&
this._checkableRowsCount} this._checkedRows.length === this._checkableRowsCount}
> >
</ha-checkbox> </ha-checkbox>
</div> </div>

View File

@@ -20,7 +20,7 @@ interface HassEntityWithCachedName extends HassEntity {
friendly_name: string; friendly_name: string;
} }
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; export type HaEntityPickerEntityFilterFunc = (entity: HassEntity) => boolean;
// eslint-disable-next-line lit/prefer-static-styles // eslint-disable-next-line lit/prefer-static-styles
const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) => const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
@@ -31,6 +31,7 @@ const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
<span>${item.friendly_name}</span> <span>${item.friendly_name}</span>
<span slot="secondary">${item.entity_id}</span> <span slot="secondary">${item.entity_id}</span>
</mwc-list-item>`; </mwc-list-item>`;
@customElement("ha-entity-picker") @customElement("ha-entity-picker")
export class HaEntityPicker extends LitElement { export class HaEntityPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;

View File

@@ -12,8 +12,10 @@ import { property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { computeActiveState } from "../../common/entity/compute_active_state"; import { computeActiveState } from "../../common/entity/compute_active_state";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { iconColorCSS } from "../../common/style/icon_color_css"; import { iconColorCSS } from "../../common/style/icon_color_css";
import { cameraUrlWithWidthHeight } from "../../data/camera";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../ha-state-icon"; import "../ha-state-icon";
@@ -93,6 +95,9 @@ export class StateBadge extends LitElement {
if (this.hass) { if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl); imageUrl = this.hass.hassUrl(imageUrl);
} }
if (computeDomain(stateObj.entity_id) === "camera") {
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
}
hostStyle.backgroundImage = `url(${imageUrl})`; hostStyle.backgroundImage = `url(${imageUrl})`;
this._showIcon = false; this._showIcon = false;
} else if (stateObj.state === "on") { } else if (stateObj.state === "on") {

View File

@@ -4,8 +4,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../common/config/is_component_loaded"; import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stringCompare } from "../common/string/compare"; import { stringCompare } from "../common/string/compare";
import { HassioAddonInfo } from "../data/hassio/addon"; import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon";
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../polymer-types"; import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
@@ -78,10 +77,10 @@ class HaAddonPicker extends LitElement {
private async _getAddons() { private async _getAddons() {
try { try {
if (isComponentLoaded(this.hass, "hassio")) { if (isComponentLoaded(this.hass, "hassio")) {
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass); const addonsInfo = await fetchHassioAddonsInfo(this.hass);
this._addons = supervisorInfo.addons.sort((a, b) => this._addons = addonsInfo.addons
stringCompare(a.name, b.name) .filter((addon) => addon.version)
); .sort((a, b) => stringCompare(a.name, b.name));
} else { } else {
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(

View File

@@ -1,17 +1,22 @@
import type { Button } from "@material/mwc-button";
import "@material/mwc-menu"; import "@material/mwc-menu";
import type { Corner, Menu, MenuCorner } from "@material/mwc-menu"; import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
import type { HaIconButton } from "./ha-icon-button";
@customElement("ha-button-menu") @customElement("ha-button-menu")
export class HaButtonMenu extends LitElement { export class HaButtonMenu extends LitElement {
protected readonly [FOCUS_TARGET];
@property() public corner: Corner = "TOP_START"; @property() public corner: Corner = "TOP_START";
@property() public menuCorner: MenuCorner = "START"; @property() public menuCorner: MenuCorner = "START";
@property({ type: Number }) public x?: number; @property({ type: Number }) public x: number | null = null;
@property({ type: Number }) public y?: number; @property({ type: Number }) public y: number | null = null;
@property({ type: Boolean }) public multi = false; @property({ type: Boolean }) public multi = false;
@@ -31,10 +36,18 @@ export class HaButtonMenu extends LitElement {
return this._menu?.selected; return this._menu?.selected;
} }
public override focus() {
if (this._menu?.open) {
this._menu.focusItemAtIndex(0);
} else {
this._triggerButton?.focus();
}
}
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div @click=${this._handleClick}> <div @click=${this._handleClick}>
<slot name="trigger"></slot> <slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
</div> </div>
<mwc-menu <mwc-menu
.corner=${this.corner} .corner=${this.corner}
@@ -50,6 +63,21 @@ export class HaButtonMenu extends LitElement {
`; `;
} }
protected firstUpdated(changedProps): void {
super.firstUpdated(changedProps);
if (document.dir === "rtl") {
this.updateComplete.then(() => {
this.querySelectorAll("mwc-list-item").forEach((item) => {
const style = document.createElement("style");
style.innerHTML =
"span.material-icons:first-of-type { margin-left: var(--mdc-list-item-graphic-margin, 32px) !important; margin-right: 0px !important;}";
item!.shadowRoot!.appendChild(style);
});
});
}
}
private _handleClick(): void { private _handleClick(): void {
if (this.disabled) { if (this.disabled) {
return; return;
@@ -58,6 +86,18 @@ export class HaButtonMenu extends LitElement {
this._menu!.show(); this._menu!.show();
} }
private get _triggerButton() {
return this.querySelector(
'ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]'
) as HaIconButton | Button | null;
}
private _setTriggerAria() {
if (this._triggerButton) {
this._triggerButton.ariaHasPopup = "menu";
}
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {

View File

@@ -66,9 +66,12 @@ export class HaChip extends LitElement {
line-height: 14px; line-height: 14px;
color: var(--ha-chip-icon-color, var(--ha-chip-text-color)); color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
} }
.mdc-chip.no-text .mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) { .mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
margin-right: -4px; margin-right: -4px;
margin-inline-start: -4px;
margin-inline-end: 4px;
direction: var(--direction);
} }
span[role="gridcell"] { span[role="gridcell"] {

View File

@@ -1,17 +1,13 @@
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base"; import { css, CSSResultGroup, html } from "lit";
import { styles } from "@material/mwc-list/mwc-list-item.css";
import { css, CSSResult, html } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { HaListItem } from "./ha-list-item";
@customElement("ha-clickable-list-item") @customElement("ha-clickable-list-item")
export class HaClickableListItem extends ListItemBase { export class HaClickableListItem extends HaListItem {
@property() public href?: string; @property() public href?: string;
@property({ type: Boolean }) public disableHref = false; @property({ type: Boolean }) public disableHref = false;
// property used only in css
@property({ type: Boolean, reflect: true }) public rtl = false;
@property({ type: Boolean, reflect: true }) public openNewTab = false; @property({ type: Boolean, reflect: true }) public openNewTab = false;
@query("a") private _anchor!: HTMLAnchorElement; @query("a") private _anchor!: HTMLAnchorElement;
@@ -39,22 +35,10 @@ export class HaClickableListItem extends ListItemBase {
}); });
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
styles, super.styles,
css` css`
:host {
padding-left: 0px;
padding-right: 0px;
}
:host([rtl]) span {
margin-left: var(--mdc-list-item-graphic-margin, 20px) !important;
margin-right: 0px !important;
}
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
a { a {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { formatNumber } from "../common/number/format_number"; import { formatNumber } from "../common/number/format_number";
import { CLIMATE_PRESET_NONE } from "../data/climate"; import { CLIMATE_PRESET_NONE } from "../data/climate";
import { UNAVAILABLE_STATES } from "../data/entity";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
@customElement("ha-climate-state") @customElement("ha-climate-state")
@@ -15,22 +16,22 @@ class HaClimateState extends LitElement {
const currentStatus = this._computeCurrentStatus(); const currentStatus = this._computeCurrentStatus();
return html`<div class="target"> return html`<div class="target">
${this.stateObj.state !== "unknown" ${!UNAVAILABLE_STATES.includes(this.stateObj.state)
? html`<span class="state-label"> ? html`<span class="state-label">
${this._localizeState()} ${this._localizeState()}
${this.stateObj.attributes.preset_mode && ${this.stateObj.attributes.preset_mode &&
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
? html`- ? html`-
${this.hass.localize( ${this.hass.localize(
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}` `state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
) || this.stateObj.attributes.preset_mode}` ) || this.stateObj.attributes.preset_mode}`
: ""} : ""}
</span>` </span>
: ""} <div class="unit">${this._computeTarget()}</div>`
<div class="unit">${this._computeTarget()}</div> : this._localizeState()}
</div> </div>
${currentStatus ${currentStatus && !UNAVAILABLE_STATES.includes(this.stateObj.state)
? html`<div class="current"> ? html`<div class="current">
${this.hass.localize("ui.card.climate.currently")}: ${this.hass.localize("ui.card.climate.currently")}:
<div class="unit">${currentStatus}</div> <div class="unit">${currentStatus}</div>
@@ -108,6 +109,10 @@ class HaClimateState extends LitElement {
} }
private _localizeState(): string { private _localizeState(): string {
if (UNAVAILABLE_STATES.includes(this.stateObj.state)) {
return this.hass.localize(`state.default.${this.stateObj.state}`);
}
const stateString = this.hass.localize( const stateString = this.hass.localize(
`component.climate.state._.${this.stateObj.state}` `component.climate.state._.${this.stateObj.state}`
); );

View File

@@ -2,6 +2,7 @@ import type {
Completion, Completion,
CompletionContext, CompletionContext,
CompletionResult, CompletionResult,
CompletionSource,
} from "@codemirror/autocomplete"; } from "@codemirror/autocomplete";
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view"; import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
import { HassEntities } from "home-assistant-js-websocket"; import { HassEntities } from "home-assistant-js-websocket";
@@ -11,6 +12,7 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { loadCodeMirror } from "../resources/codemirror.ondemand"; import { loadCodeMirror } from "../resources/codemirror.ondemand";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-icon";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@@ -26,6 +28,12 @@ const saveKeyBinding: KeyBinding = {
}, },
}; };
const renderIcon = (completion: Completion) => {
const icon = document.createElement("ha-icon");
icon.icon = completion.label;
return icon;
};
@customElement("ha-code-editor") @customElement("ha-code-editor")
export class HaCodeEditor extends ReactiveElement { export class HaCodeEditor extends ReactiveElement {
public codemirror?: EditorView; public codemirror?: EditorView;
@@ -41,12 +49,17 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean, attribute: "autocomplete-entities" }) @property({ type: Boolean, attribute: "autocomplete-entities" })
public autocompleteEntities = false; public autocompleteEntities = false;
@property({ type: Boolean, attribute: "autocomplete-icons" })
public autocompleteIcons = false;
@property() public error = false; @property() public error = false;
@state() private _value = ""; @state() private _value = "";
private _loadedCodeMirror?: typeof import("../resources/codemirror"); private _loadedCodeMirror?: typeof import("../resources/codemirror");
private _iconList?: Completion[];
public set value(value: string) { public set value(value: string) {
this._value = value; this._value = value;
} }
@@ -151,13 +164,22 @@ export class HaCodeEditor extends ReactiveElement {
), ),
]; ];
if (!this.readOnly && this.autocompleteEntities && this.hass) { if (!this.readOnly) {
extensions.push( const completionSources: CompletionSource[] = [];
this._loadedCodeMirror.autocompletion({ if (this.autocompleteEntities && this.hass) {
override: [this._entityCompletions.bind(this)], completionSources.push(this._entityCompletions.bind(this));
maxRenderedOptions: 10, }
}) if (this.autocompleteIcons) {
); completionSources.push(this._mdiCompletions.bind(this));
}
if (completionSources.length > 0) {
extensions.push(
this._loadedCodeMirror.autocompletion({
override: completionSources,
maxRenderedOptions: 10,
})
);
}
} }
this.codemirror = new this._loadedCodeMirror.EditorView({ this.codemirror = new this._loadedCodeMirror.EditorView({
@@ -187,7 +209,7 @@ export class HaCodeEditor extends ReactiveElement {
private _entityCompletions( private _entityCompletions(
context: CompletionContext context: CompletionContext
): CompletionResult | null | Promise<CompletionResult | null> { ): CompletionResult | null | Promise<CompletionResult | null> {
const entityWord = context.matchBefore(/[a-z_]{3,}\./); const entityWord = context.matchBefore(/[a-z_]{3,}\.\w*/);
if ( if (
!entityWord || !entityWord ||
@@ -205,7 +227,48 @@ export class HaCodeEditor extends ReactiveElement {
return { return {
from: Number(entityWord.from), from: Number(entityWord.from),
options: states, options: states,
span: /^\w*.\w*$/, span: /^[a-z_]{3,}\.\w*$/,
};
}
private _getIconItems = async (): Promise<Completion[]> => {
if (!this._iconList) {
let iconList: {
name: string;
keywords: string[];
}[];
if (__SUPERVISOR__) {
iconList = [];
} else {
iconList = (await import("../../build/mdi/iconList.json")).default;
}
this._iconList = iconList.map((icon) => ({
type: "variable",
label: `mdi:${icon.name}`,
detail: icon.keywords.join(", "),
info: renderIcon,
}));
}
return this._iconList;
};
private async _mdiCompletions(
context: CompletionContext
): Promise<CompletionResult | null> {
const match = context.matchBefore(/mdi:\S*/);
if (!match || (match.from === match.to && !context.explicit)) {
return null;
}
const iconItems = await this._getIconItems();
return {
from: Number(match.from),
options: iconItems,
span: /^mdi:\S*$/,
}; };
} }

View File

@@ -1,13 +1,17 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light"; import type {
ComboBoxLight,
ComboBoxLightFilterChangedEvent,
ComboBoxLightOpenedChangedEvent,
ComboBoxLightValueChangedEvent,
} from "@vaadin/combo-box/vaadin-combo-box-light";
import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles"; import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-textfield"; import "./ha-textfield";
@@ -96,6 +100,8 @@ export class HaComboBox extends LitElement {
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight; @query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
private _overlayMutationObserver?: MutationObserver;
public open() { public open() {
this.updateComplete.then(() => { this.updateComplete.then(() => {
this._comboBox?.open(); this._comboBox?.open();
@@ -108,6 +114,14 @@ export class HaComboBox extends LitElement {
}); });
} }
public disconnectedCallback() {
super.disconnectedCallback();
if (this._overlayMutationObserver) {
this._overlayMutationObserver.disconnect();
this._overlayMutationObserver = undefined;
}
}
public get selectedItem() { public get selectedItem() {
return this._comboBox.selectedItem; return this._comboBox.selectedItem;
} }
@@ -193,21 +207,64 @@ export class HaComboBox extends LitElement {
} }
} }
private _openedChanged(ev: PolymerChangedEvent<boolean>) { private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) {
const opened = ev.detail.value;
// delay this so we can handle click event before setting _opened // delay this so we can handle click event before setting _opened
setTimeout(() => { setTimeout(() => {
this._opened = ev.detail.value; this._opened = opened;
}, 0); }, 0);
// @ts-ignore // @ts-ignore
fireEvent(this, ev.type, ev.detail); fireEvent(this, ev.type, ev.detail);
if (
opened &&
"MutationObserver" in window &&
!this._overlayMutationObserver
) {
const overlay = document.querySelector<HTMLElement>(
"vaadin-combo-box-overlay"
);
if (!overlay) {
return;
}
this._overlayMutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "inert"
) {
this._overlayMutationObserver?.disconnect();
this._overlayMutationObserver = undefined;
// @ts-expect-error
overlay.inert = false;
} else if (mutation.type === "childList") {
mutation.removedNodes.forEach((node) => {
if (node.nodeName === "VAADIN-COMBO-BOX-OVERLAY") {
this._overlayMutationObserver?.disconnect();
this._overlayMutationObserver = undefined;
}
});
}
});
});
this._overlayMutationObserver.observe(overlay, {
attributes: true,
});
this._overlayMutationObserver.observe(document.body, {
childList: true,
});
}
} }
private _filterChanged(ev: PolymerChangedEvent<string>) { private _filterChanged(ev: ComboBoxLightFilterChangedEvent) {
// @ts-ignore // @ts-ignore
fireEvent(this, ev.type, ev.detail, { composed: false }); fireEvent(this, ev.type, ev.detail, { composed: false });
} }
private _valueChanged(ev: PolymerChangedEvent<string>) { private _valueChanged(ev: ComboBoxLightValueChangedEvent) {
ev.stopPropagation(); ev.stopPropagation();
const newValue = ev.detail.value; const newValue = ev.detail.value;
@@ -241,6 +298,9 @@ export class HaComboBox extends LitElement {
.toggle-button { .toggle-button {
right: 12px; right: 12px;
top: -10px; top: -10px;
inset-inline-start: initial;
inset-inline-end: 12px;
direction: var(--direction);
} }
:host([opened]) .toggle-button { :host([opened]) .toggle-button {
color: var(--primary-color); color: var(--primary-color);
@@ -249,18 +309,9 @@ export class HaComboBox extends LitElement {
--mdc-icon-size: 20px; --mdc-icon-size: 20px;
top: -7px; top: -7px;
right: 36px; right: 36px;
} inset-inline-start: initial;
inset-inline-end: 36px;
:host-context([style*="direction: rtl;"]) .toggle-button { direction: var(--direction);
left: 12px;
right: auto;
top: -10px;
}
:host-context([style*="direction: rtl;"]) .clear-button {
--mdc-icon-size: 20px;
top: -7px;
left: 36px;
right: auto;
} }
`; `;
} }

View File

@@ -140,6 +140,9 @@ export class HaDateRangePicker extends LitElement {
return css` return css`
ha-svg-icon { ha-svg-icon {
margin-right: 8px; margin-right: 8px;
margin-inline-end: 8px;
margin-inline-start: initial;
direction: var(--direction);
} }
.date-range-inputs { .date-range-inputs {
@@ -166,6 +169,9 @@ export class HaDateRangePicker extends LitElement {
ha-textfield:last-child { ha-textfield:last-child {
margin-left: 8px; margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
direction: var(--direction);
} }
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {

View File

@@ -3,8 +3,8 @@ import { styles } from "@material/mwc-dialog/mwc-dialog.css";
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { css, html, TemplateResult } from "lit"; import { css, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import { computeRTLDirection } from "../common/util/compute_rtl";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
import "./ha-icon-button"; import "./ha-icon-button";
export const createCloseHeading = ( export const createCloseHeading = (
@@ -17,12 +17,13 @@ export const createCloseHeading = (
.path=${mdiClose} .path=${mdiClose}
dialogAction="close" dialogAction="close"
class="header_button" class="header_button"
dir=${computeRTLDirection(hass)}
></ha-icon-button> ></ha-icon-button>
`; `;
@customElement("ha-dialog") @customElement("ha-dialog")
export class HaDialog extends DialogBase { export class HaDialog extends DialogBase {
protected readonly [FOCUS_TARGET];
public scrollToPos(x: number, y: number) { public scrollToPos(x: number, y: number) {
this.contentElement?.scrollTo(x, y); this.contentElement?.scrollTo(x, y);
} }
@@ -89,18 +90,19 @@ export class HaDialog extends DialogBase {
} }
.header_title { .header_title {
margin-right: 40px; margin-right: 40px;
margin-inline-end: 40px;
margin-inline-start: initial;
direction: var(--direction);
} }
[dir="rtl"].header_button { .header_button {
right: auto; inset-inline-start: initial;
left: 16px; inset-inline-end: 16px;
direction: var(--direction);
} }
[dir="rtl"].header_title { .dialog-actions {
margin-left: 40px; inset-inline-start: initial !important;
margin-right: 0px; inset-inline-end: 0px !important;
} direction: var(--direction);
:host-context([style*="direction: rtl;"]) .dialog-actions {
left: 0px !important;
right: auto !important;
} }
`, `,
]; ];

View File

@@ -133,6 +133,9 @@ class HaExpansionPanel extends LitElement {
.summary-icon { .summary-icon {
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
margin-left: auto; margin-left: auto;
margin-inline-start: auto;
margin-inline-end: initial;
direction: var(--direction);
} }
.summary-icon.expanded { .summary-icon.expanded {

View File

@@ -1,24 +1,33 @@
import { Fab } from "@material/mwc-fab"; import { FabBase } from "@material/mwc-fab/mwc-fab-base";
import { styles } from "@material/mwc-fab/mwc-fab.css";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import { css } from "lit"; import { css } from "lit";
@customElement("ha-fab") @customElement("ha-fab")
export class HaFab extends Fab { export class HaFab extends FabBase {
protected firstUpdated(changedProperties) { protected firstUpdated(changedProperties) {
super.firstUpdated(changedProperties); super.firstUpdated(changedProperties);
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)"); this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
} }
static override styles = Fab.styles.concat([ static override styles = [
styles,
css` css`
:host-context([style*="direction: rtl;"]) :host .mdc-fab--extended .mdc-fab__icon {
.mdc-fab--extended margin-inline-start: -8px;
.mdc-fab__icon { margin-inline-end: 12px;
margin-left: 12px !important; direction: var(--direction);
margin-right: calc(12px - 20px) !important;
} }
`, `,
]); // safari workaround - must be explicit
document.dir === "rtl"
? css`
:host .mdc-fab--extended .mdc-fab__icon {
direction: rtl;
}
`
: css``,
];
} }
declare global { declare global {

View File

@@ -175,24 +175,23 @@ export class HaFileUpload extends LitElement {
} }
.mdc-text-field__icon--leading { .mdc-text-field__icon--leading {
margin-bottom: 12px; margin-bottom: 12px;
} inset-inline-start: initial;
:host-context([style*="direction: rtl;"]) inset-inline-end: 0px;
.mdc-text-field__icon--leading { direction: var(--direction);
margin-right: 0px;
} }
.mdc-text-field--filled .mdc-floating-label--float-above { .mdc-text-field--filled .mdc-floating-label--float-above {
transform: scale(0.75); transform: scale(0.75);
top: 8px; top: 8px;
} }
:host-context([style*="direction: rtl;"]) .mdc-floating-label { .mdc-floating-label {
left: initial; inset-inline-start: 16px !important;
right: 16px; inset-inline-end: initial !important;
direction: var(--direction);
} }
:host-context([style*="direction: rtl;"]) .mdc-text-field--filled .mdc-floating-label {
.mdc-text-field--filled inset-inline-start: 48px !important;
.mdc-floating-label { inset-inline-end: initial !important;
left: initial; direction: var(--direction);
right: 48px;
} }
.dragged:before { .dragged:before {
position: var(--layout-fit_-_position); position: var(--layout-fit_-_position);

View File

@@ -205,6 +205,9 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
ha-formfield { ha-formfield {
display: block; display: block;
padding-right: 16px; padding-right: 16px;
padding-inline-end: 16px;
padding-inline-start: initial;
direction: var(--direction);
} }
ha-textfield { ha-textfield {
display: block; display: block;
@@ -216,6 +219,9 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
right: 1em; right: 1em;
top: 1em; top: 1em;
cursor: pointer; cursor: pointer;
inset-inline-end: 1em;
inset-inline-start: initial;
direction: var(--direction);
} }
:host([opened]) ha-svg-icon { :host([opened]) ha-svg-icon {
color: var(--primary-color); color: var(--primary-color);

View File

@@ -133,9 +133,10 @@ export class HaFormString extends LitElement implements HaFormElement {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
:host-context([style*="direction: rtl;"]) ha-icon-button { ha-icon-button {
right: auto; inset-inline-start: initial;
left: 12px; inset-inline-end: 12px;
direction: var(--direction);
} }
`; `;
} }

View File

@@ -28,10 +28,15 @@ export class HaFormfield extends FormfieldBase {
css` css`
:host(:not([alignEnd])) ::slotted(ha-switch) { :host(:not([alignEnd])) ::slotted(ha-switch) {
margin-right: 10px; margin-right: 10px;
margin-inline-end: 10px;
margin-inline-start: inline;
} }
:host([dir="rtl"]:not([alignEnd])) ::slotted(ha-switch) { .mdc-form-field > label {
margin-left: 10px; direction: var(--direction);
margin-right: auto; margin-inline-start: 0;
margin-inline-end: auto;
padding-inline-start: 4px;
padding-inline-end: 0;
} }
`, `,
]; ];

View File

@@ -14,6 +14,7 @@ const getAngle = (value: number, min: number, max: number) => {
export interface LevelDefinition { export interface LevelDefinition {
level: number; level: number;
stroke: string; stroke: string;
label?: string;
} }
@customElement("ha-gauge") @customElement("ha-gauge")
@@ -38,22 +39,31 @@ export class Gauge extends LitElement {
@state() private _updated = false; @state() private _updated = false;
@state() private _segment_label? = "";
protected firstUpdated(changedProperties: PropertyValues) { protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties); super.firstUpdated(changedProperties);
// Wait for the first render for the initial animation to work // Wait for the first render for the initial animation to work
afterNextRender(() => { afterNextRender(() => {
this._updated = true; this._updated = true;
this._angle = getAngle(this.value, this.min, this.max); this._angle = getAngle(this.value, this.min, this.max);
this._segment_label = this.getSegmentLabel();
this._rescale_svg(); this._rescale_svg();
}); });
} }
protected updated(changedProperties: PropertyValues) { protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties); super.updated(changedProperties);
if (!this._updated || !changedProperties.has("value")) { if (
!this._updated ||
(!changedProperties.has("value") &&
!changedProperties.has("label") &&
!changedProperties.has("_segment_label"))
) {
return; return;
} }
this._angle = getAngle(this.value, this.min, this.max); this._angle = getAngle(this.value, this.min, this.max);
this._segment_label = this.getSegmentLabel();
this._rescale_svg(); this._rescale_svg();
} }
@@ -118,9 +128,11 @@ export class Gauge extends LitElement {
</svg> </svg>
<svg class="text"> <svg class="text">
<text class="value-text"> <text class="value-text">
${this.valueText || formatNumber(this.value, this.locale)} ${ ${
this.label this._segment_label
} ? this._segment_label
: this.valueText || formatNumber(this.value, this.locale)
} ${this._segment_label ? "" : this.label}
</text> </text>
</svg>`; </svg>`;
} }
@@ -137,6 +149,18 @@ export class Gauge extends LitElement {
); );
} }
private getSegmentLabel() {
if (this.levels) {
this.levels.sort((a, b) => a.level - b.level);
for (let i = this.levels.length - 1; i >= 0; i--) {
if (this.value >= this.levels[i].level) {
return this.levels[i].label;
}
}
}
return "";
}
static get styles() { static get styles() {
return css` return css`
:host { :host {

View File

@@ -1,6 +1,8 @@
import "@material/mwc-icon-button"; import "@material/mwc-icon-button";
import type { IconButton } from "@material/mwc-icon-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import "./ha-svg-icon"; import "./ha-svg-icon";
@customElement("ha-icon-button") @customElement("ha-icon-button")
@@ -11,21 +13,32 @@ export class HaIconButton extends LitElement {
@property({ type: String }) path?: string; @property({ type: String }) path?: string;
// Label that is used for ARIA support and as tooltip // Label that is used for ARIA support and as tooltip
@property({ type: String }) label = ""; @property({ type: String }) label?: string;
// These should always be set as properties, not attributes,
// so that only the <button> element gets the attribute
@property({ type: String, attribute: "aria-haspopup" })
override ariaHasPopup!: IconButton["ariaHasPopup"];
@property({ type: Boolean }) hideTitle = false; @property({ type: Boolean }) hideTitle = false;
@query("mwc-icon-button", true) private _button?: IconButton;
public override focus() {
this._button?.focus();
}
static shadowRootOptions: ShadowRootInit = { static shadowRootOptions: ShadowRootInit = {
mode: "open", mode: "open",
delegatesFocus: true, delegatesFocus: true,
}; };
protected render(): TemplateResult { protected render(): TemplateResult {
// Note: `ariaLabel` required despite the `mwc-icon-button` docs saying `label` should be enough
return html` return html`
<mwc-icon-button <mwc-icon-button
.ariaLabel=${this.label} aria-label=${ifDefined(this.label)}
.title=${this.hideTitle ? "" : this.label} title=${ifDefined(this.hideTitle ? undefined : this.label)}
aria-haspopup=${ifDefined(this.ariaHasPopup)}
.disabled=${this.disabled} .disabled=${this.disabled}
> >
${this.path ${this.path

View File

@@ -0,0 +1,42 @@
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
import { styles } from "@material/mwc-list/mwc-list-item.css";
import { css, CSSResultGroup } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-list-item")
export class HaListItem extends ListItemBase {
static get styles(): CSSResultGroup {
return [
styles,
css`
:host {
padding-left: var(--mdc-list-side-padding, 20px);
padding-right: var(--mdc-list-side-padding, 20px);
}
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
span.material-icons:first-of-type {
margin-inline-start: 0px !important;
margin-inline-end: var(
--mdc-list-item-graphic-margin,
16px
) !important;
direction: var(--direction);
}
span.material-icons:last-of-type {
margin-inline-start: auto !important;
margin-inline-end: 0px !important;
direction: var(--direction);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-list-item": HaListItem;
}
}

View File

@@ -47,9 +47,18 @@ export class HaSelect extends SelectBase {
.mdc-select__anchor { .mdc-select__anchor {
width: var(--ha-select-min-width, 200px); width: var(--ha-select-min-width, 200px);
} }
:host-context([style*="direction: rtl;"]) .mdc-floating-label { .mdc-select--filled .mdc-floating-label {
right: 16px !important; inset-inline-start: 12px;
left: initial !important; inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select .mdc-select__anchor {
padding-inline-start: 12px;
padding-inline-end: 0px;
direction: var(--direction);
}
.mdc-select__anchor .mdc-floating-label--float-above {
transform-origin: var(--float-start);
} }
`, `,
]; ];

View File

@@ -1,15 +1,24 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; import memoizeOne from "memoize-one";
import { DeviceRegistryEntry } from "../../data/device_registry"; import { DeviceRegistryEntry } from "../../data/device_registry";
import { EntityRegistryEntry } from "../../data/entity_registry"; import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { AreaSelector } from "../../data/selector"; import { AreaSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "../ha-area-picker"; import "../ha-area-picker";
import "../ha-areas-picker"; import "../ha-areas-picker";
@customElement("ha-selector-area") @customElement("ha-selector-area")
export class HaAreaSelector extends LitElement { export class HaAreaSelector extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public selector!: AreaSelector; @property() public selector!: AreaSelector;
@@ -20,29 +29,44 @@ export class HaAreaSelector extends LitElement {
@property() public helper?: string; @property() public helper?: string;
@state() public _configEntries?: ConfigEntry[]; @state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true; @property({ type: Boolean }) public required = true;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
protected updated(changedProperties) { protected updated(changedProperties) {
if (changedProperties.has("selector")) { if (
const oldSelector = changedProperties.get("selector"); changedProperties.has("selector") &&
if ( (this.selector.area.device?.integration ||
oldSelector !== this.selector && this.selector.area.entity?.integration) &&
this.selector.area.device?.integration !this._entitySources
) { ) {
getConfigEntries(this.hass, { fetchEntitySourcesWithCache(this.hass).then((sources) => {
domain: this.selector.area.device.integration, this._entitySources = sources;
}).then((entries) => { });
this._configEntries = entries;
});
}
} }
} }
protected render() { protected render() {
if (
(this.selector.area.device?.integration ||
this.selector.area.entity?.integration) &&
!this._entitySources
) {
return html``;
}
if (!this.selector.area.multiple) { if (!this.selector.area.multiple) {
return html` return html`
<ha-area-picker <ha-area-picker
@@ -87,39 +111,62 @@ export class HaAreaSelector extends LitElement {
} }
private _filterEntities = (entity: EntityRegistryEntry): boolean => { private _filterEntities = (entity: EntityRegistryEntry): boolean => {
if (this.selector.area.entity?.integration) { const filterIntegration = this.selector.area.entity?.integration;
if (entity.platform !== this.selector.area.entity.integration) { if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
}
return true;
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
if (!this.selector.area.device) {
return true;
}
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.area.device;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false; return false;
} }
} }
return true; return true;
}; };
private _filterDevices = (device: DeviceRegistryEntry): boolean => { private _deviceIntegrations = memoizeOne(
if ( (entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
this.selector.area.device?.manufacturer && const deviceIntegrations: Record<string, string[]> = {};
device.manufacturer !== this.selector.area.device.manufacturer
) { for (const entity of entities) {
return false; const source = entitySources[entity.entity_id];
} if (!source?.domain) {
if ( continue;
this.selector.area.device?.model && }
device.model !== this.selector.area.device.model if (!deviceIntegrations[entity.device_id!]) {
) { deviceIntegrations[entity.device_id!] = [];
return false; }
} deviceIntegrations[entity.device_id!].push(source.domain);
if (this.selector.area.device?.integration) {
if (
this._configEntries &&
!this._configEntries.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {
return false;
} }
return deviceIntegrations;
} }
return true; );
};
} }
declare global { declare global {

View File

@@ -1,18 +1,33 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; import memoizeOne from "memoize-one";
import { ConfigEntry } from "../../data/config_entries";
import type { DeviceRegistryEntry } from "../../data/device_registry"; import type { DeviceRegistryEntry } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { DeviceSelector } from "../../data/selector"; import type { DeviceSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker"; import "../device/ha-device-picker";
import "../device/ha-devices-picker"; import "../device/ha-devices-picker";
@customElement("ha-selector-device") @customElement("ha-selector-device")
export class HaDeviceSelector extends LitElement { export class HaDeviceSelector extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public selector!: DeviceSelector; @property() public selector!: DeviceSelector;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property() public value?: any; @property() public value?: any;
@property() public label?: string; @property() public label?: string;
@@ -25,20 +40,32 @@ export class HaDeviceSelector extends LitElement {
@property({ type: Boolean }) public required = true; @property({ type: Boolean }) public required = true;
protected updated(changedProperties) { public hassSubscribe(): UnsubscribeFunc[] {
if (changedProperties.has("selector")) { return [
const oldSelector = changedProperties.get("selector"); subscribeEntityRegistry(this.hass.connection!, (entities) => {
if (oldSelector !== this.selector && this.selector.device?.integration) { this._entities = entities.filter((entity) => entity.device_id !== null);
getConfigEntries(this.hass, { }),
domain: this.selector.device.integration, ];
}).then((entries) => { }
this._configEntries = entries;
}); protected updated(changedProperties): void {
} super.updated(changedProperties);
if (
changedProperties.has("selector") &&
this.selector.device.integration &&
!this._entitySources
) {
fetchEntitySourcesWithCache(this.hass).then((sources) => {
this._entitySources = sources;
});
} }
} }
protected render() { protected render() {
if (this.selector.device.integration && !this._entitySources) {
return html``;
}
if (!this.selector.device.multiple) { if (!this.selector.device.multiple) {
return html` return html`
<ha-device-picker <ha-device-picker
@@ -80,30 +107,48 @@ export class HaDeviceSelector extends LitElement {
} }
private _filterDevices = (device: DeviceRegistryEntry): boolean => { private _filterDevices = (device: DeviceRegistryEntry): boolean => {
if ( const {
this.selector.device?.manufacturer && manufacturer: filterManufacturer,
device.manufacturer !== this.selector.device.manufacturer model: filterModel,
) { integration: filterIntegration,
} = this.selector.device;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false; return false;
} }
if ( if (filterModel && device.model !== filterModel) {
this.selector.device?.model &&
device.model !== this.selector.device.model
) {
return false; return false;
} }
if (this.selector.device?.integration) { if (filterIntegration && this._entitySources && this._entities) {
if ( const deviceIntegrations = this._deviceIntegrations(
this._configEntries && this._entitySources,
!this._configEntries.some((entry) => this._entities
device.config_entries.includes(entry.entry_id) );
) if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
) {
return false; return false;
} }
} }
return true; return true;
}; };
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
} }
declare global { declare global {

View File

@@ -1,4 +1,3 @@
import "@material/mwc-formfield/mwc-formfield";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
@@ -47,14 +46,14 @@ export class HaSelectSelector extends LitElement {
${this.label} ${this.label}
${options.map( ${options.map(
(item: SelectOption) => html` (item: SelectOption) => html`
<mwc-formfield .label=${item.label}> <ha-formfield .label=${item.label}>
<ha-radio <ha-radio
.checked=${item.value === this.value} .checked=${item.value === this.value}
.value=${item.value} .value=${item.value}
.disabled=${this.disabled} .disabled=${this.disabled}
@change=${this._valueChanged} @change=${this._valueChanged}
></ha-radio> ></ha-radio>
</mwc-formfield> </ha-formfield>
` `
)} )}
</div> </div>

View File

@@ -31,6 +31,7 @@ export class HaTemplateSelector extends LitElement {
.readOnly=${this.disabled} .readOnly=${this.disabled}
autofocus autofocus
autocomplete-entities autocomplete-entities
autocomplete-icons
@value-changed=${this._handleChange} @value-changed=${this._handleChange}
dir="ltr" dir="ltr"
></ha-code-editor> ></ha-code-editor>

View File

@@ -103,6 +103,9 @@ export class HaTextSelector extends LitElement {
--mdc-icon-button-size: 24px; --mdc-icon-button-size: 24px;
--mdc-icon-size: 20px; --mdc-icon-size: 20px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
inset-inline-start: initial;
inset-inline-end: 16px;
direction: var(--direction);
} }
`; `;
} }

View File

@@ -287,9 +287,7 @@ export class HaServiceControl extends LitElement {
${shouldRenderServiceDataYaml ${shouldRenderServiceDataYaml
? html`<ha-yaml-editor ? html`<ha-yaml-editor
.hass=${this.hass} .hass=${this.hass}
.label=${this.hass.localize( .label=${this.hass.localize("ui.components.service-control.data")}
"ui.components.service-control.service_data"
)}
.name=${"data"} .name=${"data"}
.defaultValue=${this._value?.data} .defaultValue=${this._value?.data}
@value-changed=${this._dataChanged} @value-changed=${this._dataChanged}

View File

@@ -42,8 +42,8 @@ import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-picker"; import "./ha-area-picker";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-svg-icon";
import "./ha-input-helper-text"; import "./ha-input-helper-text";
import "./ha-svg-icon";
@customElement("ha-target-picker") @customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) { export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -79,6 +79,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean, reflect: true }) public disabled = false;
@property({ type: Boolean }) public horizontal = false;
@state() private _areas?: { [areaId: string]: AreaRegistryEntry }; @state() private _areas?: { [areaId: string]: AreaRegistryEntry };
@state() private _devices?: { @state() private _devices?: {
@@ -117,7 +119,26 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
if (!this._areas || !this._devices || !this._entities) { if (!this._areas || !this._devices || !this._entities) {
return html``; return html``;
} }
return html`<div class="mdc-chip-set items"> return html`
${this.horizontal
? html`
<div class="horizontal-container">
${this._renderChips()} ${this._renderPicker()}
</div>
${this._renderItems()}
`
: html`
<div>
${this._renderItems()} ${this._renderPicker()}
${this._renderChips()}
</div>
`}
`;
}
private _renderItems() {
return html`
<div class="mdc-chip-set items">
${this.value?.area_id ${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => { ? ensureArray(this.value.area_id).map((area_id) => {
const area = this._areas![area_id]; const area = this._areas![area_id];
@@ -154,7 +175,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
}) })
: ""} : ""}
</div> </div>
${this._renderPicker()} `;
}
private _renderChips() {
return html`
<div class="mdc-chip-set"> <div class="mdc-chip-set">
<div <div
class="mdc-chip area_id add" class="mdc-chip area_id add"
@@ -217,10 +242,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
</span> </span>
</div> </div>
</div> </div>
${this.helper ${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>` ? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""} `; : ""}
`;
} }
private async _showPicker(ev) { private async _showPicker(ev) {
@@ -309,48 +334,54 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
private _renderPicker() { private _renderPicker() {
switch (this._addMode) { switch (this._addMode) {
case "area_id": case "area_id":
return html`<ha-area-picker return html`
.hass=${this.hass} <ha-area-picker
id="input" .hass=${this.hass}
.type=${"area_id"} id="input"
.label=${this.hass.localize( .type=${"area_id"}
"ui.components.target-picker.add_area_id" .label=${this.hass.localize(
)} "ui.components.target-picker.add_area_id"
no-add )}
.deviceFilter=${this.deviceFilter} no-add
.entityFilter=${this.entityRegFilter} .deviceFilter=${this.deviceFilter}
.includeDeviceClasses=${this.includeDeviceClasses} .entityFilter=${this.entityRegFilter}
.includeDomains=${this.includeDomains} .includeDeviceClasses=${this.includeDeviceClasses}
@value-changed=${this._targetPicked} .includeDomains=${this.includeDomains}
></ha-area-picker>`; @value-changed=${this._targetPicked}
></ha-area-picker>
`;
case "device_id": case "device_id":
return html`<ha-device-picker return html`
.hass=${this.hass} <ha-device-picker
id="input" .hass=${this.hass}
.type=${"device_id"} id="input"
.label=${this.hass.localize( .type=${"device_id"}
"ui.components.target-picker.add_device_id" .label=${this.hass.localize(
)} "ui.components.target-picker.add_device_id"
.deviceFilter=${this.deviceFilter} )}
.entityFilter=${this.entityRegFilter} .deviceFilter=${this.deviceFilter}
.includeDeviceClasses=${this.includeDeviceClasses} .entityFilter=${this.entityRegFilter}
.includeDomains=${this.includeDomains} .includeDeviceClasses=${this.includeDeviceClasses}
@value-changed=${this._targetPicked} .includeDomains=${this.includeDomains}
></ha-device-picker>`; @value-changed=${this._targetPicked}
></ha-device-picker>
`;
case "entity_id": case "entity_id":
return html`<ha-entity-picker return html`
.hass=${this.hass} <ha-entity-picker
id="input" .hass=${this.hass}
.type=${"entity_id"} id="input"
.label=${this.hass.localize( .type=${"entity_id"}
"ui.components.target-picker.add_entity_id" .label=${this.hass.localize(
)} "ui.components.target-picker.add_entity_id"
.entityFilter=${this.entityFilter} )}
.includeDeviceClasses=${this.includeDeviceClasses} .entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains} .includeDeviceClasses=${this.includeDeviceClasses}
@value-changed=${this._targetPicked} .includeDomains=${this.includeDomains}
allow-custom-entity @value-changed=${this._targetPicked}
></ha-entity-picker>`; allow-custom-entity
></ha-entity-picker>
`;
} }
return html``; return html``;
} }
@@ -539,6 +570,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
${unsafeCSS(chipStyles)} ${unsafeCSS(chipStyles)}
.horizontal-container {
display: flex;
flex-wrap: wrap;
min-height: 56px;
align-items: center;
}
.mdc-chip { .mdc-chip {
color: var(--primary-text-color); color: var(--primary-text-color);
} }
@@ -569,6 +606,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
height: 16px; height: 16px;
--mdc-icon-size: 14px; --mdc-icon-size: 14px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
margin-inline-start: 4px !important;
margin-inline-end: -4px !important;
direction: var(--direction);
} }
.mdc-chip__icon--leading { .mdc-chip__icon--leading {
display: flex; display: flex;
@@ -578,6 +618,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
border-radius: 50%; border-radius: 50%;
padding: 6px; padding: 6px;
margin-left: -14px !important; margin-left: -14px !important;
margin-inline-start: -14px !important;
margin-inline-end: 4px !important;
direction: var(--direction);
} }
.expand-btn { .expand-btn {
margin-right: 0; margin-right: 0;
@@ -616,10 +659,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
opacity: var(--light-disabled-opacity); opacity: var(--light-disabled-opacity);
pointer-events: none; pointer-events: none;
} }
:host-context([style*="direction: rtl;"]) .mdc-chip__icon {
margin-right: -14px !important;
margin-left: 4px !important;
}
`; `;
} }
} }

View File

@@ -57,6 +57,14 @@ export class HaTextField extends TextFieldBase {
.mdc-text-field__affix--suffix { .mdc-text-field__affix--suffix {
padding-left: var(--text-field-suffix-padding-left, 12px); padding-left: var(--text-field-suffix-padding-left, 12px);
padding-right: var(--text-field-suffix-padding-right, 0px); padding-right: var(--text-field-suffix-padding-right, 0px);
padding-inline-start: var(--text-field-suffix-padding-left, 12px);
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
direction: var(--direction);
}
.mdc-text-field--with-leading-icon {
padding-inline-start: var(--text-field-suffix-padding-left, 0px);
padding-inline-end: var(--text-field-suffix-padding-right, 16px);
direction: var(--direction);
} }
.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field:not(.mdc-text-field--disabled)
@@ -68,6 +76,12 @@ export class HaTextField extends TextFieldBase {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.mdc-text-field__icon--leading {
margin-inline-start: 16px;
margin-inline-end: 8px;
direction: var(--direction);
}
input { input {
text-align: var(--text-field-text-align); text-align: var(--text-field-text-align);
} }
@@ -92,19 +106,40 @@ export class HaTextField extends TextFieldBase {
overflow: var(--text-field-overflow); overflow: var(--text-field-overflow);
} }
:host-context([style*="direction: rtl;"]) .mdc-floating-label { .mdc-floating-label {
right: 10px !important; inset-inline-start: 16px !important;
left: initial !important; inset-inline-end: initial !important;
transform-origin: var(--float-start);
direction: var(--direction);
transform-origin: var(--float-start);
} }
:host-context([style*="direction: rtl;"]) .mdc-text-field--with-leading-icon.mdc-text-field--filled
.mdc-text-field--with-leading-icon.mdc-text-field--filled
.mdc-floating-label { .mdc-floating-label {
max-width: calc(100% - 48px); max-width: calc(100% - 48px);
right: 48px !important; inset-inline-start: 48px !important;
left: initial !important; inset-inline-end: initial !important;
direction: var(--direction);
}
.mdc-text-field__input[type="number"] {
direction: var(--direction);
} }
`, `,
// safari workaround - must be explicit
document.dir === "rtl"
? css`
.mdc-text-field__affix--suffix,
.mdc-text-field--with-leading-icon,
.mdc-text-field__icon--leading,
.mdc-floating-label,
.mdc-text-field--with-leading-icon.mdc-text-field--filled
.mdc-floating-label,
.mdc-text-field__input[type="number"] {
direction: rtl;
}
`
: css``,
]; ];
} }

View File

@@ -70,6 +70,7 @@ export class HaYamlEditor extends LitElement {
.readOnly=${this.readOnly} .readOnly=${this.readOnly}
mode="yaml" mode="yaml"
autocomplete-entities autocomplete-entities
autocomplete-icons
.error=${this.isValid === false} .error=${this.isValid === false}
@value-changed=${this._onChange} @value-changed=${this._onChange}
dir="ltr" dir="ltr"

View File

@@ -314,9 +314,10 @@ class DialogMediaManage extends LitElement {
vertical-align: middle; vertical-align: middle;
} }
:host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { ha-svg-icon[slot="icon"] {
margin-left: 8px !important; margin-inline-start: 0px !important;
margin-right: 0px !important; margin-inline-end: 8px !important;
direction: var(--direction);
} }
.refresh { .refresh {

View File

@@ -36,7 +36,7 @@ declare global {
class BrowseMediaTTS extends LitElement { class BrowseMediaTTS extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public item; @property() public item!: MediaPlayerItem;
@property() public action!: MediaPlayerBrowseAction; @property() public action!: MediaPlayerBrowseAction;

View File

@@ -60,9 +60,10 @@ class MediaManageButton extends LitElement {
vertical-align: middle; vertical-align: middle;
} }
:host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { ha-svg-icon[slot="icon"] {
margin-left: 8px; margin-inline-start: 0px;
margin-right: 0px; margin-inline-end: 8px;
direction: var(--direction);
} }
`; `;
} }

View File

@@ -28,6 +28,7 @@ import { fireEvent } from "../../common/dom/fire_event";
import { computeRTLDirection } from "../../common/util/compute_rtl"; import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce"; import { debounce } from "../../common/util/debounce";
import { getSignedPath } from "../../data/auth"; import { getSignedPath } from "../../data/auth";
import { UNAVAILABLE_STATES } from "../../data/entity";
import type { MediaPlayerItem } from "../../data/media-player"; import type { MediaPlayerItem } from "../../data/media-player";
import { import {
browseMediaPlayer, browseMediaPlayer,
@@ -42,9 +43,14 @@ import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer"; import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import { haStyle } from "../../resources/styles"; import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url"; import {
brandsUrl,
extractDomainFromBrandUrl,
isBrandUrl,
} from "../../util/brands-url";
import { documentationUrl } from "../../util/documentation-url"; import { documentationUrl } from "../../util/documentation-url";
import "../entity/ha-entity-picker"; import "../entity/ha-entity-picker";
import "../ha-alert";
import "../ha-button-menu"; import "../ha-button-menu";
import "../ha-card"; import "../ha-card";
import "../ha-circular-progress"; import "../ha-circular-progress";
@@ -110,9 +116,6 @@ export class HaMediaPlayerBrowse extends LitElement {
private _resizeObserver?: ResizeObserver; private _resizeObserver?: ResizeObserver;
// @ts-ignore
private _intersectionObserver?: IntersectionObserver;
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._attachResizeObserver()); this.updateComplete.then(() => this._attachResizeObserver());
@@ -122,9 +125,6 @@ export class HaMediaPlayerBrowse extends LitElement {
if (this._resizeObserver) { if (this._resizeObserver) {
this._resizeObserver.disconnect(); this._resizeObserver.disconnect();
} }
if (this._intersectionObserver) {
this._intersectionObserver.disconnect();
}
} }
public async refresh() { public async refresh() {
@@ -246,6 +246,16 @@ export class HaMediaPlayerBrowse extends LitElement {
], ],
replace: true, replace: true,
}); });
} else if (
err.code === "entity_not_found" &&
UNAVAILABLE_STATES.includes(this.hass.states[this.entityId]?.state)
) {
this._setError({
message: this.hass.localize(
`ui.components.media-browser.media_player_unavailable`
),
code: "entity_not_found",
});
} else { } else {
this._setError(err); this._setError(err);
} }
@@ -305,7 +315,11 @@ export class HaMediaPlayerBrowse extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (this._error) { if (this._error) {
return html` return html`
<div class="container">${this._renderError(this._error)}</div> <div class="container">
<ha-alert alert-type="error">
${this._renderError(this._error)}
</ha-alert>
</div>
`; `;
} }
@@ -420,7 +434,9 @@ export class HaMediaPlayerBrowse extends LitElement {
this._error this._error
? html` ? html`
<div class="container"> <div class="container">
${this._renderError(this._error)} <ha-alert alert-type="error">
${this._renderError(this._error)}
</ha-alert>
</div> </div>
` `
: isTTSMediaSource(currentItem.media_content_id) : isTTSMediaSource(currentItem.media_content_id)
@@ -463,7 +479,10 @@ export class HaMediaPlayerBrowse extends LitElement {
.layout=${grid({ .layout=${grid({
itemSize: { itemSize: {
width: "175px", width: "175px",
height: "225px", height:
childrenMediaClass.thumbnail_ratio === "portrait"
? "312px"
: "225px",
}, },
gap: "16px", gap: "16px",
flex: { preserve: "aspect-ratio" }, flex: { preserve: "aspect-ratio" },
@@ -545,6 +564,8 @@ export class HaMediaPlayerBrowse extends LitElement {
<div <div
class="${["app", "directory"].includes(child.media_class) class="${["app", "directory"].includes(child.media_class)
? "centered-image" ? "centered-image"
: ""} ${isBrandUrl(child.thumbnail)
? "brand-image"
: ""} image" : ""} image"
style="background-image: ${until(backgroundImage, "")}" style="background-image: ${until(backgroundImage, "")}"
></div> ></div>
@@ -643,7 +664,7 @@ export class HaMediaPlayerBrowse extends LitElement {
return (await getSignedPath(this.hass, thumbnailUrl)).path; return (await getSignedPath(this.hass, thumbnailUrl)).path;
} }
if (thumbnailUrl.startsWith("https://brands.home-assistant.io")) { if (isBrandUrl(thumbnailUrl)) {
// The backend is not aware of the theme used by the users, // The backend is not aware of the theme used by the users,
// so we rewrite the URL to show a proper icon // so we rewrite the URL to show a proper icon
thumbnailUrl = brandsUrl({ thumbnailUrl = brandsUrl({
@@ -1032,6 +1053,10 @@ export class HaMediaPlayerBrowse extends LitElement {
background-size: contain; background-size: contain;
} }
.brand-image {
background-size: 40%;
}
.children ha-card .icon-holder { .children ha-card .icon-holder {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@@ -120,9 +120,10 @@ class MediaUploadButton extends LitElement {
vertical-align: middle; vertical-align: middle;
} }
:host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { ha-svg-icon[slot="icon"] {
margin-left: 8px; margin-inline-start: 0px;
margin-right: 0px; margin-inline-end: 8px;
direction: var(--direction);
} }
`; `;
} }

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