Compare commits

..

202 Commits

Author SHA1 Message Date
Zack Barett
df4b83349e Merge pull request #13329 from home-assistant/dev 2022-08-02 10:20:21 -05:00
Zack Barett
d4232a2256 Bumped version to 20220802.0 (#13328) 2022-08-02 17:06:00 +02:00
Zack Barett
f7e348c19b Fix Automation Creation Dialog (#13322) 2022-08-02 13:48:22 +00:00
Zack Barett
f44fd35b90 Fix input and number more info dialog (#13321) 2022-08-02 15:26:17 +02:00
Erik Montnemery
dac1d76bd2 Offer to remove statistics for entities with unsupported state class (#13325) 2022-08-02 11:12:05 +00:00
Franck Nijhof
0ab823bcf5 Adjust MDI icon for Repairs menu (#13324) 2022-08-02 09:33:36 +02:00
dependabot[bot]
65e952aaeb Bump actions/stale from 5.1.0 to 5.1.1 (#13313)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-02 09:25:58 +02:00
Zack Barett
cfdf043444 Add ability to auto open system health with params (#13303)
* Add ability to auto open system health with params

* Update my link

* Update url when opening dialog

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

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

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

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

* Adjust to new format

* Use latest API

* Fix types

* Fix CI?

Co-authored-by: Zack <zackbarett@hey.com>
2022-07-11 19:07:07 -07:00
Zack Barett
437723c6a6 Fix Number Selector Label (#13178) 2022-07-12 01:05:47 +00:00
Bram Kragten
a30c8205b1 Update dialog styles (#13132) 2022-07-11 15:07:10 -05:00
puddly
c50cf78bb4 Allow stale ZHA coordinator entries to be deleted (#13154)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-11 13:14:37 -05:00
Zack Barett
be52ba0ea9 Fix Suggested Value in HA-Form (#13173) 2022-07-11 09:46:32 -05:00
Yosi Levy
55e9ebc4d2 Energy panel/cards - RTL fixes (#13171) 2022-07-11 15:47:22 +02:00
Bram Kragten
29c3fb0f92 Fix energy demo (#13172) 2022-07-11 08:44:34 -05:00
Erik Montnemery
7c3cd9d88d Merge pull request #13170 from home-assistant/number_customize_units
Allow customizing number unit of measurement
2022-07-11 14:49:47 +02:00
Erik
4881d699e3 Allow customizing number unit of measurement 2022-07-11 14:05:23 +02:00
Joakim Sørensen
414db83359 Hide homeassistant from partial restore if no version (#13168) 2022-07-11 12:55:43 +02:00
Joakim Sørensen
cd4f6e19f4 Await backup restore (#13167) 2022-07-11 12:50:37 +02:00
Joakim Sørensen
da709cbbd1 Add hacs_repository my redirect (#13153) 2022-07-11 09:06:45 +02:00
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
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
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
237 changed files with 6356 additions and 2140 deletions

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

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

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

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

View File

@@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2.0.1
- uses: dessant/lock-threads@v3.0.0
with:
github-token: ${{ github.token }}
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.10"
NODE_VERSION: 16
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

@@ -6,8 +6,8 @@ on:
- published
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
@@ -21,21 +21,21 @@ jobs:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -74,33 +74,11 @@ jobs:
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
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
uses: home-assistant/wheels@master
uses: home-assistant/wheels@2022.06.7
with:
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
abi: cp310
tag: musllinux_1_2
arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt"

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@v3.0.13
uses: actions/stale@v5.1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

View File

@@ -8,7 +8,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 14
NODE_VERSION: 16
jobs:
upload:
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Upload Translations
run: |

2
.nvmrc
View File

@@ -1 +1 @@
14
16

View File

@@ -27,7 +27,7 @@ module.exports = {
version() {
const version = fs
.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) {
throw Error("Version not found");
}

View File

@@ -156,3 +156,12 @@ gulp.task("gen-icons-json", (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("./gather-static.js");
require("./translations.js");
require("./gen-icons-json.js");
gulp.task(
"develop-hassio",
@@ -17,6 +18,7 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-hassio",
"gen-dummy-icons-json",
"gen-index-hassio-dev",
"build-supervisor-translations",
"copy-translations-supervisor",
@@ -33,6 +35,7 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-hassio",
"gen-dummy-icons-json",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",

View File

@@ -1 +1,30 @@
[]
[
{
"path": "M20,20H7A2,2 0 0,1 5,18V8.94L2.23,5.64C2.09,5.47 2,5.24 2,5A1,1 0 0,1 3,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M8.5,7A0.5,0.5 0 0,0 8,7.5V8.5A0.5,0.5 0 0,0 8.5,9H18.5A0.5,0.5 0 0,0 19,8.5V7.5A0.5,0.5 0 0,0 18.5,7H8.5M8.5,11A0.5,0.5 0 0,0 8,11.5V12.5A0.5,0.5 0 0,0 8.5,13H18.5A0.5,0.5 0 0,0 19,12.5V11.5A0.5,0.5 0 0,0 18.5,11H8.5M8.5,15A0.5,0.5 0 0,0 8,15.5V16.5A0.5,0.5 0 0,0 8.5,17H13.5A0.5,0.5 0 0,0 14,16.5V15.5A0.5,0.5 0 0,0 13.5,15H8.5Z",
"name": "android-messages"
},
{
"path": "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z",
"name": "book-variant-multiple"
},
{
"path": "M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z",
"name": "desktop-mac"
},
{
"path": "M21,14V4H3V14H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3C1.89,18 1,17.1 1,16V4C1,2.89 1.89,2 3,2H21M4,5H15V10H4V5M16,5H20V7H16V5M20,8V13H16V8H20M4,11H9V13H4V11M10,11H15V13H10V11Z",
"name": "desktop-mac-dashboard"
},
{
"path": "M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z",
"name": "discord"
},
{
"path": "M8.06,7.78C7.5,7.78 7.17,7.73 7.08,7.64L6.66,13.73C7.19,14.05 7.88,14.3 8.72,14.5C9.56,14.71 10.78,14.77 12.38,14.67C13.97,14.58 15.63,14.23 17.34,13.64L16.55,4.22C15.67,5.09 14.38,5.91 12.66,6.66C11.13,7.31 9.81,7.69 8.72,7.78H8.06M7.97,5.34C7.28,5.94 7,6.34 7.13,6.56C7.22,6.78 7.7,6.84 8.58,6.75C9.67,6.66 10.91,6.31 12.28,5.72C13.22,5.31 14.03,4.88 14.72,4.41C15.41,3.94 15.88,3.55 16.13,3.23C16.38,2.92 16.47,2.7 16.41,2.58C16.34,2.42 16.03,2.34 15.47,2.34C14.34,2.34 12.94,2.7 11.25,3.42C9.81,4.05 8.72,4.69 7.97,5.34M17.34,2.2C17.41,2.33 17.44,2.47 17.44,2.63L18.61,17C18.61,18.73 18,20.09 16.83,21.07C15.64,22.05 14.03,22.55 12,22.55C10,22.55 8.4,22.04 7.2,21C6,20 5.39,18.64 5.39,16.92L6.09,6.47C6.09,6.22 6.2,5.94 6.42,5.63C6.64,5.31 6.84,5.06 7.03,4.88L7.36,4.59C8.33,3.78 9.5,3.08 10.88,2.5C11.81,2.08 12.73,1.77 13.62,1.57C14.5,1.37 15.3,1.3 16,1.38C16.71,1.46 17.16,1.73 17.34,2.2Z",
"name": "google-home"
},
{
"path": "M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z",
"name": "tablet-android"
}
]

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
import {
@@ -7,9 +6,14 @@ import {
provideHass,
} from "../../src/fake_data/provide_hass";
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
import "../../src/resources/compatibility";
import { HomeAssistant } from "../../src/types";
import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockEnergy } from "./stubs/energy";
import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend";
import { mockHistory } from "./stubs/history";
@@ -20,9 +24,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -51,8 +52,36 @@ class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
]);
hass.addEntities(energyEntities());

View File

@@ -1,41 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

View File

@@ -0,0 +1,20 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfigEntries = (hass: MockHomeAssistant) => {
hass.mockWS("config_entries/get", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_remove_device: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
};

View File

@@ -4,4 +4,6 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEntityRegistry = (
hass: MockHomeAssistant,
data: EntityRegistryEntry[] = []
) => hass.mockWS("config/entity_registry/list", () => data);
) => {
hass.mockWS("config/entity_registry/list", () => data);
};

View File

@@ -466,6 +466,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results;
}
);
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"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

@@ -8,7 +8,7 @@ module.exports = [
{
category: "lovelace",
// Label for in the sidebar
header: "Lovelace",
header: "Dashboards",
// Specify order of pages. Any pages in the category folder but not listed here will
// automatically be added after the pages listed here.
pages: ["introduction"],
@@ -34,7 +34,7 @@ module.exports = [
},
{
category: "misc",
header: "Miscelaneous",
header: "Miscellaneous",
},
{
category: "brand",

View File

@@ -31,7 +31,7 @@ const ENTITIES = [
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Second Office Fan",
friendly_name: "Kitchen Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
@@ -102,7 +102,7 @@ class DemoArea extends LitElement {
picture: "/images/office.jpg",
},
{
name: "Second Office",
name: "Kitchen",
area_id: "kitchen",
picture: "/images/kitchen.png",
},

View File

@@ -1,11 +1,11 @@
---
title: Introduction
---
Lovelace has many different cards. Each card allows the user to tell
Dashboards have many different cards. Each card allows the user to tell
a different story about what is going on in their house. These cards
are very customizable, as no household is the same.
This gallery helps our developers and designers to see all the
different states that each card can be in.
Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace.
Check [the Dashboards documentation](https://www.home-assistant.io/dashboards/) for instructions on how to get started with Dashboards.

View File

@@ -194,6 +194,7 @@ const createEntityRegistryEntries = (
name: null,
icon: null,
platform: "updater",
has_entity_name: false,
},
];

View File

@@ -69,7 +69,7 @@ const ENTITIES = [
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effets Light",
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,

View File

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

View File

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

View File

@@ -336,7 +336,7 @@ class HassioAddonConfig extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.common.update_available",
"addon.failed_to_reset",
"error",
extractApiErrorMessage(err)
);

View File

@@ -81,7 +81,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.documentation.get_logs",
"addon.documentation.get_documentation",
"error",
extractApiErrorMessage(err)
);

View File

@@ -12,15 +12,17 @@ import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress";
import {
fetchAddonInfo,
fetchHassioAddonInfo,
fetchHassioAddonsInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioSupervisorInfo,
setSupervisorOption,
} from "../../../src/data/hassio/supervisor";
addStoreRepository,
fetchSupervisorStore,
StoreAddonDetails,
} from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-error-screen";
@@ -45,7 +47,9 @@ class HassioAddonDashboard extends LitElement {
@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;
@@ -173,10 +177,10 @@ class HassioAddonDashboard extends LitElement {
const requestedAddon = extractSearchParam("addon");
const requestedAddonRepository = extractSearchParam("repository_url");
if (requestedAddonRepository) {
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
const storeInfo = await fetchSupervisorStore(this.hass);
if (
!supervisorInfo.addons_repositories.find(
(repo) => repo === requestedAddonRepository
!storeInfo.repositories.find(
(repo) => repo.source === requestedAddonRepository
)
) {
if (
@@ -197,12 +201,7 @@ class HassioAddonDashboard extends LitElement {
}
try {
await setSupervisorOption(this.hass, {
addons_repositories: [
...supervisorInfo.addons_repositories,
requestedAddonRepository,
],
});
await addStoreRepository(this.hass, requestedAddonRepository);
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
@@ -245,6 +244,8 @@ class HassioAddonDashboard extends LitElement {
if (path === "uninstall") {
window.history.back();
} else if (path === "install") {
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
} else {
await this._routeDataChanged();
}
@@ -262,8 +263,7 @@ class HassioAddonDashboard extends LitElement {
return;
}
try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
} catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
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) {
const options: Fuse.IFuseOptions<HassioAddonInfo> = {
export function filterAndSort(addons: StoreAddon[], filter: string) {
const options: Fuse.IFuseOptions<StoreAddon> = {
keys: ["name", "description", "slug"],
isCaseSensitive: false,
minMatchCharLength: 2,

View File

@@ -96,7 +96,7 @@ export class SupervisorBackupContent extends LitElement {
: ["ssl", "share", "media", "addons/local"]
);
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.backupName = this.backup?.name || "";
@@ -168,23 +168,24 @@ export class SupervisorBackupContent extends LitElement {
: ""}
${this.backupType === "partial"
? html`<div class="partial-picker">
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>
${this.backup?.homeassistant
? html`<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${foldersSection?.templates.length
? html`
<ha-formfield

View File

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

View File

@@ -201,26 +201,24 @@ class HassioBackupDialog
}
if (!this._dialogParams?.onboarding) {
this.hass!.callApi(
"POST",
try {
await this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
);
this.closeDialog();
} catch (error: any) {
this._error = error.body.message;
}
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});

View File

@@ -15,15 +15,18 @@ import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button";
import {
fetchHassioAddonsInfo,
HassioAddonInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
import {
addStoreRepository,
fetchStoreRepositories,
removeStoreRepository,
} from "../../../../src/data/supervisor/store";
@customElement("dialog-hassio-repositories")
class HassioRepositoriesDialog extends LitElement {
@@ -58,7 +61,13 @@ class HassioRepositoriesDialog extends LitElement {
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
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))
);
@@ -78,7 +87,7 @@ class HassioRepositoriesDialog extends LitElement {
const repositories = this._filteredRepositories(this._repositories);
const usedRepositories = this._filteredUsedRepositories(
repositories,
this._dialogParams.supervisor.supervisor.addons
this._dialogParams.supervisor.addon.addons
);
return html`
<ha-dialog
@@ -215,9 +224,7 @@ class HassioRepositoriesDialog extends LitElement {
private async _loadData(): Promise<void> {
try {
const addonsinfo = await fetchHassioAddonsInfo(this.hass);
this._repositories = addonsinfo.repositories;
this._repositories = await fetchStoreRepositories(this.hass);
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err: any) {
@@ -231,14 +238,9 @@ class HassioRepositoriesDialog extends LitElement {
return;
}
this._processing = true;
const repositories = this._filteredRepositories(this._repositories!);
const newRepositories = repositories.map((repo) => repo.source);
newRepositories.push(input.value);
try {
await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await addStoreRepository(this.hass, input.value);
await this._loadData();
input.value = "";
@@ -250,19 +252,8 @@ class HassioRepositoriesDialog extends LitElement {
private async _removeRepository(ev: Event) {
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 {
await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await removeStoreRepository(this.hass, slug);
await this._loadData();
} catch (err: any) {
this._error = extractApiErrorMessage(err);

View File

@@ -25,7 +25,7 @@ import {
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { HomeAssistant, Route } from "../../src/types";
import { HomeAssistant, Route, TranslationDict } from "../../src/types";
import { getTranslation } from "../../src/util/common-translation";
declare global {
@@ -124,9 +124,13 @@ export class SupervisorBaseElement extends urlSyncMixin(
this.supervisor = {
...this.supervisor,
localize: await computeLocalize(this.constructor.prototype, language, {
[language]: data,
}),
localize: await computeLocalize<TranslationDict["supervisor"]>(
this.constructor.prototype,
language,
{
[language]: data,
}
),
};
}

View File

@@ -26,7 +26,7 @@ import {
import {
UNHEALTHY_REASON_URL,
UNSUPPORTED_REASON_URL,
} from "../../../src/panels/config/system-health/ha-config-system-health";
} from "../../../src/panels/config/repairs/dialog-system-information";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";

View File

@@ -72,8 +72,8 @@
"@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.7.96",
"@mdi/svg": "6.7.96",
"@mdi/js": "7.0.96",
"@mdi/svg": "7.0.96",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20220601.0"
version = "20220802.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
@@ -23,8 +23,3 @@ include-package-data = true
[tool.setuptools.packages.find]
include = ["hass_frontend*"]
[tool.mypy]
python_version = 3.4
show_error_codes = true
strict = true

View File

@@ -24,10 +24,15 @@ function auto(version) {
return patch(version);
}
function nightly() {
return `${today()}.dev`;
}
const methods = {
patch,
today,
auto,
nightly,
};
async function main(args) {
@@ -57,7 +62,11 @@ async function main(args) {
console.log("Current version:", version);
console.log("New version:", newVersion);
fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
fs.writeFileSync(
"pyproject.toml",
setup.replace(version, newVersion),
"utf-8"
);
if (!commit) {
return;

2
setup.cfg Normal file
View File

@@ -0,0 +1,2 @@
# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
# Keep this file until it does!

View File

@@ -47,7 +47,7 @@ import {
mdiRobotVacuum,
mdiScriptText,
mdiSineWave,
mdiTextToSpeech,
mdiMicrophoneMessage,
mdiThermometer,
mdiThermostat,
mdiTimerOutline,
@@ -74,8 +74,9 @@ export const FIXED_DOMAIN_ICONS = {
camera: mdiVideo,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiTextToSpeech,
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
demo: mdiHomeAssistant,
fan: mdiFan,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,

View File

@@ -76,7 +76,11 @@ class Storage {
public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value;
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) {
// Safari in private mode doesn't allow localstorage
}

View File

@@ -5,8 +5,7 @@ export type LeafletModuleType = typeof import("leaflet");
export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async (
mapElement: HTMLElement,
darkMode?: boolean
mapElement: HTMLElement
): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element");
@@ -23,7 +22,7 @@ export const setupLeafletMap = async (
mapElement.parentNode.appendChild(style);
map.setView([52.3731339, 4.8903147], 13);
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
const tileLayer = createTileLayer(Leaflet).addTo(map);
return [map, Leaflet, tileLayer];
};
@@ -31,23 +30,19 @@ export const setupLeafletMap = async (
export const replaceTileLayer = (
leaflet: LeafletModuleType,
map: Map,
tileLayer: TileLayer,
darkMode: boolean
tileLayer: TileLayer
): TileLayer => {
map.removeLayer(tileLayer);
tileLayer = createTileLayer(leaflet, darkMode);
tileLayer = createTileLayer(leaflet);
tileLayer.addTo(map);
return tileLayer;
};
const createTileLayer = (
leaflet: LeafletModuleType,
darkMode: boolean
): TileLayer =>
const createTileLayer = (leaflet: LeafletModuleType): TileLayer =>
leaflet.tileLayer(
`https://{s}.basemaps.cartocdn.com/${
darkMode ? "dark_all" : "light_all"
}/{z}/{x}/{y}${leaflet.Browser.retina ? "@2x.png" : ".png"}`,
`https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}${
leaflet.Browser.retina ? "@2x.png" : ".png"
}`,
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>',

View File

@@ -8,6 +8,8 @@ import {
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
mdiCloseCircleOutline,
@@ -24,6 +26,15 @@ import {
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiWeatherNight,
@@ -125,7 +136,40 @@ export const domainIconWithoutDefault = (
}
case "media_player":
return compareState === "playing" ? mdiCastConnected : mdiCast;
switch (stateObj?.attributes.device_class) {
case "speaker":
switch (compareState) {
case "playing":
return mdiSpeakerPlay;
case "paused":
return mdiSpeakerPause;
case "off":
return mdiSpeakerOff;
default:
return mdiSpeaker;
}
case "tv":
switch (compareState) {
case "playing":
return mdiTelevisionPlay;
case "paused":
return mdiTelevisionPause;
case "off":
return mdiTelevisionOff;
default:
return mdiTelevision;
}
default:
switch (compareState) {
case "playing":
case "paused":
return mdiCastConnected;
case "off":
return mdiCastOff;
default:
return mdiCast;
}
}
case "switch":
switch (stateObj?.attributes.device_class) {
@@ -153,6 +197,12 @@ export const domainIconWithoutDefault = (
? FIXED_DOMAIN_ICONS[domain]
: mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update":
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)

View File

@@ -0,0 +1,88 @@
import { html } from "lit";
import { getConfigEntries } from "../../data/config_entries";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { isComponentLoaded } from "../config/is_component_loaded";
import { fireEvent } from "../dom/fire_event";
import { navigate } from "../navigate";
export const protocolIntegrationPicked = async (
element: HTMLElement,
hass: HomeAssistant,
slug: string
) => {
if (slug === "zwave_js") {
const entries = await getConfigEntries(hass, {
domain: "zwave_js",
});
if (!entries.length) {
// If the component isn't loaded, ask them to load the integration first
showConfirmationDialog(element, {
text: hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Z-Wave",
supported_hardware_link: html`<a
href=${documentationUrl(hass, "/docs/z-wave/controllers")}
target="_blank"
rel="noreferrer"
>${hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(element, "handler-picked", {
handler: "zwave_js",
});
},
});
return;
}
showZWaveJSAddNodeDialog(element, {
entry_id: entries[0].entry_id,
});
} else if (slug === "zha") {
// If the component isn't loaded, ask them to load the integration first
if (!isComponentLoaded(hass, "zha")) {
showConfirmationDialog(element, {
text: hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Zigbee",
supported_hardware_link: html`<a
href=${documentationUrl(
hass,
"/integrations/zha/#known-working-zigbee-radio-modules"
)}
target="_blank"
rel="noreferrer"
>${hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(element, "handler-picked", {
handler: "zha",
});
},
});
return;
}
navigate("/config/zha/add");
}
};

View File

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

View File

@@ -3,10 +3,39 @@ import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-plur
import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill";
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
import IntlMessageFormat from "intl-messageformat";
import { Resources } from "../../types";
import { Resources, TranslationDict } from "../../types";
import { getLocalLanguage } from "../../util/common-translation";
export type LocalizeFunc = (key: string, ...args: any[]) => string;
// Exclude some patterns from key type checking for now
// These are intended to be removed as errors are fixed
// Fixing component category will require tighter definition of types from backend and/or web socket
type LocalizeKeyExceptions =
| `${string}`
| `panel.${string}`
| `state.${string}`
| `state_attributes.${string}`
| `state_badge.${string}`
| `ui.${string}`
| `${keyof TranslationDict["supervisor"]}.${string}`
| `component.${string}`;
// Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types
type FlattenObjectKeys<
T extends Record<string, any>,
Key extends keyof T = keyof T
> = Key extends string
? T[Key] extends Record<string, unknown>
? `${Key}.${FlattenObjectKeys<T[Key]>}`
: `${Key}`
: never;
export type LocalizeFunc<
Dict extends Record<string, unknown> = TranslationDict
> = (
key: FlattenObjectKeys<Dict> | LocalizeKeyExceptions,
...args: any[]
) => string;
interface FormatType {
[format: string]: any;
}
@@ -65,12 +94,14 @@ export const polyfillsLoaded =
* }
*/
export const computeLocalize = async (
export const computeLocalize = async <
Dict extends Record<string, unknown> = TranslationDict
>(
cache: any,
language: string,
resources: Resources,
formats?: FormatsType
): Promise<LocalizeFunc> => {
): Promise<LocalizeFunc<Dict>> => {
if (polyfillsLoaded) {
await polyfillsLoaded;
}

View File

@@ -11,6 +11,8 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { clamp } from "../../common/number/clamp";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
interface Tooltip extends TooltipModel<any> {
top: string;
left: string;
@@ -186,6 +188,10 @@ export default class HaChartBase extends LitElement {
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";
this.chart = new ChartConstructor(ctx, {
type: this.chartType,
@@ -324,6 +330,9 @@ export default class HaChartBase extends LitElement {
width: 16px;
flex-shrink: 0;
box-sizing: border-box;
margin-inline-end: 6px;
margin-inline-start: initial;
direction: var(--direction);
}
.chartTooltip .bullet {
align-self: baseline;
@@ -332,6 +341,9 @@ export default class HaChartBase extends LitElement {
:host([rtl]) .chartTooltip .bullet {
margin-right: inherit;
margin-left: 6px;
margin-inline-end: inherit;
margin-inline-start: 6px;
direction: var(--direction);
}
.chartTooltip {
padding: 8px;
@@ -368,6 +380,7 @@ export default class HaChartBase extends LitElement {
.chartTooltip .title {
text-align: center;
font-weight: 500;
direction: ltr;
}
.chartTooltip .footer {
font-weight: 500;

View File

@@ -8,7 +8,7 @@ import {
} from "../../common/number/format_number";
import { LineChartEntity, LineChartState } from "../../data/history";
import { HomeAssistant } from "../../types";
import "./ha-chart-base";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
const safeParseFloat = (value) => {
const parsed = parseFloat(value);
@@ -34,6 +34,8 @@ class StateHistoryChartLine extends LitElement {
@state() private _chartOptions?: ChartOptions;
private _chartTime: Date = new Date();
protected render() {
return html`
<ha-chart-base
@@ -121,7 +123,13 @@ class StateHistoryChartLine extends LitElement {
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();
}
}
@@ -135,6 +143,7 @@ class StateHistoryChartLine extends LitElement {
return;
}
this._chartTime = new Date();
const endTime = this.endTime;
const names = this.names || {};
entityStates.forEach((states) => {

View File

@@ -9,7 +9,7 @@ import { numberFormatToLocale } from "../../common/number/format_number";
import { computeRTL } from "../../common/util/compute_rtl";
import { TimelineEntity } from "../../data/history";
import { HomeAssistant } from "../../types";
import "./ha-chart-base";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
import type { TimeLineData } from "./timeline-chart/const";
/** Binary sensor device classes for which the static colors for on/off are NOT inverted.
@@ -103,6 +103,8 @@ export class StateHistoryChartTimeline extends LitElement {
@state() private _chartOptions?: ChartOptions<"timeline">;
private _chartTime: Date = new Date();
protected render() {
return html`
<ha-chart-base
@@ -211,7 +213,13 @@ export class StateHistoryChartTimeline extends LitElement {
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();
}
}
@@ -224,6 +232,7 @@ export class StateHistoryChartTimeline extends LitElement {
stateHistory = [];
}
this._chartTime = new Date();
const startTime = this.startTime;
const endTime = this.endTime;
const labels: string[] = [];

View File

@@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement {
line-height: 60px;
color: var(--secondary-text-color);
}
.container {
max-height: var(--history-max-height);
}

View File

@@ -12,8 +12,10 @@ import { property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import { computeActiveState } from "../../common/entity/compute_active_state";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { iconColorCSS } from "../../common/style/icon_color_css";
import { cameraUrlWithWidthHeight } from "../../data/camera";
import type { HomeAssistant } from "../../types";
import "../ha-state-icon";
@@ -93,6 +95,9 @@ export class StateBadge extends LitElement {
if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl);
}
if (computeDomain(stateObj.entity_id) === "camera") {
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
}
hostStyle.backgroundImage = `url(${imageUrl})`;
this._showIcon = false;
} 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 { fireEvent } from "../common/dom/fire_event";
import { stringCompare } from "../common/string/compare";
import { HassioAddonInfo } from "../data/hassio/addon";
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types";
@@ -78,27 +77,27 @@ class HaAddonPicker extends LitElement {
private async _getAddons() {
try {
if (isComponentLoaded(this.hass, "hassio")) {
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
this._addons = supervisorInfo.addons.sort((a, b) =>
stringCompare(a.name, b.name)
);
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
this._addons = addonsInfo.addons
.filter((addon) => addon.version)
.sort((a, b) => stringCompare(a.name, b.name));
} else {
showAlertDialog(this, {
title: this.hass.localize(
"ui.componencts.addon-picker.error.no_supervisor.title"
"ui.components.addon-picker.error.no_supervisor.title"
),
text: this.hass.localize(
"ui.componencts.addon-picker.error.no_supervisor.description"
"ui.components.addon-picker.error.no_supervisor.description"
),
});
}
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.componencts.addon-picker.error.fetch_addons.title"
"ui.components.addon-picker.error.fetch_addons.title"
),
text: this.hass.localize(
"ui.componencts.addon-picker.error.fetch_addons.description"
"ui.components.addon-picker.error.fetch_addons.description"
),
});
}

View File

@@ -76,6 +76,7 @@ class HaAttributes extends LitElement {
css`
.attribute-container {
margin-bottom: 8px;
direction: ltr;
}
.data-entry {
display: flex;

View File

@@ -1,5 +1,4 @@
import "@material/mwc-list/mwc-list-item";
import "./ha-select";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -8,6 +7,7 @@ import { stopPropagation } from "../common/dom/stop_propagation";
import { stringCompare } from "../common/string/compare";
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
import { HomeAssistant } from "../types";
import "./ha-select";
@customElement("ha-blueprint-picker")
class HaBluePrintPicker extends LitElement {
@@ -51,7 +51,7 @@ class HaBluePrintPicker extends LitElement {
return html`
<ha-select
.label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.label")}
this.hass.localize("ui.components.blueprint-picker.select_blueprint")}
fixedMenuPosition
naturalMenuWidth
.value=${this.value}
@@ -59,11 +59,6 @@ class HaBluePrintPicker extends LitElement {
@selected=${this._blueprintChanged}
@closed=${stopPropagation}
>
<mwc-list-item value="">
${this.hass.localize(
"ui.components.blueprint-picker.select_blueprint"
)}
</mwc-list-item>
${this._processedBlueprints(this.blueprints).map(
(blueprint) => html`
<mwc-list-item .value=${blueprint.path}>

View File

@@ -67,8 +67,7 @@ export class HaChip extends LitElement {
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
}
.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
.mdc-chip.no-text
.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-inline-start: -4px;
margin-inline-end: 4px;

View File

@@ -1,17 +1,13 @@
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
import { styles } from "@material/mwc-list/mwc-list-item.css";
import { css, CSSResult, html } from "lit";
import { css, CSSResultGroup, html } from "lit";
import { customElement, property, query } from "lit/decorators";
import { HaListItem } from "./ha-list-item";
@customElement("ha-clickable-list-item")
export class HaClickableListItem extends ListItemBase {
export class HaClickableListItem extends HaListItem {
@property() public href?: string;
@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;
@query("a") private _anchor!: HTMLAnchorElement;
@@ -39,18 +35,10 @@ export class HaClickableListItem extends ListItemBase {
});
}
static get styles(): CSSResult[] {
static get styles(): CSSResultGroup {
return [
styles,
super.styles,
css`
:host {
padding-left: 0px;
padding-right: 0px;
}
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
a {
width: 100%;
height: 100%;
@@ -60,19 +48,6 @@ export class HaClickableListItem extends ListItemBase {
padding-right: var(--mdc-list-side-padding, 20px);
overflow: hidden;
}
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);
}
`,
];
}

View File

@@ -2,6 +2,7 @@ import type {
Completion,
CompletionContext,
CompletionResult,
CompletionSource,
} from "@codemirror/autocomplete";
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
import { HassEntities } from "home-assistant-js-websocket";
@@ -11,6 +12,7 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { loadCodeMirror } from "../resources/codemirror.ondemand";
import { HomeAssistant } from "../types";
import "./ha-icon";
declare global {
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")
export class HaCodeEditor extends ReactiveElement {
public codemirror?: EditorView;
@@ -41,12 +49,17 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean, attribute: "autocomplete-entities" })
public autocompleteEntities = false;
@property({ type: Boolean, attribute: "autocomplete-icons" })
public autocompleteIcons = false;
@property() public error = false;
@state() private _value = "";
private _loadedCodeMirror?: typeof import("../resources/codemirror");
private _iconList?: Completion[];
public set value(value: string) {
this._value = value;
}
@@ -151,13 +164,22 @@ export class HaCodeEditor extends ReactiveElement {
),
];
if (!this.readOnly && this.autocompleteEntities && this.hass) {
extensions.push(
this._loadedCodeMirror.autocompletion({
override: [this._entityCompletions.bind(this)],
maxRenderedOptions: 10,
})
);
if (!this.readOnly) {
const completionSources: CompletionSource[] = [];
if (this.autocompleteEntities && this.hass) {
completionSources.push(this._entityCompletions.bind(this));
}
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({
@@ -187,7 +209,7 @@ export class HaCodeEditor extends ReactiveElement {
private _entityCompletions(
context: CompletionContext
): CompletionResult | null | Promise<CompletionResult | null> {
const entityWord = context.matchBefore(/[a-z_]{3,}\./);
const entityWord = context.matchBefore(/[a-z_]{3,}\.\w*/);
if (
!entityWord ||
@@ -205,7 +227,48 @@ export class HaCodeEditor extends ReactiveElement {
return {
from: Number(entityWord.from),
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

@@ -11,7 +11,7 @@ export const createCloseHeading = (
hass: HomeAssistant,
title: string | TemplateResult
) => html`
<span class="header_title">${title}</span>
<div class="header_title">${title}</div>
<ha-icon-button
.label=${hass.localize("ui.dialogs.generic.close")}
.path=${mdiClose}
@@ -40,10 +40,13 @@ export class HaDialog extends DialogBase {
z-index: var(--dialog-z-index, 7);
-webkit-backdrop-filter: var(--dialog-backdrop-filter, none);
backdrop-filter: var(--dialog-backdrop-filter, none);
--mdc-dialog-box-shadow: var(--dialog-box-shadow, none);
--mdc-typography-headline6-font-weight: 400;
--mdc-typography-headline6-font-size: 1.574rem;
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
padding-bottom: max(env(safe-area-inset-bottom), 8px);
padding-bottom: max(env(safe-area-inset-bottom), 24px);
}
.mdc-dialog__actions span:nth-child(1) {
flex: var(--secondary-action-button-flex, unset);
@@ -54,17 +57,23 @@ export class HaDialog extends DialogBase {
.mdc-dialog__container {
align-items: var(--vertial-align-dialog, center);
}
.mdc-dialog__title {
padding: 24px 24px 0 24px;
}
.mdc-dialog__actions {
padding: 0 24px 24px 24px;
}
.mdc-dialog__title::before {
display: block;
height: 20px;
height: 0px;
}
.mdc-dialog .mdc-dialog__content {
position: var(--dialog-content-position, relative);
padding: var(--dialog-content-padding, 20px 24px);
padding: var(--dialog-content-padding, 24px);
}
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
padding-bottom: max(
var(--dialog-content-padding, 20px),
var(--dialog-content-padding, 24px),
env(safe-area-inset-bottom)
);
}
@@ -72,10 +81,7 @@ export class HaDialog extends DialogBase {
position: var(--dialog-surface-position, relative);
top: var(--dialog-surface-top);
min-height: var(--mdc-dialog-min-height, auto);
border-radius: var(
--ha-dialog-border-radius,
var(--ha-card-border-radius, 4px)
);
border-radius: var(--ha-dialog-border-radius, 28px);
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;
@@ -89,8 +95,9 @@ export class HaDialog extends DialogBase {
color: inherit;
}
.header_title {
margin-right: 40px;
margin-inline-end: 40px;
margin-right: 32px;
margin-inline-end: 32px;
margin-inline-start: initial;
direction: var(--direction);
}
.header_button {

View File

@@ -19,6 +19,14 @@ export class HaFab extends FabBase {
direction: var(--direction);
}
`,
// safari workaround - must be explicit
document.dir === "rtl"
? css`
:host .mdc-fab--extended .mdc-fab__icon {
direction: rtl;
}
`
: css``,
];
}

View File

@@ -6,7 +6,10 @@ export const computeInitialHaFormData = (
): Record<string, any> => {
const data = {};
schema.forEach((field) => {
if (field.description?.suggested_value) {
if (
field.description?.suggested_value !== undefined &&
field.description?.suggested_value !== null
) {
data[field.name] = field.description.suggested_value;
} else if ("default" in field) {
data[field.name] = field.default;

View File

@@ -3,15 +3,15 @@ import {
CSSResultGroup,
html,
LitElement,
TemplateResult,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { HaCheckbox } from "../ha-checkbox";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
import "../ha-slider";
import { HaTextField } from "../ha-textfield";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
@customElement("ha-form-integer")
export class HaFormInteger extends LitElement implements HaFormElement {
@@ -105,7 +105,8 @@ export class HaFormInteger extends LitElement implements HaFormElement {
}
return (
this.schema.description?.suggested_value ||
(this.schema.description?.suggested_value !== undefined &&
this.schema.description?.suggested_value !== null) ||
this.schema.default ||
this.schema.valueMin ||
0

View File

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

View File

@@ -14,6 +14,7 @@ const getAngle = (value: number, min: number, max: number) => {
export interface LevelDefinition {
level: number;
stroke: string;
label?: string;
}
@customElement("ha-gauge")
@@ -38,22 +39,31 @@ export class Gauge extends LitElement {
@state() private _updated = false;
@state() private _segment_label? = "";
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
// Wait for the first render for the initial animation to work
afterNextRender(() => {
this._updated = true;
this._angle = getAngle(this.value, this.min, this.max);
this._segment_label = this.getSegmentLabel();
this._rescale_svg();
});
}
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (!this._updated || !changedProperties.has("value")) {
if (
!this._updated ||
(!changedProperties.has("value") &&
!changedProperties.has("label") &&
!changedProperties.has("_segment_label"))
) {
return;
}
this._angle = getAngle(this.value, this.min, this.max);
this._segment_label = this.getSegmentLabel();
this._rescale_svg();
}
@@ -118,9 +128,11 @@ export class Gauge extends LitElement {
</svg>
<svg class="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>
</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() {
return css`
:host {

View File

@@ -29,7 +29,102 @@ interface DeprecatedIcon {
};
}
const mdiDeprecatedIcons: DeprecatedIcon = {};
const mdiDeprecatedIcons: DeprecatedIcon = {
"android-messages": {
newName: "message-text",
removeIn: "2022.10",
},
"book-variant-multiple": {
newName: "bookmark-box-multiple",
removeIn: "2022.10",
},
"desktop-mac": {
newName: "monitor",
removeIn: "2022.10",
},
"desktop-mac-dashboard": {
newName: "monitor-dashboard",
removeIn: "2022.10",
},
discord: {
removeIn: "2022.10",
},
"diving-scuba": {
newName: "diving-scuba-mask",
removeIn: "2022.10",
},
"email-send": {
newName: "email-arrow-right",
removeIn: "2022.10",
},
"email-send-outline": {
newName: "email-arrow-right-outline",
removeIn: "2022.10",
},
"email-receive": {
newName: "email-arrow-left",
removeIn: "2022.10",
},
"email-receive-outline": {
newName: "email-arrow-left-outline",
removeIn: "2022.10",
},
"format-textdirection-r-to-l": {
newName: "format-pilcrow-arrow-left",
removeIn: "2022.10",
},
"format-textdirection-l-to-r": {
newName: "format-pilcrow-arrow-right",
removeIn: "2022.10",
},
"google-controller": {
newName: "controller",
removeIn: "2022.10",
},
"google-controller-off": {
newName: "controller-off",
removeIn: "2022.10",
},
"google-home": {
removeIn: "2022.10",
},
lecturn: {
newName: "lectern",
removeIn: "2022.10",
},
receipt: {
newName: "receipt-text",
removeIn: "2022.10",
},
"receipt-outline": {
newName: "receipt-text-outline",
removeIn: "2022.10",
},
"tablet-android": {
newName: "tablet",
removeIn: "2022.10",
},
"text-to-speech": {
newName: "microphone-message",
removeIn: "2022.10",
},
"text-to-speech-off": {
newName: "microphone-message-off",
removeIn: "2022.10",
},
"timeline-help": {
newName: "timeline-question",
removeIn: "2022.10",
},
"timeline-help-outline": {
newName: "timeline-question-outline",
removeIn: "2022.10",
},
"vector-point": {
newName: "vector-point-select",
removeIn: "2022.10",
},
};
const chunks: Chunks = {};

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

@@ -52,6 +52,11 @@ export class HaSelect extends SelectBase {
inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label {
inset-inline-start: 48px;
inset-inline-end: initial;
direction: var(--direction);
}
.mdc-select .mdc-select__anchor {
padding-inline-start: 12px;
padding-inline-end: 0px;

View File

@@ -1,8 +1,9 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { DeviceRegistryEntry } from "../../data/device_registry";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@@ -11,7 +12,11 @@ import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { AreaSelector } from "../../data/selector";
import type { AreaSelector } from "../../data/selector";
import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../ha-area-picker";
@@ -29,13 +34,15 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
@@ -45,7 +52,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
];
}
protected updated(changedProperties) {
protected updated(changedProperties: PropertyValues): void {
if (
changedProperties.has("selector") &&
(this.selector.area.device?.integration ||
@@ -58,7 +65,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
}
}
protected render() {
protected render(): TemplateResult {
if (
(this.selector.area.device?.integration ||
this.selector.area.entity?.integration) &&
@@ -77,12 +84,6 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
no-add
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
.disabled=${this.disabled}
.required=${this.required}
></ha-area-picker>
@@ -98,27 +99,22 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
no-add
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
.disabled=${this.disabled}
.required=${this.required}
></ha-areas-picker>
`;
}
private _filterEntities = (entity: EntityRegistryEntry): boolean => {
const filterIntegration = this.selector.area.entity?.integration;
if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
private _filterEntities = (entity: HassEntity): boolean => {
if (!this.selector.area.entity) {
return true;
}
return true;
return filterSelectorEntities(
this.selector.area.entity,
entity,
this._entitySources
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
@@ -126,47 +122,17 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true;
}
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.area.device;
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
return filterSelectorDevices(
this.selector.area.device,
device,
deviceIntegrations
);
};
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
}
declare global {

View File

@@ -2,8 +2,8 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ConfigEntry } from "../../data/config_entries";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@@ -13,6 +13,7 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { DeviceSelector } from "../../data/selector";
import { filterSelectorDevices } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker";
@@ -34,12 +35,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@state() public _configEntries?: ConfigEntry[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
@@ -107,48 +108,17 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
}
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.device;
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
return filterSelectorDevices(
this.selector.device,
device,
deviceIntegrations
);
};
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
}
declare global {

View File

@@ -1,12 +1,12 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { EntitySelector } from "../../data/selector";
import type { EntitySelector } from "../../data/selector";
import { filterSelectorEntities } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../entity/ha-entities-picker";
import "../entity/ha-entity-picker";
@@ -73,37 +73,8 @@ export class HaEntitySelector extends LitElement {
}
}
private _filterEntities = (entity: HassEntity): boolean => {
const {
domain: filterDomain,
device_class: filterDeviceClass,
integration: filterIntegration,
} = this.selector.entity;
if (filterDomain) {
const entityDomain = computeStateDomain(entity);
if (
Array.isArray(filterDomain)
? !filterDomain.includes(entityDomain)
: entityDomain !== filterDomain
) {
return false;
}
}
if (
filterDeviceClass &&
entity.attributes.device_class !== filterDeviceClass
) {
return false;
}
if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
}
return true;
};
private _filterEntities = (entity: HassEntity): boolean =>
filterSelectorEntities(this.selector.entity, entity, this._entitySources);
}
declare global {

View File

@@ -4,9 +4,9 @@ import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import { NumberSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../ha-input-helper-text";
import "../ha-slider";
import "../ha-textfield";
import "../ha-input-helper-text";
@customElement("ha-selector-number")
export class HaNumberSelector extends LitElement {
@@ -30,21 +30,25 @@ export class HaNumberSelector extends LitElement {
const isBox = this.selector.number.mode === "box";
return html`
${this.label ? html`${this.label}${this.required ? " *" : ""}` : ""}
<div class="input">
${!isBox
? html`<ha-slider
.min=${this.selector.number.min}
.max=${this.selector.number.max}
.value=${this._value}
.step=${this.selector.number.step ?? 1}
.disabled=${this.disabled}
.required=${this.required}
pin
ignore-bar-touch
@change=${this._handleSliderChange}
>
</ha-slider>`
? html`
${this.label
? html`${this.label}${this.required ? " *" : ""}`
: ""}
<ha-slider
.min=${this.selector.number.min}
.max=${this.selector.number.max}
.value=${this._value}
.step=${this.selector.number.step ?? 1}
.disabled=${this.disabled}
.required=${this.required}
pin
ignore-bar-touch
@change=${this._handleSliderChange}
>
</ha-slider>
`
: ""}
<ha-textfield
inputMode="numeric"

View File

@@ -3,17 +3,33 @@ import {
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import { DeviceRegistryEntry } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { TargetSelector } from "../../data/selector";
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import {
DeviceRegistryEntry,
getDeviceIntegrationLookup,
} from "../../data/device_registry";
import type { EntityRegistryEntry } from "../../data/entity_registry";
import { subscribeEntityRegistry } from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import {
filterSelectorDevices,
filterSelectorEntities,
TargetSelector,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import type { HomeAssistant } from "../../types";
import "../ha-target-picker";
@customElement("ha-selector-target")
@@ -28,119 +44,82 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@state() private _entityPlaformLookup?: Record<string, string>;
@state() private _configEntries?: ConfigEntry[];
@property({ type: Boolean }) public disabled = false;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
const entityLookup = {};
for (const confEnt of entities) {
if (!confEnt.platform) {
continue;
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
this._entityPlaformLookup = entityLookup;
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
protected updated(changedProperties) {
if (changedProperties.has("selector")) {
const oldSelector = changedProperties.get("selector");
if (
oldSelector !== this.selector &&
(this.selector.target.device?.integration ||
this.selector.target.entity?.integration)
) {
this._loadConfigEntries();
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (
changedProperties.has("selector") &&
this.selector.target.device?.integration &&
!this._entitySources
) {
fetchEntitySourcesWithCache(this.hass).then((sources) => {
this._entitySources = sources;
});
}
}
protected render() {
protected render(): TemplateResult {
if (
(this.selector.target.device?.integration ||
this.selector.target.entity?.integration) &&
!this._entitySources
) {
return html``;
}
return html`<ha-target-picker
.hass=${this.hass}
.value=${this.value}
.helper=${this.helper}
.deviceFilter=${this._filterDevices}
.entityRegFilter=${this._filterRegEntities}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.target.entity?.device_class
? [this.selector.target.entity.device_class]
: undefined}
.includeDomains=${this.selector.target.entity?.domain
? [this.selector.target.entity.domain]
: undefined}
.disabled=${this.disabled}
></ha-target-picker>`;
}
private _filterEntities = (entity: HassEntity): boolean => {
if (
this.selector.target.entity?.integration ||
this.selector.target.device?.integration
) {
if (
!this._entityPlaformLookup ||
this._entityPlaformLookup[entity.entity_id] !==
(this.selector.target.entity?.integration ||
this.selector.target.device?.integration)
) {
return false;
}
if (!this.selector.target.entity) {
return true;
}
return true;
};
private _filterRegEntities = (entity: EntityRegistryEntry): boolean => {
if (this.selector.target.entity?.integration) {
if (entity.platform !== this.selector.target.entity.integration) {
return false;
}
}
return true;
return filterSelectorEntities(
this.selector.target.entity,
entity,
this._entitySources
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
if (
this.selector.target.device?.manufacturer &&
device.manufacturer !== this.selector.target.device.manufacturer
) {
return false;
if (!this.selector.target.device) {
return true;
}
if (
this.selector.target.device?.model &&
device.model !== this.selector.target.device.model
) {
return false;
}
if (
this.selector.target.device?.integration ||
this.selector.target.entity?.integration
) {
if (
!this._configEntries?.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {
return false;
}
}
return true;
};
private async _loadConfigEntries() {
this._configEntries = (await getConfigEntries(this.hass)).filter(
(entry) =>
entry.domain === this.selector.target.device?.integration ||
entry.domain === this.selector.target.entity?.integration
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
return filterSelectorDevices(
this.selector.target.device,
device,
deviceIntegrations
);
}
};
static get styles(): CSSResultGroup {
return css`

View File

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

View File

@@ -21,6 +21,7 @@ import "@polymer/paper-item/paper-icon-item";
import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResult,
@@ -44,7 +45,9 @@ import {
PersistentNotification,
subscribeNotifications,
} from "../data/persistent_notification";
import { subscribeRepairsIssueRegistry } from "../data/repairs";
import { updateCanInstall, UpdateEntity } from "../data/update";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant, PanelInfo, Route } from "../types";
@@ -177,7 +180,7 @@ const computePanels = memoizeOne(
let Sortable;
@customElement("ha-sidebar")
class HaSidebar extends LitElement {
class HaSidebar extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
@@ -192,6 +195,8 @@ class HaSidebar extends LitElement {
@state() private _updatesCount = 0;
@state() private _issuesCount = 0;
@state() private _renderEmptySortable = false;
private _mouseLeaveTimeout?: number;
@@ -214,6 +219,16 @@ class HaSidebar extends LitElement {
private _sortable?;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeRepairsIssueRegistry(this.hass.connection!, (repairs) => {
this._issuesCount = repairs.issues.filter(
(issue) => !issue.ignored
).length;
}),
];
}
protected render() {
if (!this.hass) {
return html``;
@@ -238,6 +253,7 @@ class HaSidebar extends LitElement {
changedProps.has("alwaysExpand") ||
changedProps.has("_externalConfig") ||
changedProps.has("_updatesCount") ||
changedProps.has("_issuesCount") ||
changedProps.has("_notifications") ||
changedProps.has("editMode") ||
changedProps.has("_renderEmptySortable") ||
@@ -500,7 +516,7 @@ class HaSidebar extends LitElement {
}
private _renderConfiguration(title: string | null) {
return html` <a
return html`<a
class="configuration-container"
role="option"
href="/config"
@@ -511,17 +527,20 @@ class HaSidebar extends LitElement {
>
<paper-icon-item class="configuration" role="option">
<ha-svg-icon slot="item-icon" .path=${mdiCog}></ha-svg-icon>
${!this.alwaysExpand && this._updatesCount > 0
${!this.alwaysExpand &&
(this._updatesCount > 0 || this._issuesCount > 0)
? html`
<span class="configuration-badge" slot="item-icon">
${this._updatesCount}
${this._updatesCount + this._issuesCount}
</span>
`
: ""}
<span class="item-text">${title}</span>
${this.alwaysExpand && this._updatesCount > 0
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
? html`
<span class="configuration-badge">${this._updatesCount}</span>
<span class="configuration-badge"
>${this._updatesCount + this._issuesCount}</span
>
`
: ""}
</paper-icon-item>

View File

@@ -42,8 +42,8 @@ import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-picker";
import "./ha-icon-button";
import "./ha-svg-icon";
import "./ha-input-helper-text";
import "./ha-svg-icon";
@customElement("ha-target-picker")
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 }) public horizontal = false;
@state() private _areas?: { [areaId: string]: AreaRegistryEntry };
@state() private _devices?: {
@@ -117,7 +119,26 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
if (!this._areas || !this._devices || !this._entities) {
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
? ensureArray(this.value.area_id).map((area_id) => {
const area = this._areas![area_id];
@@ -154,7 +175,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
})
: ""}
</div>
${this._renderPicker()}
`;
}
private _renderChips() {
return html`
<div class="mdc-chip-set">
<div
class="mdc-chip area_id add"
@@ -217,10 +242,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
</span>
</div>
</div>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""} `;
: ""}
`;
}
private async _showPicker(ev) {
@@ -289,7 +314,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
class="mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${this.hass.localize("ui.components.target-picker.expand")}
.label=${this.hass.localize("ui.components.target-picker.remove")}
.path=${mdiClose}
hideTooltip
.id=${id}
@@ -309,48 +334,54 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
private _renderPicker() {
switch (this._addMode) {
case "area_id":
return html`<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-area-picker>`;
return html`
<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-area-picker>
`;
case "device_id":
return html`<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-device-picker>`;
return html`
<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-device-picker>
`;
case "entity_id":
return html`<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
allow-custom-entity
></ha-entity-picker>`;
return html`
<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
allow-custom-entity
></ha-entity-picker>
`;
}
return html``;
}
@@ -539,6 +570,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
.horizontal-container {
display: flex;
flex-wrap: wrap;
min-height: 56px;
align-items: center;
}
.mdc-chip {
color: var(--primary-text-color);
}

View File

@@ -61,6 +61,11 @@ export class HaTextField extends TextFieldBase {
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__affix--suffix {
@@ -71,8 +76,14 @@ export class HaTextField extends TextFieldBase {
color: var(--secondary-text-color);
}
.mdc-text-field__icon--leading {
margin-inline-start: 16px;
margin-inline-end: 8px;
direction: var(--direction);
}
input {
text-align: var(--text-field-text-align);
text-align: var(--text-field-text-align, start);
}
/* Chrome, Safari, Edge, Opera */
@@ -110,7 +121,25 @@ export class HaTextField extends TextFieldBase {
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

@@ -23,7 +23,7 @@ export class HaThemePicker extends LitElement {
return html`
<ha-select
.label=${this.label ||
this.hass!.localize("ui.components.theme_picker.theme")}
this.hass!.localize("ui.components.theme-picker.theme")}
.value=${this.value}
.required=${this.required}
.disabled=${this.disabled}
@@ -34,7 +34,7 @@ export class HaThemePicker extends LitElement {
>
<mwc-list-item value="remove"
>${this.hass!.localize(
"ui.components.theme_picker.no_theme"
"ui.components.theme-picker.no_theme"
)}</mwc-list-item
>
${Object.keys(this.hass!.themes.themes)

View File

@@ -41,7 +41,7 @@ export class HaYamlEditor extends LitElement {
try {
this._yaml =
value && !isEmpty(value)
? dump(value, { schema: this.yamlSchema })
? dump(value, { schema: this.yamlSchema, quotingType: '"' })
: "";
} catch (err: any) {
// eslint-disable-next-line no-console
@@ -70,6 +70,7 @@ export class HaYamlEditor extends LitElement {
.readOnly=${this.readOnly}
mode="yaml"
autocomplete-entities
autocomplete-icons
.error=${this.isValid === false}
@value-changed=${this._onChange}
dir="ltr"

View File

@@ -6,21 +6,19 @@ import {
Map,
Marker,
Polyline,
TileLayer,
} from "leaflet";
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
LeafletModuleType,
replaceTileLayer,
setupLeafletMap,
} from "../../common/dom/setup-leaflet-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import "./ha-entity-marker";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import "./ha-entity-marker";
const getEntityId = (entity: string | HaMapEntity): string =>
typeof entity === "string" ? entity : entity.entity_id;
@@ -60,8 +58,6 @@ export class HaMap extends ReactiveElement {
private Leaflet?: LeafletModuleType;
private _tileLayer?: TileLayer;
private _resizeObserver?: ResizeObserver;
private _mapItems: Array<Marker | Circle> = [];
@@ -142,12 +138,6 @@ export class HaMap extends ReactiveElement {
return;
}
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
this._tileLayer = replaceTileLayer(
this.Leaflet!,
this.leafletMap!,
this._tileLayer!,
darkMode
);
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
}
@@ -159,10 +149,7 @@ export class HaMap extends ReactiveElement {
this.shadowRoot!.append(map);
}
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
[this.leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
map,
darkMode
);
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
this._loaded = true;
}
@@ -466,6 +453,7 @@ export class HaMap extends ReactiveElement {
}
#map.dark {
background: #090909;
--map-filter: invert(0.9) hue-rotate(170deg) grayscale(0.7);
}
.light {
color: #000000;
@@ -473,6 +461,13 @@ export class HaMap extends ReactiveElement {
.dark {
color: #ffffff;
}
.leaflet-tile-pane {
filter: var(--map-filter);
}
.dark .leaflet-bar a {
background-color: var(--card-background-color, #1c1c1c);
color: #ffffff;
}
.leaflet-marker-draggable {
cursor: move !important;
}

View File

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

View File

@@ -116,9 +116,6 @@ export class HaMediaPlayerBrowse extends LitElement {
private _resizeObserver?: ResizeObserver;
// @ts-ignore
private _intersectionObserver?: IntersectionObserver;
public connectedCallback(): void {
super.connectedCallback();
this.updateComplete.then(() => this._attachResizeObserver());
@@ -128,9 +125,6 @@ export class HaMediaPlayerBrowse extends LitElement {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
if (this._intersectionObserver) {
this._intersectionObserver.disconnect();
}
}
public async refresh() {
@@ -485,7 +479,10 @@ export class HaMediaPlayerBrowse extends LitElement {
.layout=${grid({
itemSize: {
width: "175px",
height: "225px",
height:
childrenMediaClass.thumbnail_ratio === "portrait"
? "312px"
: "225px",
},
gap: "16px",
flex: { preserve: "aspect-ratio" },

View File

@@ -8,6 +8,7 @@ import { fetchUsers, User } from "../../data/user";
import { HomeAssistant } from "../../types";
import "../ha-select";
import "./ha-user-badge";
import "../ha-list-item";
class HaUserPicker extends LitElement {
public hass?: HomeAssistant;
@@ -48,14 +49,14 @@ class HaUserPicker extends LitElement {
: ""}
${this._sortedUsers(this.users).map(
(user) => html`
<mwc-list-item graphic="avatar" .value=${user.id}>
<ha-list-item graphic="avatar" .value=${user.id}>
<ha-user-badge
.hass=${this.hass}
.user=${user}
slot="graphic"
></ha-user-badge>
${user.name}
</mwc-list-item>
</ha-list-item>
`
)}
</ha-select>

View File

@@ -1,7 +1,11 @@
import { HomeAssistant } from "../types";
export interface ApplicationCredentialsDomainConfig {
description_placeholders: string;
}
export interface ApplicationCredentialsConfig {
domains: string[];
integrations: Record<string, ApplicationCredentialsDomainConfig>;
}
export interface ApplicationCredential {

View File

@@ -3,6 +3,8 @@ import { Store } from "home-assistant-js-websocket/dist/store";
import { stringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
import { DeviceRegistryEntry } from "./device_registry";
import { EntityRegistryEntry } from "./entity_registry";
export interface AreaRegistryEntry {
area_id: string;
@@ -10,6 +12,14 @@ export interface AreaRegistryEntry {
picture: string | null;
}
export interface AreaEntityLookup {
[areaId: string]: EntityRegistryEntry[];
}
export interface AreaDeviceLookup {
[areaId: string]: DeviceRegistryEntry[];
}
export interface AreaRegistryEntryMutableParams {
name: string;
picture?: string | null;
@@ -79,3 +89,35 @@ export const subscribeAreaRegistry = (
conn,
onChange
);
export const getAreaEntityLookup = (
entities: EntityRegistryEntry[]
): AreaEntityLookup => {
const areaEntityLookup: AreaEntityLookup = {};
for (const entity of entities) {
if (!entity.area_id) {
continue;
}
if (!(entity.area_id in areaEntityLookup)) {
areaEntityLookup[entity.area_id] = [];
}
areaEntityLookup[entity.area_id].push(entity);
}
return areaEntityLookup;
};
export const getAreaDeviceLookup = (
devices: DeviceRegistryEntry[]
): AreaDeviceLookup => {
const areaDeviceLookup: AreaDeviceLookup = {};
for (const device of devices) {
if (!device.area_id) {
continue;
}
if (!(device.area_id in areaDeviceLookup)) {
areaDeviceLookup[device.area_id] = [];
}
areaDeviceLookup[device.area_id].push(device);
}
return areaDeviceLookup;
};

View File

@@ -158,8 +158,14 @@ export const getRecentWithCache = (
}
const stateHistory = computeHistory(hass, fetchedHistory, localize);
if (appendingToCache) {
mergeLine(stateHistory.line, cache.data.line);
mergeTimeline(stateHistory.timeline, cache.data.timeline);
if (stateHistory.line.length) {
mergeLine(stateHistory.line, cache.data.line);
}
if (stateHistory.timeline.length) {
mergeTimeline(stateHistory.timeline, cache.data.timeline);
// Replace the timeline array to force an update
cache.data.timeline = [...cache.data.timeline];
}
pruneStartTime(startTime, cache.data);
} else {
cache.data = stateHistory;
@@ -191,6 +197,8 @@ const mergeLine = (
oldLine.data.push(entity);
}
});
// Replace the cached line data to force an update
oldLine.data = [...oldLine.data];
} else {
cacheLines.push(line);
}

View File

@@ -41,6 +41,12 @@ export interface WebRtcAnswer {
answer: string;
}
export const cameraUrlWithWidthHeight = (
base_url: string,
width: number,
height: number
) => `${base_url}&width=${width}&height=${height}`;
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
`/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`;
@@ -57,7 +63,7 @@ export const fetchThumbnailUrlWithCache = async (
hass,
entityId
);
return `${base_url}&width=${width}&height=${height}`;
return cameraUrlWithWidthHeight(base_url, width, height);
};
export const fetchThumbnailUrl = async (

View File

@@ -11,7 +11,8 @@ export interface ConfigEntry {
| "migration_error"
| "setup_retry"
| "not_loaded"
| "failed_unload";
| "failed_unload"
| "setup_in_progress";
supports_options: boolean;
supports_remove_device: boolean;
supports_unload: boolean;
@@ -28,29 +29,38 @@ export type ConfigEntryMutableParams = Partial<
>
>;
// https://github.com/home-assistant/core/blob/2286dea636fda001f03433ba14d7adbda43979e5/homeassistant/config_entries.py#L81
export const ERROR_STATES: ConfigEntry["state"][] = [
"migration_error",
"setup_error",
"setup_retry",
];
// https://github.com/home-assistant/core/blob/2286dea636fda001f03433ba14d7adbda43979e5/homeassistant/config_entries.py#L81
export const RECOVERABLE_STATES: ConfigEntry["state"][] = [
"not_loaded",
"loaded",
"setup_error",
"setup_retry",
];
export const getConfigEntries = (
hass: HomeAssistant,
filters?: { type?: "helper" | "integration"; domain?: string }
): Promise<ConfigEntry[]> => {
const params = new URLSearchParams();
const params: any = {};
if (filters) {
if (filters.type) {
params.append("type", filters.type);
params.type_filter = filters.type;
}
if (filters.domain) {
params.append("domain", filters.domain);
params.domain = filters.domain;
}
}
return hass.callApi<ConfigEntry[]>(
"GET",
`config/config_entries/entry?${params.toString()}`
);
return hass.callWS<ConfigEntry[]>({
type: "config_entries/get",
...params,
});
};
export const updateConfigEntry = (

View File

@@ -6,16 +6,17 @@ import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
import { domainToName } from "./integration";
export const DISCOVERY_SOURCES = [
"usb",
"unignore",
"bluetooth",
"dhcp",
"homekit",
"ssdp",
"zeroconf",
"discovery",
"hassio",
"homekit",
"integration_discovery",
"mqtt",
"hassio",
"ssdp",
"unignore",
"usb",
"zeroconf",
];
export const ATTENTION_SOURCES = ["reauth"];

View File

@@ -1,10 +1,11 @@
import { Connection, createCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import type { Store } from "home-assistant-js-websocket/dist/store";
import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
import { EntityRegistryEntry } from "./entity_registry";
import type { HomeAssistant } from "../types";
import type { EntityRegistryEntry } from "./entity_registry";
import type { EntitySources } from "./entity_sources";
export interface DeviceRegistryEntry {
id: string;
@@ -20,7 +21,7 @@ export interface DeviceRegistryEntry {
area_id: string | null;
name_by_user: string | null;
entry_type: "service" | null;
disabled_by: string | null;
disabled_by: "user" | "integration" | "config_entry" | null;
configuration_url: string | null;
}
@@ -126,3 +127,39 @@ export const sortDeviceRegistryByName = (entries: DeviceRegistryEntry[]) =>
entries.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "")
);
export const getDeviceEntityLookup = (
entities: EntityRegistryEntry[]
): DeviceEntityLookup => {
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
return deviceEntityLookup;
};
export const getDeviceIntegrationLookup = (
entitySources: EntitySources,
entities: EntityRegistryEntry[]
): Record<string, string[]> => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain || entity.device_id === null) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
};

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