Compare commits

...

139 Commits

Author SHA1 Message Date
Paul Bottein
67166be0d7 Add icon 2023-09-29 15:44:08 +02:00
Paul Bottein
4b386c0661 Add current state 2023-09-29 12:45:03 +02:00
Paul Bottein
3a03abbd3a Add "Add condition" button in the editor 2023-09-29 11:17:00 +02:00
Paul Bottein
873ce2b405 Improve performance and editor 2023-09-29 11:17:00 +02:00
Paul Bottein
89b53a76f0 Add reponsive editor with breakpoints 2023-09-29 11:16:59 +02:00
Paul Bottein
15522d4926 Use media query, remove ui editor for responsive 2023-09-29 11:16:59 +02:00
Paul Bottein
c6e6255d19 Add UI for responsive condition 2023-09-29 11:16:59 +02:00
Paul Bottein
8bcd730501 Migrate state condition ui to ha-form 2023-09-29 11:16:59 +02:00
Paul Bottein
689cd3d145 Add responsive condition for conditional card 2023-09-29 11:16:59 +02:00
Bram Kragten
8daff17d6a Change linear-progress bar background color for dark mode (#18059) 2023-09-29 10:33:26 +02:00
renovate[bot]
59bd852e7a Update dependency @octokit/rest to v20.0.2 (#18053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-28 22:51:10 -04:00
renovate[bot]
246fe2861e Update dependency eslint-plugin-wc to v2.0.4 (#18050)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-28 14:18:22 -04:00
renovate[bot]
dbf623ada2 Update typescript-eslint monorepo to v6.7.3 (#18051)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-28 14:17:33 -04:00
renovate[bot]
c848356a6d Update dependency @types/sortablejs to v1.15.3 (#18049)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-28 14:16:08 -04:00
Paul Bottein
d732bd4776 Bumped version to 20230928.0 2023-09-28 20:12:59 +02:00
Raman Gupta
5a5265723c Handle breaking changes as result of zwave_js lib upgrade (#18009)
* Handle breaking changes as result of zwave_js lib upgrade

* fix capitalization
2023-09-28 20:00:21 +02:00
Bram Kragten
30c6e4e35e Link to docs when no wake word engines (#18046) 2023-09-28 19:34:31 +02:00
renovate[bot]
c5f909d89f Update dependency @types/serve-handler to v6.1.2 (#18048)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-28 13:19:14 -04:00
Simon Lamon
13a691606f Migrate developer events tools to LitElement (#17869) 2023-09-28 14:58:16 +00:00
Simon Lamon
44748df3ac Make numeric_state trigger and condition translatable (#18017) 2023-09-28 14:39:57 +02:00
dependabot[bot]
4f3dc82fd9 Bump get-func-name from 2.0.0 to 2.0.2 (#18043)
Bumps [get-func-name](https://github.com/chaijs/get-func-name) from 2.0.0 to 2.0.2.
- [Release notes](https://github.com/chaijs/get-func-name/releases)
- [Commits](https://github.com/chaijs/get-func-name/commits/v2.0.2)

---
updated-dependencies:
- dependency-name: get-func-name
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 23:26:34 -04:00
Paul Bottein
b2e260d6ba Use initial color param for favorite color dialog (#18039)
* Use initial color param for favorite color dialog

* Select right mode if light is off
2023-09-27 16:26:16 +02:00
imgbot[bot]
7111a21173 [ImgBot] Optimize images (#18038)
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2023-09-27 14:31:05 +02:00
karwosts
ad51d313a1 Debug option to enable logging server calls (#17956) 2023-09-27 14:26:25 +02:00
Bram Kragten
956723cf15 Bumped version to 20230926.0 2023-09-26 23:51:50 +02:00
K3A
dd6a69ea03 State History Charts - Handle click to chart to open More Info for the clicked entity (#18036) 2023-09-26 21:19:11 +00:00
Bram Kragten
98bd08c9dd Update layout of hardware info (#18034) 2023-09-26 23:09:14 +02:00
karwosts
6b31c07459 Add an image fit mode to hui-image and picture-entity-card (#17959) 2023-09-26 23:08:56 +02:00
renovate[bot]
c3b41afb68 Update dependency @types/esprima to v4.0.4 (#18031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-26 23:07:07 +02:00
renovate[bot]
46d8f2eefb Update dependency @lokalise/node-api to v12 (#18029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-26 23:05:51 +02:00
karwosts
0ffc7b59d6 Prevent creating views with duplicate URL (#17871) 2023-09-26 22:54:27 +02:00
karwosts
74be4ae20a Enable reorder mode for choose options (#17422) 2023-09-26 22:52:20 +02:00
karwosts
5455ce2e0f Display an error message when disabling the button in devtools/service (#17519) 2023-09-26 22:49:57 +02:00
karwosts
01405d96b6 Fix too wide energy chart bars (#17787) 2023-09-26 22:49:04 +02:00
renovate[bot]
965f893a65 Update dependency @octokit/auth-oauth-device to v6.0.1 (#18033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-26 22:44:02 +02:00
Simon Lamon
0b6813d9dc Update Add and Edit area dialog (#17711) 2023-09-26 22:41:02 +02:00
Paul Bottein
cbd424ff5a Fix aria label auth page (#18032) 2023-09-26 22:39:25 +02:00
Bram Kragten
b899e39a9e Fix markdown card template handling (#17851) 2023-09-26 22:37:27 +02:00
karwosts
49a66961e2 Fix adjust statistic dialog after a period of sensor unavailability (#17934)
* Fix adjust statistic dialog after a period of sensor unavailability

* Remove fetching an unnecessary datapoint
2023-09-26 22:00:39 +02:00
karwosts
c69fb77b62 Fix choose description when a non-list form of conditions is used (#18023) 2023-09-26 21:54:40 +02:00
Bram Kragten
d794ec3408 Update assist-pipeline-detail-wakeword.ts 2023-09-26 21:27:27 +02:00
Paul Bottein
d8c98d8f96 Move auth components from shadow DOM to light DOM (#18015) 2023-09-26 20:17:03 +02:00
karwosts
acb32ae5c8 Allow saving the light current color as a favorite (#17992) 2023-09-26 18:59:59 +02:00
Bram Kragten
c567a61dd7 Add wake word to assist pipeline settings (#18019)
* Add wake word to assist pipeline settings

* Update assist-pipeline-detail-wakeword.ts

* Add icon for wake word domain

* format state

* implement `wake_word/info` command
2023-09-26 12:17:45 -04:00
Bram Kragten
2a8d98307e Fix chart not able to setup because canvas in use (#18018)
* Fix chart not able to setup because canvas in use

* Update ha-chart-base.ts
2023-09-26 17:58:02 +02:00
Bram Kragten
5aaf0cd579 Update logo (#17964) 2023-09-26 15:33:40 +02:00
renovate[bot]
f38e4dcf54 Update dependency @octokit/plugin-retry to v6.0.1 (#18026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-26 12:54:40 +00:00
Steve Repsher
1df1ce5423 Stop notifications for errors writing to system log (#18022) 2023-09-26 14:43:27 +02:00
Paul Bottein
a68381a4d9 Clear quick bar items with closing the quick bar dialog (#18025) 2023-09-26 14:27:27 +02:00
renovate[bot]
f7604b136e Update dependency eslint to v8.50.0 (#18021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 18:35:42 -04:00
karwosts
362d950515 Ensure computeStateName returns a string type (#17928) 2023-09-25 18:11:02 +02:00
Bram Kragten
22f9dbd65d Add wake word to assist pipeline debug (#17897) 2023-09-25 17:51:12 +02:00
karwosts
579050bfc7 Add new config options for map entity markers (#17938) 2023-09-25 17:26:53 +02:00
Joost Lekkerkerker
6b33b4e656 Add label to location selector (#17402)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-09-25 13:38:47 +00:00
renovate[bot]
dac7c0f5fd Lock file maintenance (#17960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-09-25 12:43:17 +00:00
renovate[bot]
aaceff0d23 Update dependency marked to v9 (#17914)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 14:13:27 +02:00
renovate[bot]
48e5cc6b63 Update dependency eslint-plugin-lit-a11y to v4 (#17143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-09-25 14:11:42 +02:00
renovate[bot]
799a0933ba Update dependency chart.js to v4.4.0 (#17736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 14:11:18 +02:00
dependabot[bot]
940618f72d Bump home-assistant/wheels from 2023.04.0 to 2023.09.1 (#18011)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 14:10:42 +02:00
renovate[bot]
d8ff69b65d Update fullcalendar monorepo to v6.1.9 (#18007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 14:08:54 +02:00
Steve Repsher
391aca6388 Improve core script and default to upgrade (#17915) 2023-09-25 14:08:30 +02:00
Simon Lamon
071d078e84 Make some triggers translatable (#17692) 2023-09-25 14:07:31 +02:00
renovate[bot]
61982bcb77 Update CodeMirror (#17996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 13:23:51 +02:00
Bram Kragten
1c79fcc244 Fix translations onboarding integration (#17980) 2023-09-25 12:58:54 +02:00
dependabot[bot]
f3513e3e52 Bump actions/checkout from 4.0.0 to 4.1.0 (#18010)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 11:20:50 +02:00
renovate[bot]
b3b10fa2ef Update dependency eslint-plugin-wc to v2.0.3 (#17999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-24 16:48:26 -04:00
renovate[bot]
0ffabcc055 Update dependency glob to v10.3.5 (#18000)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-24 16:44:03 -04:00
renovate[bot]
9da8499004 Update dependency eslint-plugin-wc to v2 (#17966)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-23 00:50:32 -04:00
renovate[bot]
04d1fccb87 Update formatjs monorepo (#17975)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-23 00:27:33 -04:00
renovate[bot]
fdf1eb5170 Update dependency @lokalise/node-api to v11.1.0 (#17995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-23 00:24:25 -04:00
renovate[bot]
4e2877b035 Update vaadinWebComponents monorepo to v24.1.9 (#17976) 2023-09-22 00:45:15 +00:00
renovate[bot]
467059f515 Update dependency cropperjs to v1.6.1 (#17968) 2023-09-21 20:38:02 -04:00
renovate[bot]
e6db63bb63 Update typescript-eslint monorepo to v6.7.2 (#17982) 2023-09-21 20:33:43 -04:00
Paul Bottein
78ddec2c8c Small fixes for tile features (#17983)
* Add missing translations

* Remove unused import
2023-09-21 21:33:32 +02:00
Paul Bottein
9217d5bf40 Refactor strategy foundation (#17921) 2023-09-21 20:22:52 +02:00
Paul Bottein
90d01e4b63 Add style and preset modes options to climate preset tile feature (#17977) 2023-09-21 18:41:09 +02:00
Joost Lekkerkerker
e7960bf8c0 Make pull request template more in line with core (#17441) 2023-09-21 17:23:31 +02:00
Thomas Aldrian
bf12eaa1b3 Add climate preset tile feature (#17946)
* Add climate preset tile feature

* Minor fixes

* Formatting fixes

* Update src/panels/lovelace/create-element/create-tile-feature-element.ts

* Update src/panels/lovelace/editor/config-elements/hui-tile-card-features-editor.ts

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-09-21 12:27:41 +02:00
Bram Kragten
6179c75182 don't round update entity images (#17972) 2023-09-20 20:39:54 +02:00
Paul Bottein
4b5c7021ff Add select option tile feature (#17971) 2023-09-20 12:43:21 +02:00
Paul Bottein
3349031cbd Simplify data table template (#17825)
* Simplify data table template

* Fix backup and gallery
2023-09-20 12:09:44 +02:00
renovate[bot]
5e107d43d7 Update babel monorepo to v7.22.20 (#17965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-19 14:36:07 -04:00
Steve Repsher
e46f2cd9bf Adjust password manager polyfill for form injections (#17830) 2023-09-19 14:51:38 +02:00
Steve Repsher
713ebfcc22 Enable shipped proposals in Babel (#17909) 2023-09-19 13:56:55 +02:00
renovate[bot]
46e4eafe95 Update dependency @types/babel__plugin-transform-runtime to v7.9.3 (#17957) 2023-09-19 07:45:50 -04:00
renovate[bot]
e6fd18e23b Update dependency @types/js-yaml to v4.0.6 (#17958) 2023-09-19 07:43:55 -04:00
renovate[bot]
71cd71dfd5 Update formatjs monorepo (#17890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 12:50:43 -04:00
renovate[bot]
1019ccfd26 Update dependency @lrnwebcomponents/simple-tooltip to v7.0.18 (#17905)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 12:41:29 -04:00
renovate[bot]
577c1d8522 Update dependency @lit-labs/context to v0.4.1 (#17935)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 12:35:46 -04:00
renovate[bot]
63f0b469cc Update dependency vis-data to v7.1.7 (#17939) 2023-09-18 12:25:23 -04:00
renovate[bot]
e688417863 Update dependency @babel/core to v7.22.19 (#17948) 2023-09-18 08:27:41 -04:00
renovate[bot]
a19633e2d4 Update CodeMirror (#17944) 2023-09-18 08:21:31 -04:00
renovate[bot]
8797142cca Update dependency sinon to v16 (#17940) 2023-09-18 08:15:40 -04:00
Madelena Mak
2a7403b6fd Added new logo design guidelines (#17951)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2023-09-17 22:29:53 +02:00
renovate[bot]
22efe14149 Update dependency @types/tar to v6.1.6 (#17931)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-15 18:57:25 -04:00
renovate[bot]
7cce24bcd1 Update typescript-eslint monorepo to v6.7.0 (#17923) 2023-09-14 20:25:46 -04:00
renovate[bot]
b8f0bb66cd Update dependency @codemirror/view to v6.18.1 (#17922)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-14 13:11:12 -04:00
renovate[bot]
b950f990b4 Update dependency @material/web to v1.0.0-pre.17 (#17882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-13 10:11:13 +02:00
renovate[bot]
b511e7a37d Update vaadinWebComponents monorepo to v24.1.7 (#17894) 2023-09-12 20:38:23 -04:00
renovate[bot]
50f4b78f2e Update dependency ua-parser-js to v1.0.36 (#17912)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-12 11:45:17 -04:00
renovate[bot]
7b0b4cdfe4 Update dependency @codemirror/view to v6.18.0 (#17865) 2023-09-11 20:59:49 -04:00
renovate[bot]
c60e5c4c61 Update dependency eslint to v8.49.0 (#17904) 2023-09-11 20:41:20 -04:00
karwosts
709a63e6da Fix schedule helper form when resizing or dragging to or past midnight (#17900) 2023-09-11 22:54:45 +02:00
dependabot[bot]
f689eed073 Bump actions/checkout from 3.6.0 to 4.0.0 (#17891)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.6.0...v4.0.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 15:22:18 -04:00
renovate[bot]
cd55eee2fc Update dependency @babel/core to v7.22.17 (#17899)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 15:19:05 -04:00
dependabot[bot]
cf27e68748 Bump actions/cache from 3.3.1 to 3.3.2 (#17892)
Bumps [actions/cache](https://github.com/actions/cache) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3.3.1...v3.3.2)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 12:24:36 -04:00
Bram Kragten
472ed2fe82 Bumped version to 20230911.0 2023-09-11 18:19:34 +02:00
Paul Bottein
d0a60984ed Allow user to theme all cover states in tile card and more info. (#17898) 2023-09-11 18:18:39 +02:00
Bram Kragten
24d401061c Show error when set state fails (#17850) 2023-09-11 18:13:36 +02:00
Bram Kragten
2352d05573 Ignore errors during logging (#17893) 2023-09-11 13:24:08 +02:00
renovate[bot]
87d53e38c4 Update dependency tar to v6.2.0 (#17861)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-10 19:21:53 -04:00
renovate[bot]
db3c535884 Update dependency core-js to v3.32.2 (#17887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-10 19:18:23 -04:00
renovate[bot]
158b24f902 Update dependency luxon to v3.4.3 (#17872)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-10 19:16:40 -04:00
Joakim Sørensen
19c4ed4690 Show alternative names in certificate dialog (#17839) 2023-09-08 19:55:27 +02:00
Bram Kragten
eae4ca1271 if select has multiple, make init value an array (#17868)
if multiple, make it an array
2023-09-08 19:46:40 +02:00
Bram Kragten
0276430ab5 Bumped version to 20230908.0 2023-09-08 15:55:22 +02:00
karwosts
db7caf1c32 Fix default formatEntityState function (#17852) 2023-09-08 15:54:54 +02:00
karwosts
7176a51fec update hasChanged for formatEntityState, fix glance card shouldUpdate (#17854) 2023-09-08 15:54:11 +02:00
Bram Kragten
4a6539d75b Clear template result when template changes (#17849) 2023-09-07 20:48:57 -04:00
renovate[bot]
850699ea70 Update dependency @types/chromecast-caf-sender to v1.0.6 (#17857) 2023-09-08 00:01:27 +00:00
renovate[bot]
c17cc22f88 Update typescript-eslint monorepo to v6.6.0 (#17858) 2023-09-07 19:59:46 -04:00
renovate[bot]
9e3f2d5cb7 Update babel monorepo to v7.22.15 (#17853) 2023-09-07 19:56:59 -04:00
renovate[bot]
0677c9c7b0 Update dependency @types/chromecast-caf-receiver to v6.0.10 (#17856) 2023-09-07 19:47:22 -04:00
Bram Kragten
af7e385884 Fix tooltip graph overflow (#17848) 2023-09-07 19:12:35 +02:00
Bram Kragten
ba88fef09b Bumped version to 20230906.1 2023-09-06 13:41:40 +02:00
Paul Bottein
ad0e59c8f4 Fix no listener margin in flow preview template (#17837) 2023-09-06 13:41:16 +02:00
Bram Kragten
14e6f5e8ca Subscribe to config entries in helper config (#17835) 2023-09-06 13:39:20 +02:00
Bram Kragten
3c48157793 Use report errors instead of strict for template subscription (#17824) 2023-09-06 09:53:54 +02:00
Bram Kragten
3a07af6ad2 Bumped version to 20230906.0 2023-09-06 09:42:29 +02:00
Bram Kragten
c1c05f8d22 Add listeners to template helper preview (#17833) 2023-09-06 09:37:38 +02:00
Bram Kragten
29aed5371c Move translation fetching to gulp action (#17827) 2023-09-06 00:28:26 +02:00
Bram Kragten
76c878df57 Report Unhandled promise rejection as debug for now (#17831) 2023-09-05 16:39:40 -04:00
Bram Kragten
d6e7ebe71d Bumped version to 20230905.0 2023-09-05 18:07:59 +02:00
Erik Montnemery
085b26d5ea Simplify entity sources (#17770) 2023-09-05 14:45:22 +02:00
Bram Kragten
32472ca627 Add weather forecast options to more info (#17823) 2023-09-05 13:29:56 +02:00
Bram Kragten
c3c4bb4421 Fix initial value of select selector (#17822) 2023-09-05 13:29:47 +02:00
Paul Bottein
f7f1a0c32d Add better localize keys typings for config pages (#17815)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-09-05 11:39:03 +02:00
Bram Kragten
d4872b177f Start improve restore backup in onboarding (#17813) 2023-09-04 20:08:33 +02:00
Simon Lamon
5bb8c51d25 Guard against empty application credential domain (#17786) 2023-09-04 19:21:56 +02:00
245 changed files with 6894 additions and 4679 deletions

View File

@@ -2,9 +2,7 @@
You are amazing! Thanks for contributing to our project!
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Breaking change
<!--
If your PR contains a breaking change for existing users, it is important
to tell them what breaks, how to make it work again and why we did this.
@@ -13,8 +11,8 @@
Note: Remove this section if this PR is NOT a breaking change.
-->
## Proposed change
## Proposed change
<!--
Describe the big picture of your changes here to communicate to the
maintainers why we should accept this pull request. If it fixes a bug
@@ -22,8 +20,8 @@
in the additional information section.
-->
## Type of change
## Type of change
<!--
What type of change does your PR introduce to the Home Assistant frontend?
NOTE: Please, check only 1! box!
@@ -38,7 +36,6 @@
- [ ] Code quality improvements to existing code or addition of tests
## Example configuration
<!--
Supplying a configuration snippet, makes it easier for a maintainer to test
your PR.
@@ -49,7 +46,6 @@
```
## Additional information
<!--
Details are important, and help maintainers processing your PR.
Please be sure to fill out additional details, if applicable.
@@ -60,7 +56,6 @@
- Link to documentation pull request:
## Checklist
<!--
Put an `x` in the boxes that apply. You can also fill these out after
creating the PR. If you're unsure about any of them, don't hesitate to ask.

View File

@@ -21,7 +21,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
with:
ref: dev
@@ -57,7 +57,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
with:
ref: master

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Setup Node
uses: actions/setup-node@v3.8.1
with:
@@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/cache@v3.3.1
uses: actions/cache@v3.3.2
with:
path: |
node_modules/.cache/prettier
@@ -55,7 +55,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Setup Node
uses: actions/setup-node@v3.8.1
with:
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Setup Node
uses: actions/setup-node@v3.8.1
with:
@@ -91,7 +91,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Setup Node
uses: actions/setup-node@v3.8.1
with:

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

View File

@@ -22,7 +22,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
with:
ref: dev
@@ -58,7 +58,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
with:
ref: master

View File

@@ -16,7 +16,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Setup Node
uses: actions/setup-node@v3.8.1

View File

@@ -21,7 +21,7 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Setup Node
uses: actions/setup-node@v3.8.1

View File

@@ -20,7 +20,7 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4

View File

@@ -23,7 +23,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
@@ -74,7 +74,7 @@ jobs:
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels
uses: home-assistant/wheels@2023.04.0
uses: home-assistant/wheels@2023.09.1
with:
abi: cp311
tag: musllinux_1_2

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.0
- name: Upload Translations
run: |

View File

@@ -1,3 +1,4 @@
CLA.md
CODE_OF_CONDUCT.md
LICENSE.md
PULL_REQUEST_TEMPLATE.md

View File

@@ -100,6 +100,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
useBuiltIns: latestBuild ? false : "entry",
corejs: latestBuild ? false : { version: "3.32", proposals: true },
bugfixes: true,
shippedProposals: true,
},
],
"@babel/preset-typescript",

View File

@@ -1,10 +1,14 @@
import fs from "fs/promises";
import gulp from "gulp";
import path from "path";
import mapStream from "map-stream";
import transform from "gulp-json-transform";
import { LokaliseApi } from "@lokalise/node-api";
import JSZip from "jszip";
const inDirFrontend = "translations/frontend";
const inDirBackend = "translations/backend";
const inDir = "translations";
const inDirFrontend = `${inDir}/frontend`;
const inDirBackend = `${inDir}/backend`;
const srcMeta = "src/translations/translationMetadata.json";
const encoding = "utf8";
@@ -68,8 +72,9 @@ gulp.task("convert-backend-translations", function () {
});
gulp.task("check-translations-html", function () {
// We exclude backend translations because they are not compliant with the HTML rule for now
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
return gulp
.src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`])
.pipe(checkHtml());
});
gulp.task("check-all-files-exist", async function () {
@@ -89,7 +94,83 @@ gulp.task("check-all-files-exist", async function () {
await Promise.allSettled(writings);
});
const lokaliseProjects = {
backend: "130246255a974bd3b5e8a1.51616605",
frontend: "3420425759f6d6d241f598.13594006",
};
gulp.task("fetch-lokalise", async function () {
let apiKey;
try {
apiKey =
process.env.LOKALISE_TOKEN ||
(await fs.readFile(".lokalise_token", { encoding }));
} catch {
throw new Error(
"An Administrator Lokalise API token is required to download the latest set of translations. Place your token in a new file `.lokalise_token` in the repo root directory."
);
}
const lokaliseApi = new LokaliseApi({ apiKey });
const mkdirPromise = Promise.all([
fs.mkdir(inDirFrontend, { recursive: true }),
fs.mkdir(inDirBackend, { recursive: true }),
]);
await Promise.all(
Object.entries(lokaliseProjects).map(([project, projectId]) =>
lokaliseApi
.files()
.download(projectId, {
format: "json",
original_filenames: false,
replace_breaks: false,
json_unescaped_slashes: true,
export_empty_as: "skip",
})
.then((download) => fetch(download.bundle_url))
.then((response) => {
if (response.status === 200 || response.status === 0) {
return response.arrayBuffer();
}
throw new Error(response.statusText);
})
.then(JSZip.loadAsync)
.then(async (contents) => {
await mkdirPromise;
return Promise.all(
Object.keys(contents.files).map(async (filename) => {
const file = contents.file(filename);
if (!file) {
// no file, probably a directory
return Promise.resolve();
}
return file
.async("nodebuffer")
.then((content) =>
fs.writeFile(
path.join(
inDir,
project,
filename.split("/").splice(-1)[0]
),
content,
{ flag: "w", encoding }
)
);
})
);
})
)
);
});
gulp.task(
"check-downloaded-translations",
gulp.series("check-translations-html", "check-all-files-exist")
"download-translations",
gulp.series(
"fetch-lokalise",
"convert-backend-translations",
"check-translations-html",
"check-all-files-exist"
)
);

View File

@@ -6,6 +6,8 @@ import presetEnv from "@babel/preset-env";
import compilationTargets from "@babel/helper-compilation-targets";
import coreJSCompat from "core-js-compat";
import { logPlugin } from "@babel/preset-env/lib/debug.js";
// eslint-disable-next-line import/no-relative-packages
import shippedPolyfills from "../node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js";
import { babelOptions } from "./bundle.cjs";
const detailsOpen = (heading) =>
@@ -26,6 +28,22 @@ const dummyAPI = {
targets: () => ({}),
};
// Generate filter function based on proposal/method inputs
// Copied and adapted from babel-plugin-polyfill-corejs3/esm/index.mjs
const polyfillFilter = (method, proposals, shippedProposals) => (name) => {
if (proposals || method === "entry-global") return true;
if (shippedProposals && shippedPolyfills.default.has(name)) {
return true;
}
if (name.startsWith("esnext.")) {
const esName = `es.${name.slice(7)}`;
// If its imaginative esName is not in latest compat data, it means the proposal is not stage 4
return esName in coreJSCompat.data;
}
return true;
};
// Log the plugins and polyfills for each build environment
for (const buildType of ["Modern", "Legacy"]) {
const browserslistEnv = buildType.toLowerCase();
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
@@ -46,7 +64,13 @@ for (const buildType of ["Modern", "Legacy"]) {
const targets = compilationTargets.default(babelOpts?.targets, {
browserslistEnv,
});
const polyfillList = coreJSCompat({ targets }).list;
const polyfillList = coreJSCompat({ targets }).list.filter(
polyfillFilter(
`${presetEnvOpts.useBuiltIns}-global`,
presetEnvOpts?.corejs?.proposals,
presetEnvOpts?.shippedProposals
)
);
console.log(
"The following %i polyfills may be injected by Babel:\n",
polyfillList.length

View File

@@ -165,6 +165,7 @@ const createWebpackConfig = ({
"lit/directives/guard$": "lit/directives/guard.js",
"lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js",
"lit/directives/live$": "lit/directives/live.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
"@lit-labs/virtualizer/layouts/grid":
"@lit-labs/virtualizer/layouts/grid.js",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -32,6 +32,8 @@ import { HassElement } from "../../../../src/state/hass-element";
import { castContext } from "../cast_context";
import "./hc-launch-screen";
const DEFAULT_STRATEGY = "original-states";
let resourcesLoaded = false;
@customElement("hc-main")
export class HcMain extends HassElement {
@@ -258,7 +260,7 @@ export class HcMain extends HassElement {
{
strategy: {
type: "energy",
options: { show_date_selection: true },
show_date_selection: true,
},
},
],
@@ -320,10 +322,10 @@ export class HcMain extends HassElement {
this._handleNewLovelaceConfig(
await generateLovelaceDashboardStrategy(
{
hass: this.hass!,
narrow: false,
type: DEFAULT_STRATEGY,
},
"original-states"
this.hass!,
{ narrow: false }
)
);
}

View File

@@ -8,25 +8,67 @@
"src": "/static/icons/favicon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
"purpose": "any"
},
{
"src": "/static/icons/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
"purpose": "any"
},
{
"src": "/static/icons/favicon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
"purpose": "any"
},
{
"src": "/static/icons/favicon-1024x1024.png",
"sizes": "1024x1024",
"type": "image/png",
"purpose": "maskable any"
"purpose": "any"
},
{
"src": "/static/icons/maskable_icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"lang": "en-US",

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
# Note!
Note, the assets in this folder, are not part of the CC license this repository is shipped in.
All rights reserved.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -2,30 +2,86 @@
title: "Logo"
---
# Using our logo
# Our logo
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
As a community, we are proud of our logo. Follow these guidelines to ensure it always represents the identity of the Home Assistant project and community the best way possible.
[Download Logo](https://github.com/home-assistant/assets/tree/master/logo)
![Logo](/images/logo.png)
![Logo](/images/brand/logo.png)
## Using the icon
Please note that this logo is not released under the CC license. All rights reserved.
Our icon is a shorter and most used version of our logo. The icon can exist without the wordmark, the wordmark should never exist without the icon.
# Design
![Logo variants](/images/logo-variants.png)
At the core of the Home Assistant logomark is the Blue House with Antenna, the three most recognizable and distinct features of the previous logo throughout the past decade.
## Using the right variant
### Blue
The pretty blue logo with a background shadow, pictured top left, is our primary logo. It should only be used with black, white, and non-duotone photography.
Blue feels stable and essential. A bright sky blue is joyful, clear, and free of clouds.
When needed you can use our logo without a shadow, as seen as the second variant.
### House
The outlined logo should only be used on packaging.
Of all possible combinations of shapes, a home is best abstracted in the shape of a structure with a pitched roof. With the vast amount of logos based on this shape, the best we can do is to make it more iconic. The house is further simplified - there is no gable and there is no chimney - to an orthogonal shape with an elegant and deliberate proportion.
## Exclusion zone
### Antenna
The logo needs some personal space. It's exclusion zone is equal to a quarter the height of the icon.
Call it a tree, a set of nodes, a PCB, or an antenna. The antenna is the most recognizable and memorable part of the previous Home Assistant logo, and is an easily understandable symbol that conveys technologies that are smart, connected, and growing evergreen.
![Clearspace](/images/clearspace.png)
# Usage
The default variation is the static colored wordmark in horizontal layout and dark text on a light background.
## Layout variations
![Logo layout variants](/images/brand/logo-layout-variants.png)
The default layout is the wordmark in horizontal layout. It provides the clearest context to the brand identity of Home Assistant.
Use the logomark variant when the context is clear that the logo is about Home Assistant. For example, inside the Home Assistant app where users are already aware of where they are at, the logomark variant without the wordmark can be used. The logomark can exist without the wordmark, however, the wordmark should never exist without the icon.
Use the wordmark in vertical layout when the space available has an aspect ratio less than 4:3. For example, in a square space on a t-shirt where a logo is needed, since there is no established context of Home Assistant, the wordmark in vertical layout should be used.
Lastly, use the wordmark in vertical layout with small logomark when Home Assistant is displayed in context of other Home Assistant-related projects. For example, in a flowchart showing the voice pipeline, use this layout for Home Assistant and its other related projects.
## Color variations, backgrounds, and placement
The default color is the colored version on light background with dark text.
For backgrounds that are dark, for example, when it is used on a page in a dark theme, use the colored version on dark background with light text.
In printed materials where color is unavailable, use the monochrome color variations.
On background that are dark or photographic, use the light monochrome color on dark background variation.
On backgrounds that are light or photographic, use the colored version. Do not use the monochrome variations.
Do not enclose the logmark in a square or color or any confined backgrounds, except in specific situations enforced by another company's marketplace guidelines, for example, an iOS app icon.
Do not add drop shadow to the logomark or the wordmark. If legibility is compromised due to the background, change the background to provide more contrast, or in last resort, add a heavily blurred drop shadaow.
It should only be used with black, white, and non-duotone photography.
Unlike the previous version of our logo, no outlined variants are available. Use the monochrome variants in those spaces.
### Exclusion zone
The logo needs some personal space. Its exclusion zone is equal to a quarter the height of the icon.
![Space clearance for the wordmark](/images/brand/logo-exclusion-zone.png)
## Animation
The default is the static variant.
Use the animated variant only for introductory purposes, for example, in the beginning of a video or on a loading screen.
Use the animated with sound variant only when sound is warranted in the user's context. For example, use it in the beginning of a video since sounds are expected in a video, but do not use it on a loading screen since sounds are not expected in a user interface.
Do not repeat the logo animation.
## Sizes and app icon variants
Special variants are created for specific contexts.
Use the tiny variants when the logomark is used in a very small space (16x16 dp), for example, the favicon of the Home Assistant website, a notification on Android, or the menubar of macOS.

View File

@@ -1,10 +1,10 @@
import { mdiHomeAssistant } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-chip";
import "../../../../src/components/ha-chip-set";
import "../../../../src/components/ha-svg-icon";
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
const chips: {
icon?: string;

View File

@@ -343,7 +343,7 @@ export class DemoEntityState extends LitElement {
const columns: DataTableColumnContainer<EntityRowData> = {
icon: {
title: "Icon",
template: (_, entry) => html`
template: (entry) => html`
<state-badge
.stateObj=${entry.stateObj}
.stateColor=${true}
@@ -360,7 +360,7 @@ export class DemoEntityState extends LitElement {
title: "State",
width: "20%",
sortable: true,
template: (_, entry) =>
template: (entry) =>
html`${computeStateDisplay(
hass.localize,
entry.stateObj,
@@ -371,14 +371,14 @@ export class DemoEntityState extends LitElement {
},
device_class: {
title: "Device class",
template: (dc) => html`${dc ?? "-"}`,
template: (entry) => html`${entry.device_class ?? "-"}`,
width: "20%",
filterable: true,
sortable: true,
},
domain: {
title: "Domain",
template: (_, entry) => html`${computeDomain(entry.entity_id)}`,
template: (entry) => html`${computeDomain(entry.entity_id)}`,
width: "20%",
filterable: true,
sortable: true,

View File

@@ -7,7 +7,6 @@ import {
mdiDocker,
mdiExclamationThick,
mdiFlask,
mdiHomeAssistant,
mdiKey,
mdiLinkLock,
mdiNetwork,
@@ -22,7 +21,7 @@ import {
mdiPound,
mdiShield,
} from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
@@ -40,11 +39,11 @@ import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
AddonCapability,
fetchHassioAddonChangelog,
fetchHassioAddonInfo,
HassioAddonDetails,
HassioAddonSetOptionParams,
HassioAddonSetSecurityParams,
fetchHassioAddonChangelog,
fetchHassioAddonInfo,
installHassioAddon,
rebuildLocalAddon,
restartHassioAddon,
@@ -56,9 +55,9 @@ import {
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import {
HassioStats,
extractApiErrorMessage,
fetchHassioStats,
HassioStats,
} from "../../../../src/data/hassio/common";
import {
StoreAddon,
@@ -69,6 +68,7 @@ import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../../src/types";
import { bytesToString } from "../../../../src/util/bytes-to-string";

View File

@@ -49,6 +49,10 @@ import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hass
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
type BackupItem = HassioBackup & {
secondary: string;
};
@customElement("hassio-backups")
export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -117,15 +121,15 @@ export class HassioBackups extends LitElement {
}
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
(narrow: boolean): DataTableColumnContainer<BackupItem> => ({
name: {
title: this.supervisor.localize("backup.name"),
main: true,
sortable: true,
filterable: true,
grows: true,
template: (entry: string, backup: any) =>
html`${entry || backup.slug}
template: (backup) =>
html`${backup.name || backup.slug}
<div class="secondary">${backup.secondary}</div>`,
},
size: {
@@ -134,7 +138,7 @@ export class HassioBackups extends LitElement {
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB",
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
},
location: {
title: this.supervisor.localize("backup.location"),
@@ -142,8 +146,8 @@ export class HassioBackups extends LitElement {
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string | null) =>
entry || this.supervisor.localize("backup.data_disk"),
template: (backup) =>
backup.location || this.supervisor.localize("backup.data_disk"),
},
date: {
title: this.supervisor.localize("backup.created"),
@@ -152,8 +156,8 @@ export class HassioBackups extends LitElement {
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.locale),
template: (backup) =>
relativeTime(new Date(backup.date), this.hass.locale),
},
secondary: {
title: "",
@@ -163,7 +167,7 @@ export class HassioBackups extends LitElement {
})
);
private _backupData = memoizeOne((backups: HassioBackup[]) =>
private _backupData = memoizeOne((backups: HassioBackup[]): BackupItem[] =>
backups.map((backup) => ({
...backup,
secondary: this._computeBackupContent(backup),

View File

@@ -33,7 +33,6 @@ export class HassioUploadBackup extends LitElement {
label="Upload backup"
supports="Supports .TAR files"
@file-picked=${this._uploadFile}
auto-open-file-dialog
></ha-file-upload>
`;
}

View File

@@ -1,13 +1,13 @@
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
import { mdiFolder, mdiPuzzle } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, query } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
@@ -24,6 +24,7 @@ import {
HassioPartialBackupCreateParams,
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
import {
HomeAssistant,
TranslationDict,

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import { mdiHomeAssistant } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -13,6 +12,7 @@ import {
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style";

View File

@@ -25,35 +25,35 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.22.11",
"@babel/runtime": "7.23.1",
"@braintree/sanitize-url": "6.0.4",
"@codemirror/autocomplete": "6.9.0",
"@codemirror/autocomplete": "6.9.1",
"@codemirror/commands": "6.2.5",
"@codemirror/language": "6.9.0",
"@codemirror/language": "6.9.1",
"@codemirror/legacy-modes": "6.3.3",
"@codemirror/search": "6.5.2",
"@codemirror/search": "6.5.4",
"@codemirror/state": "6.2.1",
"@codemirror/view": "6.17.1",
"@codemirror/view": "6.20.2",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.10.0",
"@formatjs/intl-displaynames": "6.5.0",
"@formatjs/intl-datetimeformat": "6.10.3",
"@formatjs/intl-displaynames": "6.5.2",
"@formatjs/intl-getcanonicallocales": "2.2.1",
"@formatjs/intl-listformat": "7.4.0",
"@formatjs/intl-locale": "3.3.2",
"@formatjs/intl-numberformat": "8.7.0",
"@formatjs/intl-pluralrules": "5.2.4",
"@formatjs/intl-relativetimeformat": "11.2.4",
"@fullcalendar/core": "6.1.8",
"@fullcalendar/daygrid": "6.1.8",
"@fullcalendar/interaction": "6.1.8",
"@fullcalendar/list": "6.1.8",
"@fullcalendar/luxon3": "6.1.8",
"@fullcalendar/timegrid": "6.1.8",
"@formatjs/intl-listformat": "7.4.2",
"@formatjs/intl-locale": "3.3.4",
"@formatjs/intl-numberformat": "8.7.2",
"@formatjs/intl-pluralrules": "5.2.6",
"@formatjs/intl-relativetimeformat": "11.2.6",
"@fullcalendar/core": "6.1.9",
"@fullcalendar/daygrid": "6.1.9",
"@fullcalendar/interaction": "6.1.9",
"@fullcalendar/list": "6.1.9",
"@fullcalendar/luxon3": "6.1.9",
"@fullcalendar/timegrid": "6.1.9",
"@lezer/highlight": "1.1.6",
"@lit-labs/context": "0.4.0",
"@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.4",
"@lit-labs/virtualizer": "2.0.7",
"@lrnwebcomponents/simple-tooltip": "7.0.16",
"@lrnwebcomponents/simple-tooltip": "7.0.18",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-button": "0.27.0",
@@ -62,6 +62,7 @@
"@material/mwc-dialog": "0.27.0",
"@material/mwc-drawer": "0.27.0",
"@material/mwc-fab": "0.27.0",
"@material/mwc-floating-label": "0.27.0",
"@material/mwc-formfield": "0.27.0",
"@material/mwc-icon-button": "0.27.0",
"@material/mwc-linear-progress": "0.27.0",
@@ -79,7 +80,7 @@
"@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "=1.0.0-pre.16",
"@material/web": "=1.0.0-pre.17",
"@mdi/js": "7.2.96",
"@mdi/svg": "7.2.96",
"@polymer/iron-flex-layout": "3.0.1",
@@ -93,8 +94,8 @@
"@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.1.6",
"@vaadin/vaadin-themable-mixin": "24.1.6",
"@vaadin/combo-box": "24.1.9",
"@vaadin/vaadin-themable-mixin": "24.1.9",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -102,10 +103,10 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"chart.js": "4.3.3",
"chart.js": "4.4.0",
"comlink": "4.4.1",
"core-js": "3.32.1",
"cropperjs": "1.6.0",
"core-js": "3.32.2",
"cropperjs": "1.6.1",
"date-fns": "2.30.0",
"date-fns-tz": "2.0.0",
"deep-clone-simple": "1.1.1",
@@ -115,13 +116,13 @@
"hls.js": "1.4.12",
"home-assistant-js-websocket": "8.2.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.0",
"intl-messageformat": "10.5.3",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.2",
"marked": "7.0.5",
"luxon": "3.4.3",
"marked": "9.0.3",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
@@ -137,9 +138,9 @@
"tinykeys": "2.1.0",
"tsparticles-engine": "2.12.0",
"tsparticles-preset-links": "2.12.0",
"ua-parser-js": "1.0.35",
"ua-parser-js": "1.0.36",
"unfetch": "5.0.0",
"vis-data": "7.1.6",
"vis-data": "7.1.7",
"vis-network": "9.1.6",
"vue": "2.7.14",
"vue2-daterange-picker": "0.6.8",
@@ -153,47 +154,48 @@
"xss": "1.0.14"
},
"devDependencies": {
"@babel/core": "7.22.11",
"@babel/plugin-proposal-decorators": "7.22.10",
"@babel/plugin-transform-runtime": "7.22.10",
"@babel/preset-env": "7.22.14",
"@babel/preset-typescript": "7.22.11",
"@babel/core": "7.23.0",
"@babel/plugin-proposal-decorators": "7.23.0",
"@babel/plugin-transform-runtime": "7.22.15",
"@babel/preset-env": "7.22.20",
"@babel/preset-typescript": "7.23.0",
"@koa/cors": "4.0.0",
"@octokit/auth-oauth-device": "6.0.0",
"@octokit/plugin-retry": "6.0.0",
"@octokit/rest": "20.0.1",
"@lokalise/node-api": "12.0.0",
"@octokit/auth-oauth-device": "6.0.1",
"@octokit/plugin-retry": "6.0.1",
"@octokit/rest": "20.0.2",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.4",
"@rollup/plugin-json": "6.0.0",
"@rollup/plugin-node-resolve": "15.2.1",
"@rollup/plugin-replace": "5.0.2",
"@types/babel__plugin-transform-runtime": "7.9.2",
"@types/chromecast-caf-receiver": "6.0.9",
"@types/chromecast-caf-sender": "1.0.5",
"@types/esprima": "4.0.3",
"@types/babel__plugin-transform-runtime": "7.9.3",
"@types/chromecast-caf-receiver": "6.0.10",
"@types/chromecast-caf-sender": "1.0.6",
"@types/esprima": "4.0.4",
"@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.0",
"@types/js-yaml": "4.0.5",
"@types/leaflet": "1.9.4",
"@types/js-yaml": "4.0.6",
"@types/leaflet": "1.9.6",
"@types/leaflet-draw": "1.0.8",
"@types/luxon": "3.3.2",
"@types/mocha": "10.0.1",
"@types/qrcode": "1.5.2",
"@types/serve-handler": "6.1.1",
"@types/sortablejs": "1.15.2",
"@types/tar": "6.1.5",
"@types/serve-handler": "6.1.2",
"@types/sortablejs": "1.15.3",
"@types/tar": "6.1.6",
"@types/ua-parser-js": "0.7.37",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.8",
"del": "7.1.0",
"eslint": "8.48.0",
"eslint": "8.50.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-prettier": "9.0.0",
@@ -201,13 +203,13 @@
"eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-lit": "1.9.1",
"eslint-plugin-lit-a11y": "3.0.0",
"eslint-plugin-lit-a11y": "4.1.0",
"eslint-plugin-unused-imports": "3.0.0",
"eslint-plugin-wc": "1.5.0",
"eslint-plugin-wc": "2.0.4",
"esprima": "4.0.1",
"fancy-log": "2.0.0",
"fs-extra": "11.1.1",
"glob": "10.3.4",
"glob": "10.3.7",
"gulp": "4.0.2",
"gulp-flatmap": "1.0.2",
"gulp-json-transform": "0.4.8",
@@ -233,10 +235,10 @@
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.9.2",
"serve-handler": "6.1.5",
"sinon": "15.2.0",
"sinon": "16.0.0",
"source-map-url": "0.4.1",
"systemjs": "6.14.2",
"tar": "6.1.15",
"tar": "6.2.0",
"terser-webpack-plugin": "5.3.9",
"ts-lit-plugin": "2.0.0-pre.1",
"typescript": "5.2.2",

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/static/icons/tile-win-70x70.png"/>
<square150x150logo src="/static/icons/tile-win-150x150.png"/>
<wide310x150logo src="/static/icons/tile-win-310x150.png"/>
<square310x310logo src="/static/icons/tile-win-310x310.png"/>
<TileColor>#18bcf2</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1 +1,23 @@
<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="nonzero" fill="#000" d="M9,16 L9,17 L7,17 L7,16 L1,16 L1,9 L-1,9 L8.00163907,0 L13,4.785368 L13,3 L15,3 L15,7.035368 L17,9 L15,9 L15,16 L9,16 Z M9,16 L9,13.5 C9.49775077,13.0022492 10.1813086,12.3186914 11.0506735,11.4493265 C11.1951058,11.4824829 11.3455072,11.5 11.5,11.5 C12.6045695,11.5 13.5,10.6045695 13.5,9.5 C13.5,8.3954305 12.6045695,7.5 11.5,7.5 C10.3954305,7.5 9.5,8.3954305 9.5,9.5 C9.5,9.65449279 9.5175171,9.80489423 9.55067348,9.94932652 L9,10.5 L9,7.73243561 C9.59780137,7.38662619 10,6.74028236 10,6 C10,4.8954305 9.1045695,4 8,4 C6.8954305,4 6,4.8954305 6,6 C6,6.74028236 6.40219863,7.38662619 7,7.73243561 L7,10.5 L6.44932652,9.94932652 C6.4824829,9.80489423 6.5,9.65449279 6.5,9.5 C6.5,8.3954305 5.6045695,7.5 4.5,7.5 C3.3954305,7.5 2.5,8.3954305 2.5,9.5 C2.5,10.6045695 3.3954305,11.5 4.5,11.5 C4.65352068,11.5 4.80300134,11.4827027 4.9465994,11.4499505 C5.81726201,12.3268973 6.50172888,13.0147433 7,13.5134884 L7,16 L9,16 Z M11.5,10 C11.2238576,10 11,9.77614237 11,9.5 C11,9.22385763 11.2238576,9 11.5,9 C11.7761424,9 12,9.22385763 12,9.5 C12,9.77614237 11.7761424,10 11.5,10 Z M4.5,10 C4.22385763,10 4,9.77614237 4,9.5 C4,9.22385763 4.22385763,9 4.5,9 C4.77614237,9 5,9.22385763 5,9.5 C5,9.77614237 4.77614237,10 4.5,10 Z M8,6.5 C7.72385763,6.5 7.5,6.27614237 7.5,6 C7.5,5.72385763 7.72385763,5.5 8,5.5 C8.27614237,5.5 8.5,5.72385763 8.5,6 C8.5,6.27614237 8.27614237,6.5 8,6.5 Z" id="house-small-tree"/></svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="480.000000pt" height="480.000000pt" viewBox="0 0 480.000000 480.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,480.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2313 4666 c-23 -7 -56 -23 -75 -34 -47 -30 -2059 -2048 -2095 -2102
-45 -67 -77 -135 -109 -230 l-29 -85 0 -995 0 -995 27 -51 c31 -59 93 -118
152 -145 39 -18 83 -19 1001 -19 l960 0 -406 405 c-395 395 -406 406 -433 395
-15 -5 -63 -10 -107 -10 -429 0 -566 577 -181 767 67 34 86 38 164 42 105 4
165 -13 246 -67 113 -74 175 -190 176 -327 1 -44 -3 -96 -7 -115 l-8 -35 316
-315 315 -315 0 1160 -1 1160 -51 35 c-260 177 -226 567 62 704 82 39 209 48
293 21 239 -78 354 -352 242 -575 -32 -63 -89 -125 -141 -156 l-44 -26 0 -811
0 -812 315 315 c218 217 313 320 309 330 -14 35 -16 134 -4 190 26 122 111
227 230 284 82 39 209 48 293 21 115 -38 214 -130 258 -242 19 -46 23 -78 24
-153 0 -86 -3 -101 -32 -163 -40 -84 -118 -163 -198 -202 -49 -23 -77 -29
-150 -33 -50 -2 -108 1 -130 7 l-40 11 -437 -438 -438 -437 0 -307 0 -308 998
0 c981 0 998 1 1042 21 58 26 115 81 148 144 l27 50 0 995 0 995 -33 95 c-72
209 -6 135 -1147 1278 -840 843 -1040 1037 -1082 1059 -64 31 -159 39 -220 19z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20230904.0"
version = "20230928.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# Helper to start Home Assistant Core inside the devcontainer
# Stop on errors
@@ -11,11 +11,35 @@ if [ -z "${DEVCONTAINER}" ]; then
exit 1
fi
if [ -z $(which hass) ]; then
echo "Installing Home Asstant core from dev."
python3 -m pip install --upgrade \
colorlog \
git+https://github.com/home-assistant/home-assistant.git@dev
# Default to installing (or upgrading to) dev branch
coreURL="https://github.com/home-assistant/core.git"
ref="dev"
while getopts "hr:s" opt; do
case $opt in
h) # Help
echo "Usage: $0 [-h|-r <ref>|-s]"
echo -n "Install and run core at the given ref, i.e. branch, tag, or commit. The dev branch is used if no option is specified."
echo "The -s flag skips the install/upgrade, using whatever version is currently installed."
exit 0
;;
r) # Git ref
ref="${OPTARG}"
;;
s) # Skip (use current install)
ref=""
;;
*)
echo "Try $0 -h for help" >&2
exit 1
;;
esac
done
if [ -n "$ref" ]; then
echo "Installing Home Assistant core at ${ref}..."
python3 -m pip install --user --upgrade --src "$HOME/src" \
--editable "git+${coreURL}@${ref}#egg=homeassistant"
fi
if [ ! -d "${WD}/config" ]; then
@@ -30,7 +54,7 @@ logger:
homeassistant.components.frontend: debug
" >> "${WD}/config/configuration.yaml"
if [ ! -z "${HASSIO}" ]; then
if [ -n "${HASSIO}" ]; then
echo "
# frontend:
# development_repo: ${WD}
@@ -46,7 +70,7 @@ frontend:
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
fi
if [ ! -z "${CODESPACES}" ]; then
if [ -n "${CODESPACES}" ]; then
echo "
http:
use_x_forwarded_for: true

View File

@@ -8,40 +8,4 @@ set -eu -o pipefail
cd "$(dirname "$0")/.."
if [ -z "${LOKALISE_TOKEN-}" ] && [ ! -f .lokalise_token ] ; then
echo "Lokalise API token is required to download the latest set of" \
"translations. Please create an account by using the following link:" \
"https://lokalise.co/signup/3420425759f6d6d241f598.13594006/all/" \
"Place your token in a new file \".lokalise_token\" in the repo" \
"root directory."
exit 1
fi
# Load token from file if not already in the environment
[ -z "${LOKALISE_TOKEN-}" ] && LOKALISE_TOKEN="$(<.lokalise_token)"
declare -A PROJECT_ID=( \
[frontend]="3420425759f6d6d241f598.13594006" \
[backend]="130246255a974bd3b5e8a1.51616605" \
)
for project in ${!PROJECT_ID[*]}; do
LOCAL_DIR=`pwd`/translations/${project}
rm -f ${LOCAL_DIR}/* || mkdir -p ${LOCAL_DIR}
docker run \
-v ${LOCAL_DIR}:/opt/dest/locale \
--rm \
lokalise/lokalise-cli-2@sha256:f1860b26be22fa73b8c93bc5f8690f2afc867610a42de6fc27adc790e5d4425d \
lokalise2 \
--token ${LOKALISE_TOKEN} \
--project-id ${PROJECT_ID[${project}]} \
file download \
--export-empty-as skip \
--format json \
--json-unescaped-slashes=true \
--replace-breaks=false \
--original-filenames=false \
--unzip-to /opt/dest
done
./node_modules/.bin/gulp check-downloaded-translations
./node_modules/.bin/gulp download-translations

View File

@@ -1,19 +1,12 @@
/* eslint-disable lit/prefer-static-styles */
import "@material/mwc-button";
import { genClientId } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-alert";
import "../components/ha-checkbox";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
import "../components/ha-form/ha-form";
import "../components/ha-formfield";
import "../components/ha-markdown";
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
@@ -21,7 +14,7 @@ import {
DataEntryFlowStep,
DataEntryFlowStepForm,
} from "../data/data_entry_flow";
import "./ha-password-manager-polyfill";
import "./ha-auth-form";
type State = "loading" | "error" | "step";
@@ -49,6 +42,10 @@ export class HaAuthFlow extends LitElement {
@state() private _storeToken = false;
createRenderRoot() {
return this;
}
willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
@@ -79,13 +76,17 @@ export class HaAuthFlow extends LitElement {
protected render() {
return html`
<style>
ha-auth-flow .action {
margin: 24px 0 8px;
text-align: center;
}
ha-auth-flow .store-token {
margin-top: 10px;
margin-left: -16px;
}
</style>
<form>${this._renderForm()}</form>
<ha-password-manager-polyfill
.step=${this._step}
.stepData=${this._stepData}
@form-submitted=${this._handleSubmit}
@value-changed=${this._stepDataChanged}
></ha-password-manager-polyfill>
`;
}
@@ -128,12 +129,6 @@ export class HaAuthFlow extends LitElement {
(form as any).focus();
}
}, 100);
setTimeout(() => {
this.renderRoot.querySelector(
"ha-password-manager-polyfill"
)!.boundingRect = this.getBoundingClientRect();
}, 500);
}
private _renderForm() {
@@ -205,7 +200,7 @@ export class HaAuthFlow extends LitElement {
></ha-markdown>
`
: nothing}
<ha-form
<ha-auth-form
.data=${this._stepData}
.schema=${autocompleteLoginFields(step.data_schema)}
.error=${step.errors}
@@ -213,7 +208,7 @@ export class HaAuthFlow extends LitElement {
.computeLabel=${this._computeLabelCallback(step)}
.computeError=${this._computeErrorCallback(step)}
@value-changed=${this._stepDataChanged}
></ha-form>
></ha-auth-form>
${this.clientId === genClientId() &&
!["select_mfa_module", "mfa"].includes(step.step_id)
? html`
@@ -395,20 +390,6 @@ export class HaAuthFlow extends LitElement {
this._submitting = false;
}
}
static get styles(): CSSResultGroup {
return css`
.action {
margin: 24px 0 8px;
text-align: center;
}
/* Align with the rest of the form. */
.store-token {
margin-top: 10px;
margin-left: -16px;
}
`;
}
}
declare global {

View File

@@ -0,0 +1,77 @@
/* eslint-disable lit/prefer-static-styles */
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators";
import { HaFormString } from "../components/ha-form/ha-form-string";
import "../components/ha-icon-button";
import "./ha-auth-textfield";
@customElement("ha-auth-form-string")
export class HaAuthFormString extends HaFormString {
protected createRenderRoot() {
return this;
}
protected render(): TemplateResult {
return html`
<style>
ha-auth-form-string {
display: block;
position: relative;
}
ha-auth-form-string[own-margin] {
margin-bottom: 5px;
}
ha-auth-form-string ha-auth-textfield {
display: block !important;
}
ha-auth-form-string ha-icon-button {
position: absolute;
top: 1em;
right: 12px;
--mdc-icon-button-size: 24px;
color: var(--secondary-text-color);
}
ha-auth-form-string ha-icon-button {
inset-inline-start: initial;
inset-inline-end: 12px;
direction: var(--direction);
}
</style>
<ha-auth-textfield
.type=${
!this.isPassword
? this.stringType
: this.unmaskedPassword
? "text"
: "password"
}
.label=${this.label}
.value=${this.data || ""}
.helper=${this.helper}
helperPersistent
.disabled=${this.disabled}
.required=${this.schema.required}
.autoValidate=${this.schema.required}
.name=${this.schema.name}
.autocomplete=${this.schema.autocomplete}
.suffix=${
this.isPassword
? // reserve some space for the icon.
html`<div style="width: 24px"></div>`
: this.schema.description?.suffix
}
.validationMessage=${this.schema.required ? "Required" : undefined}
@input=${this._valueChanged}
@change=${this._valueChanged}
></ha-auth-textfield>
${this.renderIcon()}
</ha-auth-textfield>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-auth-form-string": HaAuthFormString;
}
}

44
src/auth/ha-auth-form.ts Normal file
View File

@@ -0,0 +1,44 @@
/* eslint-disable lit/prefer-static-styles */
import { html } from "lit";
import { customElement } from "lit/decorators";
import { HaForm } from "../components/ha-form/ha-form";
import "./ha-auth-form-string";
@customElement("ha-auth-form")
export class HaAuthForm extends HaForm {
protected fieldElementName(type: string): string {
if (type === "string") {
return `ha-auth-form-${type}`;
}
return super.fieldElementName(type);
}
protected createRenderRoot() {
// attach it as soon as possible to make sure we fetch all events.
this.addValueChangedListener(this);
return this;
}
protected render() {
return html`
<style>
ha-auth-form .root > * {
display: block;
}
ha-auth-form .root > *:not([own-margin]):not(:last-child) {
margin-bottom: 24px;
}
ha-auth-form ha-alert[own-margin] {
margin-bottom: 4px;
}
</style>
${super.render()}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-auth-form": HaAuthForm;
}
}

View File

@@ -0,0 +1,254 @@
/* eslint-disable lit/value-after-constraints */
/* eslint-disable lit/prefer-static-styles */
import { floatingLabel } from "@material/mwc-floating-label/mwc-floating-label-directive";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { live } from "lit/directives/live";
import { HaTextField } from "../components/ha-textfield";
@customElement("ha-auth-textfield")
export class HaAuthTextField extends HaTextField {
protected renderLabel(): TemplateResult | string {
return !this.label
? ""
: html`
<span
.floatingLabelFoundation=${floatingLabel(
this.label
) as unknown as any}
.id=${this.name}
>${this.label}</span
>
`;
}
protected renderInput(shouldRenderHelperText: boolean): TemplateResult {
const minOrUndef = this.minLength === -1 ? undefined : this.minLength;
const maxOrUndef = this.maxLength === -1 ? undefined : this.maxLength;
const autocapitalizeOrUndef = this.autocapitalize
? (this.autocapitalize as
| "off"
| "none"
| "on"
| "sentences"
| "words"
| "characters")
: undefined;
const showValidationMessage = this.validationMessage && !this.isUiValid;
const ariaLabelledbyOrUndef = this.label ? this.name : undefined;
const ariaControlsOrUndef = shouldRenderHelperText
? "helper-text"
: undefined;
const ariaDescribedbyOrUndef =
this.focused || this.helperPersistent || showValidationMessage
? "helper-text"
: undefined;
// TODO: live() directive needs casting for lit-analyzer
// https://github.com/runem/lit-analyzer/pull/91/files
// TODO: lit-analyzer labels min/max as (number|string) instead of string
return html` <input
aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
aria-controls=${ifDefined(ariaControlsOrUndef)}
aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
class="mdc-text-field__input"
type=${this.type}
.value=${live(this.value) as unknown as string}
?disabled=${this.disabled}
placeholder=${this.placeholder}
?required=${this.required}
?readonly=${this.readOnly}
minlength=${ifDefined(minOrUndef)}
maxlength=${ifDefined(maxOrUndef)}
pattern=${ifDefined(this.pattern ? this.pattern : undefined)}
min=${ifDefined(this.min === "" ? undefined : (this.min as number))}
max=${ifDefined(this.max === "" ? undefined : (this.max as number))}
step=${ifDefined(this.step === null ? undefined : (this.step as number))}
size=${ifDefined(this.size === null ? undefined : this.size)}
name=${ifDefined(this.name === "" ? undefined : this.name)}
inputmode=${ifDefined(this.inputMode)}
autocapitalize=${ifDefined(autocapitalizeOrUndef)}
@input=${this.handleInputChange}
@focus=${this.onInputFocus}
@blur=${this.onInputBlur}
/>`;
}
public render() {
return html`
<style>
ha-auth-textfield {
display: inline-flex;
flex-direction: column;
outline: none;
}
ha-auth-textfield:not([disabled]):hover
:not(.mdc-text-field--invalid):not(.mdc-text-field--focused)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-hover-border-color,
rgba(0, 0, 0, 0.87)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-fill-color, whitesmoke);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
+ .mdc-text-field-helper-line
.mdc-text-field-character-counter,
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
.mdc-text-field__icon {
color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused
mwc-notched-outline {
--mdc-notched-outline-stroke-width: 2px;
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-focused-label-color,
var(--mdc-theme-primary, rgba(98, 0, 238, 0.87))
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: #6200ee;
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input {
color: var(--mdc-text-field-ink-color, rgba(0, 0, 0, 0.87));
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line
.mdc-text-field-helper-text:not(
.mdc-text-field-helper-text--validation-msg
),
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line:not(.mdc-text-field--invalid)
.mdc-text-field-character-counter {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-disabled-fill-color, #fafafa);
}
ha-auth-textfield[disabled]
.mdc-text-field.mdc-text-field--outlined
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-disabled-border-color,
rgba(0, 0, 0, 0.06)
);
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled] .mdc-text-field .mdc-text-field__input,
ha-auth-textfield[disabled]
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-helper-text,
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-character-counter {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield[no-spinner] input::-webkit-outer-spin-button,
ha-auth-textfield[no-spinner] input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
ha-auth-textfield[no-spinner] input[type="number"] {
-moz-appearance: textfield;
}
</style>
${super.render()}
`;
}
protected createRenderRoot() {
// add parent style to light dom
const style = document.createElement("style");
style.textContent = HaTextField.elementStyles as unknown as string;
this.append(style);
return this;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-auth-textfield": HaAuthTextField;
}
}

View File

@@ -1,13 +1,7 @@
import punycode from "punycode";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
/* eslint-disable lit/prefer-static-styles */
import { html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import punycode from "punycode";
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
import { extractSearchParamsObject } from "../common/url/search-params";
import "../components/ha-alert";
@@ -61,13 +55,27 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
protected render() {
if (this._error) {
return html`<ha-alert alert-type="error"
>${this._error} ${this.redirectUri}</ha-alert
>`;
return html`
<style>
ha-authorize ha-alert {
display: block;
margin: 16px 0;
}
</style>
<ha-alert alert-type="error"
>${this._error} ${this.redirectUri}</ha-alert
>
`;
}
if (!this._authProviders) {
return html`
<style>
ha-authorize p {
font-size: 14px;
line-height: 20px;
}
</style>
<p>${this.localize("ui.panel.page-authorize.initializing")}</p>
`;
}
@@ -79,6 +87,25 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
const app = this.clientId && this.clientId in appNames;
return html`
<style>
ha-pick-auth-provider {
display: block;
margin-top: 48px;
}
ha-auth-flow {
display: block;
margin-top: 24px;
}
ha-alert {
display: block;
margin: 16px 0;
}
p {
font-size: 14px;
line-height: 20px;
}
</style>
${!this._ownInstance
? html`<ha-alert .alertType=${app ? "info" : "warning"}>
${app
@@ -123,6 +150,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
`;
}
createRenderRoot() {
return this;
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
@@ -217,25 +248,4 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
private async _handleAuthProviderPick(ev) {
this._authProvider = ev.detail;
}
static get styles(): CSSResultGroup {
return css`
ha-pick-auth-provider {
display: block;
margin-top: 48px;
}
ha-auth-flow {
display: block;
margin-top: 24px;
}
ha-alert {
display: block;
margin: 16px 0;
}
p {
font-size: 14px;
line-height: 20px;
}
`;
}
}

View File

@@ -1,127 +0,0 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import type { HaFormSchema } from "../components/ha-form/types";
import { autocompleteLoginFields } from "../data/auth";
import type { DataEntryFlowStep } from "../data/data_entry_flow";
declare global {
interface HTMLElementTagNameMap {
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
}
interface HASSDomEvents {
"form-submitted": undefined;
}
}
const ENABLED_HANDLERS = [
"homeassistant",
"legacy_api_password",
"command_line",
];
@customElement("ha-password-manager-polyfill")
export class HaPasswordManagerPolyfill extends LitElement {
@property({ attribute: false }) public step?: DataEntryFlowStep;
@property({ attribute: false }) public stepData: any;
@property({ attribute: false }) public boundingRect?: DOMRect;
private _styleElement?: HTMLStyleElement;
public connectedCallback() {
super.connectedCallback();
this._styleElement = document.createElement("style");
this._styleElement.textContent = css`
.password-manager-polyfill {
position: absolute;
opacity: 0;
z-index: -1;
}
.password-manager-polyfill input {
width: 100%;
height: 62px;
padding: 0;
border: 0;
}
.password-manager-polyfill input[type="submit"] {
width: 0;
height: 0;
}
`.toString();
document.head.append(this._styleElement);
}
public disconnectedCallback() {
super.disconnectedCallback();
this._styleElement?.remove();
delete this._styleElement;
}
protected createRenderRoot() {
// Add under document body so the element isn't placed inside any shadow roots
return document.body;
}
protected render() {
if (
this.step &&
this.step.type === "form" &&
this.step.step_id === "init" &&
ENABLED_HANDLERS.includes(this.step.handler[0])
) {
return html`
<form
class="password-manager-polyfill"
style=${styleMap({
top: `${this.boundingRect?.y || 148}px`,
left: `calc(50% - ${(this.boundingRect?.width || 360) / 2}px)`,
width: `${this.boundingRect?.width || 360}px`,
})}
aria-hidden="true"
@submit=${this._handleSubmit}
>
${autocompleteLoginFields(this.step.data_schema).map((input) =>
this.render_input(input)
)}
<input type="submit" />
</form>
`;
}
return nothing;
}
private render_input(schema: HaFormSchema) {
const inputType = schema.name.includes("password") ? "password" : "text";
if (schema.type !== "string") {
return "";
}
return html`
<input
tabindex="-1"
.id=${schema.name}
.name=${schema.name}
.type=${inputType}
.value=${this.stepData[schema.name] || ""}
.autocomplete=${schema.autocomplete}
@input=${this._valueChanged}
@change=${this._valueChanged}
/>
`;
}
private _handleSubmit(ev: Event) {
ev.preventDefault();
fireEvent(this, "form-submitted");
}
private _valueChanged(ev: Event) {
const target = ev.target! as HTMLInputElement;
this.stepData = { ...this.stepData, [target.id]: target.value };
fireEvent(this, "value-changed", {
value: this.stepData,
});
}
}

View File

@@ -0,0 +1,9 @@
export function getAllCombinations<T>(arr: T[]) {
return arr.reduce<T[][]>(
(combinations, element) =>
combinations.concat(
combinations.map((combination) => [...combination, element])
),
[[]]
);
}

View File

@@ -15,6 +15,7 @@ import {
mdiCalendarClock,
mdiCarCoolantLevel,
mdiCash,
mdiChatSleep,
mdiClock,
mdiCloudUpload,
mdiCog,
@@ -31,7 +32,6 @@ import {
mdiGauge,
mdiGoogleAssistant,
mdiGoogleCirclesCommunities,
mdiHomeAssistant,
mdiHomeAutomation,
mdiImage,
mdiImageFilterFrames,
@@ -70,6 +70,8 @@ import {
mdiWifi,
} from "@mdi/js";
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
// Constants should be alphabetically sorted by name.
// Arrays with values should be alphabetically sorted if order doesn't matter.
// Each constant should have a description what it is supposed to be used for.
@@ -123,6 +125,7 @@ export const FIXED_DOMAIN_ICONS = {
tts: mdiSpeakerMessage,
updater: mdiCloudUpload,
vacuum: mdiRobotVacuum,
wake_word: mdiChatSleep,
zone: mdiMapMarkerRadius,
};

View File

@@ -193,6 +193,7 @@ export const computeStateDisplayFromEntityAttributes = (
"scene",
"stt",
"tts",
"wake_word",
].includes(domain) ||
(domain === "sensor" && attributes.device_class === "timestamp")
) {

View File

@@ -7,7 +7,7 @@ export const computeStateNameFromEntityAttributes = (
): string =>
attributes.friendly_name === undefined
? computeObjectId(entityId).replace(/_/g, " ")
: attributes.friendly_name || "";
: (attributes.friendly_name ?? "").toString();
export const computeStateName = (stateObj: HassEntity): string =>
computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);

View File

@@ -22,14 +22,7 @@ export type LocalizeKeys =
| `ui.dialogs.unhealthy.reason.${string}`
| `ui.dialogs.unsupported.reason.${string}`
| `ui.panel.config.${string}.${"caption" | "description"}`
| `ui.panel.config.automation.${string}`
| `ui.panel.config.dashboard.${string}`
| `ui.panel.config.devices.${string}`
| `ui.panel.config.energy.${string}`
| `ui.panel.config.info.${string}`
| `ui.panel.config.lovelace.${string}`
| `ui.panel.config.network.${string}`
| `ui.panel.config.scene.${string}`
| `ui.panel.config.zha.${string}`
| `ui.panel.config.zwave_js.${string}`
| `ui.panel.lovelace.card.${string}`

View File

@@ -53,13 +53,14 @@ export class HaChartBase extends LitElement {
@state() private _hiddenDatasets: Set<number> = new Set();
public disconnectedCallback() {
this._releaseCanvas();
super.disconnectedCallback();
this._releaseCanvas();
}
public connectedCallback() {
super.connectedCallback();
if (this.hasUpdated) {
this._releaseCanvas();
this._setupChart();
}
}
@@ -110,7 +111,7 @@ export class HaChartBase extends LitElement {
return;
}
if (changedProps.has("plugins") || changedProps.has("chartType")) {
this.chart.destroy();
this._releaseCanvas();
this._setupChart();
return;
}
@@ -349,9 +350,6 @@ export class HaChartBase extends LitElement {
height: 0;
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
.chartContainer {
position: relative;
}
canvas {
max-height: var(--chart-max-height, 400px);
}

View File

@@ -35,6 +35,8 @@ export class StateHistoryChartLine extends LitElement {
@property({ type: Boolean }) public showNames = true;
@property({ type: Boolean }) public clickForMoreInfo = true;
@property({ attribute: false }) public startTime!: Date;
@property({ attribute: false }) public endTime!: Date;
@@ -45,6 +47,8 @@ export class StateHistoryChartLine extends LitElement {
@state() private _chartData?: ChartData<"line">;
@state() private _entityIds: string[] = [];
@state() private _chartOptions?: ChartOptions;
@state() private _yWidth = 0;
@@ -171,6 +175,25 @@ export class StateHistoryChartLine extends LitElement {
},
// @ts-expect-error
locale: numberFormatToLocale(this.hass.locale),
onClick: (e: any) => {
if (!this.clickForMoreInfo) {
return;
}
const points = e.chart.getElementsAtEventForMode(
e,
"nearest",
{ intersect: true },
true
);
if (points.length) {
const firstPoint = points[0];
fireEvent(this, "hass-more-info", {
entityId: this._entityIds[firstPoint.datasetIndex],
});
}
},
};
}
if (
@@ -191,6 +214,7 @@ export class StateHistoryChartLine extends LitElement {
const computedStyles = getComputedStyle(this);
const entityStates = this.data;
const datasets: ChartDataset<"line">[] = [];
const entityIds: string[] = [];
if (entityStates.length === 0) {
return;
}
@@ -242,6 +266,7 @@ export class StateHistoryChartLine extends LitElement {
pointRadius: 0,
data: [],
});
entityIds.push(states.entity_id);
};
if (
@@ -493,6 +518,7 @@ export class StateHistoryChartLine extends LitElement {
this._chartData = {
datasets,
};
this._entityIds = entityIds;
}
}
customElements.define("state-history-chart-line", StateHistoryChartLine);

View File

@@ -1,4 +1,5 @@
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
import { getRelativePosition } from "chart.js/helpers";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
@@ -32,6 +33,8 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ type: Boolean }) public showNames = true;
@property({ type: Boolean }) public clickForMoreInfo = true;
@property({ type: Boolean }) public chunked = false;
@property({ attribute: false }) public startTime!: Date;
@@ -220,6 +223,22 @@ export class StateHistoryChartTimeline extends LitElement {
},
// @ts-expect-error
locale: numberFormatToLocale(this.hass.locale),
onClick: (e: any) => {
if (!this.clickForMoreInfo) {
return;
}
const chart = e.chart;
const canvasPosition = getRelativePosition(e, chart);
const index = Math.abs(
chart.scales.y.getValueForPixel(canvasPosition.y)
);
fireEvent(this, "hass-more-info", {
// @ts-ignore
entityId: this._chartData?.datasets[index]?.label,
});
},
};
}

View File

@@ -69,6 +69,8 @@ export class StateHistoryCharts extends LitElement {
@property({ type: Boolean }) public showNames = true;
@property({ type: Boolean }) public clickForMoreInfo = true;
@property({ type: Boolean }) public isLoadingData = false;
@state() private _computedStartTime!: Date;
@@ -181,6 +183,7 @@ export class StateHistoryCharts extends LitElement {
.paddingYAxis=${this._maxYWidth}
.names=${this.names}
.chartIndex=${index}
.clickForMoreInfo=${this.clickForMoreInfo}
@y-width-changed=${this._yWidthChanged}
></state-history-chart-line>
</div> `;
@@ -197,6 +200,7 @@ export class StateHistoryCharts extends LitElement {
.chunked=${this.virtualize}
.paddingYAxis=${this._maxYWidth}
.chartIndex=${index}
.clickForMoreInfo=${this.clickForMoreInfo}
@y-width-changed=${this._yWidthChanged}
></state-history-chart-timeline>
</div> `;

View File

@@ -74,7 +74,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
title: TemplateResult | string;
label?: TemplateResult | string;
type?: "numeric" | "icon" | "icon-button" | "overflow-menu" | "flex";
template?: (data: any, row: T) => TemplateResult | string | typeof nothing;
template?: (row: T) => TemplateResult | string | typeof nothing;
width?: string;
maxWidth?: string;
grows?: boolean;
@@ -431,7 +431,7 @@ export class HaDataTable extends LitElement {
})
: ""}
>
${column.template ? column.template(row[key], row) : row[key]}
${column.template ? column.template(row) : row[key]}
</div>
`;
})}

View File

@@ -131,11 +131,15 @@ export class StateBadge extends LitElement {
if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl);
}
if (computeDomain(stateObj.entity_id) === "camera") {
const domain = computeDomain(stateObj.entity_id);
if (domain === "camera") {
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
}
hostStyle.backgroundImage = `url(${imageUrl})`;
this._showIcon = false;
if (domain === "update") {
hostStyle.borderRadius = "0";
}
} else if (this.color) {
// Externally provided overriding color wins over state color
iconStyle.color = this.color;

View File

@@ -41,8 +41,6 @@ export class HaClickableListItem extends HaListItem {
height: 100%;
display: flex;
align-items: center;
padding-left: var(--mdc-list-side-padding, 20px);
padding-right: var(--mdc-list-side-padding, 20px);
overflow: hidden;
}
`,

View File

@@ -1,10 +1,12 @@
import { Ripple } from "@material/mwc-ripple";
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import { SelectBase } from "@material/mwc-select/mwc-select-base";
import { mdiMenuDown } from "@mdi/js";
import { css, html, nothing } from "lit";
import {
customElement,
eventOptions,
property,
query,
queryAsync,
state,
@@ -24,6 +26,12 @@ export class HaControlSelectMenu extends SelectBase {
@query(".select-anchor") protected anchorElement!: HTMLDivElement | null;
@property({ type: Boolean, attribute: "show-arrow" })
public showArrow?: boolean;
@property({ type: Boolean, attribute: "hide-label" })
public hideLabel?: boolean;
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
@state() private _shouldRenderRipple = false;
@@ -36,7 +44,9 @@ export class HaControlSelectMenu extends SelectBase {
"select-no-value": !this.selectedText,
};
const labelledby = this.label ? "label" : undefined;
const labelledby = this.label && !this.hideLabel ? "label" : undefined;
const labelAttribute =
this.label && this.hideLabel ? this.label : undefined;
return html`
<div class="select ${classMap(classes)}">
@@ -57,6 +67,7 @@ export class HaControlSelectMenu extends SelectBase {
aria-invalid=${!this.isUiValid}
aria-haspopup="listbox"
aria-labelledby=${ifDefined(labelledby)}
aria-label=${ifDefined(labelAttribute)}
aria-required=${this.required}
@click=${this.onClick}
@focus=${this.onFocus}
@@ -72,11 +83,14 @@ export class HaControlSelectMenu extends SelectBase {
>
${this.renderIcon()}
<div class="content">
<p id="label" class="label">${this.label}</p>
${this.hideLabel
? nothing
: html`<p id="label" class="label">${this.label}</p>`}
${this.selectedText
? html`<p class="value">${this.selectedText}</p>`
: nothing}
</div>
${this.renderArrow()}
${this._shouldRenderRipple && !this.disabled
? html` <mwc-ripple></mwc-ripple> `
: nothing}
@@ -86,13 +100,29 @@ export class HaControlSelectMenu extends SelectBase {
`;
}
private renderArrow() {
if (!this.showArrow) return nothing;
return html`
<div class="icon">
<ha-svg-icon .path=${mdiMenuDown}></ha-svg-icon>
</div>
`;
}
private renderIcon() {
const index = this.mdcFoundation?.getSelectedIndex();
const items = this.menuElement?.items ?? [];
const item = index != null ? items[index] : undefined;
const icon =
item?.querySelector("[slot='graphic']") ??
(null as HaSvgIcon | HaIcon | null);
const defaultIcon = this.querySelector("[slot='icon']");
const icon = (item?.querySelector("[slot='graphic']") ?? null) as
| HaSvgIcon
| HaIcon
| null;
if (!defaultIcon && !icon) {
return null;
}
return html`
<div class="icon">
@@ -171,14 +201,18 @@ export class HaControlSelectMenu extends SelectBase {
--control-select-menu-background-color: var(--disabled-color);
--control-select-menu-background-opacity: 0.2;
--control-select-menu-border-radius: 14px;
--control-select-menu-height: 48px;
--control-select-menu-padding: 6px 10px;
--mdc-icon-size: 20px;
font-size: 14px;
line-height: 1.4;
width: auto;
color: var(--primary-text-color);
-webkit-tap-highlight-color: transparent;
}
.select-anchor {
height: 48px;
padding: 6px 10px;
height: var(--control-select-menu-height);
padding: var(--control-select-menu-padding);
overflow: hidden;
position: relative;
cursor: pointer;
@@ -193,15 +227,12 @@ export class HaControlSelectMenu extends SelectBase {
--mdc-ripple-color: var(--control-select-menu-background-color);
/* For safari border-radius overflow */
z-index: 0;
font-size: inherit;
transition: color 180ms ease-in-out;
gap: 10px;
width: 100%;
user-select: none;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
letter-spacing: 0.25px;
}
.content {
@@ -223,8 +254,7 @@ export class HaControlSelectMenu extends SelectBase {
}
.label {
font-size: 12px;
line-height: 16px;
font-size: 0.85em;
letter-spacing: 0.4px;
}

View File

@@ -155,11 +155,12 @@ export class HaConversationAgentPicker extends LitElement {
if (!this._configEntry) {
return;
}
showOptionsFlowDialog(
this,
this._configEntry,
await fetchIntegrationManifest(this.hass, this._configEntry.domain)
);
showOptionsFlowDialog(this, this._configEntry, {
manifest: await fetchIntegrationManifest(
this.hass,
this._configEntry.domain
),
});
}
static get styles(): CSSResultGroup {

View File

@@ -27,7 +27,8 @@ export const computeInitialHaFormData = (
data[field.name] = 0.0;
} else if (field.type === "select") {
if (field.options.length) {
data[field.name] = field.options[0][0];
const val = field.options[0];
data[field.name] = Array.isArray(val) ? val[0] : val;
}
} else if (field.type === "positive_time_period_dict") {
data[field.name] = {
@@ -60,8 +61,10 @@ export const computeInitialHaFormData = (
data[field.name] = selector.number?.min ?? 0;
} else if ("select" in selector) {
if (selector.select?.options.length) {
const val = selector.select.options[0];
data[field.name] = Array.isArray(val) ? val[0] : val;
const firstOption = selector.select.options[0];
const val =
typeof firstOption === "string" ? firstOption : firstOption.value;
data[field.name] = selector.select.multiple ? [val] : val;
}
} else if ("duration" in selector) {
data[field.name] = {

View File

@@ -1,11 +1,13 @@
/* eslint-disable lit/prefer-static-styles */
import { mdiEye, mdiEyeOff } from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
@@ -32,7 +34,7 @@ export class HaFormString extends LitElement implements HaFormElement {
@property({ type: Boolean }) public disabled = false;
@state() private _unmaskedPassword = false;
@state() protected unmaskedPassword = false;
@query("ha-textfield") private _input?: HaTextField;
@@ -43,14 +45,11 @@ export class HaFormString extends LitElement implements HaFormElement {
}
protected render(): TemplateResult {
const isPassword = MASKED_FIELDS.some((field) =>
this.schema.name.includes(field)
);
return html`
<ha-textfield
.type=${!isPassword
? this._stringType
: this._unmaskedPassword
.type=${!this.isPassword
? this.stringType
: this.unmaskedPassword
? "text"
: "password"}
.label=${this.label}
@@ -62,7 +61,7 @@ export class HaFormString extends LitElement implements HaFormElement {
.autoValidate=${this.schema.required}
.name=${this.schema.name}
.autocomplete=${this.schema.autocomplete}
.suffix=${isPassword
.suffix=${this.isPassword
? // reserve some space for the icon.
html`<div style="width: 24px"></div>`
: this.schema.description?.suffix}
@@ -70,14 +69,19 @@ export class HaFormString extends LitElement implements HaFormElement {
@input=${this._valueChanged}
@change=${this._valueChanged}
></ha-textfield>
${isPassword
? html`<ha-icon-button
toggles
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>`
: ""}
${this.renderIcon()}
`;
}
protected renderIcon() {
if (!this.isPassword) return nothing;
return html`
<ha-icon-button
toggles
.label=${`${this.unmaskedPassword ? "Hide" : "Show"} password`}
@click=${this.toggleUnmaskedPassword}
.path=${this.unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
`;
}
@@ -87,11 +91,11 @@ export class HaFormString extends LitElement implements HaFormElement {
}
}
private _toggleUnmaskedPassword(): void {
this._unmaskedPassword = !this._unmaskedPassword;
protected toggleUnmaskedPassword(): void {
this.unmaskedPassword = !this.unmaskedPassword;
}
private _valueChanged(ev: Event): void {
protected _valueChanged(ev: Event): void {
let value: string | undefined = (ev.target as HaTextField).value;
if (this.data === value) {
return;
@@ -104,7 +108,7 @@ export class HaFormString extends LitElement implements HaFormElement {
});
}
private get _stringType(): string {
protected get stringType(): string {
if (this.schema.format) {
if (["email", "url"].includes(this.schema.format)) {
return this.schema.format;
@@ -116,6 +120,10 @@ export class HaFormString extends LitElement implements HaFormElement {
return "text";
}
protected get isPassword(): boolean {
return MASKED_FIELDS.some((field) => this.schema.name.includes(field));
}
static get styles(): CSSResultGroup {
return css`
:host {

View File

@@ -1,3 +1,4 @@
/* eslint-disable lit/prefer-static-styles */
import {
css,
CSSResultGroup,
@@ -135,7 +136,7 @@ export class HaForm extends LitElement implements HaFormElement {
.required=${item.required || false}
.context=${this._generateContext(item)}
></ha-selector>`
: dynamicElement(`ha-form-${item.type}`, {
: dynamicElement(this.fieldElementName(item.type), {
schema: item,
data: getValue(this.data, item),
label: this._computeLabel(item, this.data),
@@ -152,6 +153,10 @@ export class HaForm extends LitElement implements HaFormElement {
`;
}
protected fieldElementName(type: string): string {
return `ha-form-${type}`;
}
private _generateContext(
schema: HaFormSchema
): Record<string, any> | undefined {
@@ -169,10 +174,17 @@ export class HaForm extends LitElement implements HaFormElement {
protected createRenderRoot() {
const root = super.createRenderRoot();
// attach it as soon as possible to make sure we fetch all events.
root.addEventListener("value-changed", (ev) => {
this.addValueChangedListener(root);
return root;
}
protected addValueChangedListener(element: Element | ShadowRoot) {
element.addEventListener("value-changed", (ev) => {
ev.stopPropagation();
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
if (ev.target === this) return;
const newValue = !schema.name
? ev.detail.value
: { [schema.name]: ev.detail.value };
@@ -181,7 +193,6 @@ export class HaForm extends LitElement implements HaFormElement {
value: { ...this.data, ...newValue },
});
});
return root;
}
private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) {

View File

@@ -124,6 +124,17 @@ export class HaIcon extends LitElement {
return;
}
if (iconName === "home-assistant") {
const icon = (await import("../resources/home-assistant-logo-svg"))
.mdiHomeAssistant;
if (this.icon === requestedIcon) {
this._path = icon;
}
cachedIcons[iconName] = icon;
return;
}
let databaseIcon: string | undefined;
try {
databaseIcon = await getIcon(iconName);

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,7 @@ export class HaLocationSelector extends LitElement {
protected render() {
return html`
<p>${this.label ? this.label : ""}</p>
<ha-locations-editor
class="flex"
.hass=${this.hass}
@@ -78,10 +79,13 @@ export class HaLocationSelector extends LitElement {
}
static styles = css`
:host {
ha-locations-editor {
display: block;
height: 400px;
}
p {
margin-top: 0;
}
`;
}

View File

@@ -237,8 +237,6 @@ export class HaTargetPicker extends LitElement {
: html`<span role="gridcell">
<ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${this.hass.localize(
"ui.components.target-picker.expand"
)}
@@ -257,8 +255,6 @@ export class HaTargetPicker extends LitElement {
<span role="gridcell">
<ha-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${this.hass.localize("ui.components.target-picker.remove")}
.path=${mdiClose}
hideTooltip

View File

@@ -15,7 +15,7 @@ class HaEntityMarker extends LitElement {
protected render() {
return html`
<div
class="marker"
class="marker ${this.entityPicture ? "picture" : ""}"
style=${styleMap({ "border-color": this.entityColor })}
@click=${this._badgeTap}
>
@@ -45,7 +45,6 @@ class HaEntityMarker extends LitElement {
justify-content: center;
align-items: center;
box-sizing: border-box;
overflow: hidden;
width: 48px;
height: 48px;
font-size: var(--ha-marker-font-size, 1.5em);
@@ -54,6 +53,9 @@ class HaEntityMarker extends LitElement {
color: var(--primary-text-color);
background-color: var(--card-background-color);
}
.marker.picture {
overflow: hidden;
}
.entity-picture {
background-size: cover;
height: 100%;

View File

@@ -37,6 +37,9 @@ export interface HaMapPaths {
export interface HaMapEntity {
entity_id: string;
color: string;
label_mode?: "name" | "state";
name?: string;
focus?: boolean;
}
@customElement("ha-map")
@@ -71,6 +74,8 @@ export class HaMap extends ReactiveElement {
private _mapItems: Array<Marker | Circle> = [];
private _mapFocusItems: Array<Marker | Circle> = [];
private _mapZones: Array<Marker | Circle> = [];
private _mapPaths: Array<Polyline | CircleMarker> = [];
@@ -168,7 +173,7 @@ export class HaMap extends ReactiveElement {
return;
}
if (!this._mapItems.length && !this.layers?.length) {
if (!this._mapFocusItems.length && !this.layers?.length) {
this.leafletMap.setView(
new this.Leaflet.LatLng(
this.hass.config.latitude,
@@ -180,7 +185,9 @@ export class HaMap extends ReactiveElement {
}
let bounds = this.Leaflet.latLngBounds(
this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : []
this._mapFocusItems
? this._mapFocusItems.map((item) => item.getLatLng())
: []
);
if (this.fitZones) {
@@ -324,6 +331,7 @@ export class HaMap extends ReactiveElement {
if (this._mapItems.length) {
this._mapItems.forEach((marker) => marker.remove());
this._mapItems = [];
this._mapFocusItems = [];
}
if (this._mapZones.length) {
@@ -353,7 +361,8 @@ export class HaMap extends ReactiveElement {
if (!stateObj) {
continue;
}
const title = computeStateName(stateObj);
const customTitle = typeof entity !== "string" ? entity.name : undefined;
const title = customTitle ?? computeStateName(stateObj);
const {
latitude,
longitude,
@@ -413,17 +422,20 @@ export class HaMap extends ReactiveElement {
// DRAW ENTITY
// create icon
const entityName = title
.split(" ")
.map((part) => part[0])
.join("")
.substr(0, 3);
const entityName =
typeof entity !== "string" && entity.label_mode === "state"
? this.hass.formatEntityState(stateObj)
: customTitle ??
title
.split(" ")
.map((part) => part[0])
.join("")
.substr(0, 3);
// create marker with the icon
this._mapItems.push(
Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
html: `
const marker = Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
html: `
<ha-entity-marker
entity-id="${getEntityId(entity)}"
entity-name="${entityName}"
@@ -437,12 +449,15 @@ export class HaMap extends ReactiveElement {
}
></ha-entity-marker>
`,
iconSize: [48, 48],
className: "",
}),
title: computeStateName(stateObj),
})
);
iconSize: [48, 48],
className: "",
}),
title: title,
});
this._mapItems.push(marker);
if (typeof entity === "string" || entity.focus !== false) {
this._mapFocusItems.push(marker);
}
// create circle around if entity has accuracy
if (gpsAccuracy) {

View File

@@ -14,6 +14,8 @@ export interface AssistPipeline {
tts_engine: string | null;
tts_language: string | null;
tts_voice: string | null;
wake_word_entity: string | null;
wake_word_id: string | null;
}
export interface AssistPipelineMutableParams {
@@ -26,6 +28,8 @@ export interface AssistPipelineMutableParams {
tts_engine: string | null;
tts_language: string | null;
tts_voice: string | null;
wake_word_entity: string | null;
wake_word_id: string | null;
}
export interface assistRunListing {
@@ -61,6 +65,19 @@ interface PipelineErrorEvent extends PipelineEventBase {
};
}
interface PipelineWakeWordStartEvent extends PipelineEventBase {
type: "wake_word-start";
data: {
engine: string;
metadata: SpeechMetadata;
};
}
interface PipelineWakeWordEndEvent extends PipelineEventBase {
type: "wake_word-end";
data: { wake_word_output: { ww_id: string; timestamp: number } };
}
interface PipelineSTTStartEvent extends PipelineEventBase {
type: "stt-start";
data: {
@@ -110,6 +127,8 @@ export type PipelineRunEvent =
| PipelineRunStartEvent
| PipelineRunEndEvent
| PipelineErrorEvent
| PipelineWakeWordStartEvent
| PipelineWakeWordEndEvent
| PipelineSTTStartEvent
| PipelineSTTEndEvent
| PipelineIntentStartEvent
@@ -126,6 +145,14 @@ export type PipelineRunOptions = (
start_stage: "stt";
input: { sample_rate: number };
}
| {
start_stage: "wake_word";
input: {
sample_rate: number;
timeout?: number;
audio_seconds_to_buffer?: number;
};
}
) & {
end_stage: "stt" | "intent" | "tts";
pipeline?: string;
@@ -135,9 +162,11 @@ export type PipelineRunOptions = (
export interface PipelineRun {
init_options?: PipelineRunOptions;
events: PipelineRunEvent[];
stage: "ready" | "stt" | "intent" | "tts" | "done" | "error";
stage: "ready" | "wake_word" | "stt" | "intent" | "tts" | "done" | "error";
run: PipelineRunStartEvent["data"];
error?: PipelineErrorEvent["data"];
wake_word?: PipelineWakeWordStartEvent["data"] &
Partial<PipelineWakeWordEndEvent["data"]> & { done: boolean };
stt?: PipelineSTTStartEvent["data"] &
Partial<PipelineSTTEndEvent["data"]> & { done: boolean };
intent?: PipelineIntentStartEvent["data"] &
@@ -167,7 +196,18 @@ export const processEvent = (
return undefined;
}
if (event.type === "stt-start") {
if (event.type === "wake_word-start") {
run = {
...run,
stage: "wake_word",
wake_word: { ...event.data, done: false },
};
} else if (event.type === "wake_word-end") {
run = {
...run,
wake_word: { ...run.wake_word!, ...event.data, done: true },
};
} else if (event.type === "stt-start") {
run = {
...run,
stage: "stt",

View File

@@ -238,11 +238,13 @@ export interface ZoneCondition extends BaseCondition {
zone: string;
}
type Weekday = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
export interface TimeCondition extends BaseCondition {
condition: "time";
after?: string;
before?: string;
weekday?: string | string[];
weekday?: Weekday | Weekday[];
}
export interface TemplateCondition extends BaseCondition {

View File

@@ -19,6 +19,10 @@ import {
} from "./device_automation";
import { EntityRegistryEntry } from "./entity_registry";
import { FrontendLocaleData } from "./translation";
import {
formatListWithAnds,
formatListWithOrs,
} from "../common/string/format-list";
const triggerTranslationBaseKey =
"ui.panel.config.automation.editor.triggers.type";
@@ -104,11 +108,6 @@ const tryDescribeTrigger = (
return trigger.alias;
}
const disjunctionFormatter = new Intl.ListFormat("en", {
style: "long",
type: "disjunction",
});
// Event Trigger
if (trigger.platform === "event" && trigger.event_type) {
const eventTypes: string[] = [];
@@ -121,7 +120,7 @@ const tryDescribeTrigger = (
eventTypes.push(trigger.event_type);
}
const eventTypesString = disjunctionFormatter.format(eventTypes);
const eventTypesString = formatListWithOrs(hass.locale, eventTypes);
return hass.localize(
`${triggerTranslationBaseKey}.event.description.full`,
{ eventTypes: eventTypesString }
@@ -139,41 +138,54 @@ const tryDescribeTrigger = (
// Numeric State Trigger
if (trigger.platform === "numeric_state" && trigger.entity_id) {
let base = "When";
const stateObj = hass.states[trigger.entity_id];
const entity = stateObj ? computeStateName(stateObj) : trigger.entity_id;
if (trigger.attribute) {
base += ` ${computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
trigger.attribute
)} from`;
const attribute = trigger.attribute
? computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
trigger.attribute
)
: undefined;
const duration = trigger.for ? describeDuration(trigger.for) : undefined;
if (trigger.above && trigger.below) {
return hass.localize(
`${triggerTranslationBaseKey}.numeric_state.description.above-below`,
{
attribute: attribute,
entity: entity,
above: trigger.above,
below: trigger.below,
duration: duration,
}
);
}
base += ` ${entity} is`;
if (trigger.above !== undefined) {
base += ` above ${trigger.above}`;
if (trigger.above) {
return hass.localize(
`${triggerTranslationBaseKey}.numeric_state.description.above`,
{
attribute: attribute,
entity: entity,
above: trigger.above,
duration: duration,
}
);
}
if (trigger.below !== undefined && trigger.above !== undefined) {
base += " and";
if (trigger.below) {
return hass.localize(
`${triggerTranslationBaseKey}.numeric_state.description.below`,
{
attribute: attribute,
entity: entity,
below: trigger.below,
duration: duration,
}
);
}
if (trigger.below !== undefined) {
base += ` below ${trigger.below}`;
}
if (trigger.for) {
const duration = describeDuration(trigger.for);
if (duration) {
base += ` for ${duration}`;
}
}
return base;
}
// State Trigger
@@ -242,7 +254,7 @@ const tryDescribeTrigger = (
);
}
if (from.length !== 0) {
const fromString = disjunctionFormatter.format(from);
const fromString = formatListWithOrs(hass.locale, from);
base += ` from ${fromString}`;
}
} else {
@@ -283,7 +295,7 @@ const tryDescribeTrigger = (
);
}
if (to.length !== 0) {
const toString = disjunctionFormatter.format(to);
const toString = formatListWithOrs(hass.locale, to);
base += ` to ${toString}`;
}
} else {
@@ -356,7 +368,7 @@ const tryDescribeTrigger = (
);
return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, {
time: disjunctionFormatter.format(result),
time: formatListWithOrs(hass.locale, result),
});
}
@@ -505,11 +517,12 @@ const tryDescribeTrigger = (
);
}
const entitiesString = disjunctionFormatter.format(entities);
const zonesString = disjunctionFormatter.format(zones);
return `When ${entitiesString} ${trigger.event}s ${zonesString} ${
zones.length > 1 ? "zones" : "zone"
}`;
return hass.localize(`${triggerTranslationBaseKey}.zone.description.full`, {
entity: formatListWithOrs(hass.locale, entities),
event: trigger.event.toString(),
zone: formatListWithOrs(hass.locale, zones),
numberOfZones: zones.length,
});
}
// Geo Location Trigger
@@ -540,11 +553,15 @@ const tryDescribeTrigger = (
);
}
const sourcesString = disjunctionFormatter.format(sources);
const zonesString = disjunctionFormatter.format(zones);
return `When ${sourcesString} ${trigger.event}s ${zonesString} ${
zones.length > 1 ? "zones" : "zone"
}`;
return hass.localize(
`${triggerTranslationBaseKey}.geo_location.description.full`,
{
source: formatListWithOrs(hass.locale, sources),
event: trigger.event.toString(),
zone: formatListWithOrs(hass.locale, zones),
numberOfZones: zones.length,
}
);
}
// MQTT Trigger
@@ -583,7 +600,8 @@ const tryDescribeTrigger = (
return hass.localize(
`${triggerTranslationBaseKey}.conversation.description.full`,
{
sentence: disjunctionFormatter.format(
sentence: formatListWithOrs(
hass.locale,
ensureArray(trigger.command).map((cmd) => `'${cmd}'`)
),
}
@@ -592,7 +610,9 @@ const tryDescribeTrigger = (
// Persistent Notification Trigger
if (trigger.platform === "persistent_notification") {
return "When a persistent notification is updated";
return hass.localize(
`${triggerTranslationBaseKey}.persistent_notification.description.full`
);
}
// Device Trigger
@@ -650,15 +670,6 @@ const tryDescribeCondition = (
return condition.alias;
}
const conjunctionFormatter = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
const disjunctionFormatter = new Intl.ListFormat("en", {
style: "long",
type: "disjunction",
});
if (!condition.condition) {
const shorthands: Array<"and" | "or" | "not"> = ["and", "or", "not"];
for (const key of shorthands) {
@@ -756,8 +767,8 @@ const tryDescribeCondition = (
if (entities.length !== 0) {
const entitiesString =
condition.match === "any"
? disjunctionFormatter.format(entities)
: conjunctionFormatter.format(entities);
? formatListWithOrs(hass.locale, entities)
: formatListWithAnds(hass.locale, entities);
base += ` ${entitiesString} ${
condition.entity_id.length > 1 ? "are" : "is"
}`;
@@ -812,7 +823,7 @@ const tryDescribeCondition = (
states.push("a state");
}
const statesString = disjunctionFormatter.format(states);
const statesString = formatListWithOrs(hass.locale, states);
base += ` ${statesString}`;
if (condition.for) {
@@ -827,29 +838,49 @@ const tryDescribeCondition = (
// Numeric State Condition
if (condition.condition === "numeric_state" && condition.entity_id) {
let base = "Confirm";
const stateObj = hass.states[condition.entity_id];
const entity = stateObj ? computeStateName(stateObj) : condition.entity_id;
if ("attribute" in condition) {
base += ` ${condition.attribute} from`;
const attribute = condition.attribute
? computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
condition.attribute
)
: undefined;
if (condition.above && condition.below) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.above-below`,
{
attribute: attribute,
entity: entity,
above: condition.above,
below: condition.below,
}
);
}
base += ` ${entity} is`;
if ("above" in condition) {
base += ` above ${condition.above}`;
if (condition.above) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.above`,
{
attribute: attribute,
entity: entity,
above: condition.above,
}
);
}
if ("below" in condition && "above" in condition) {
base += " and";
if (condition.below) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.below`,
{
attribute: attribute,
entity: entity,
below: condition.below,
}
);
}
if ("below" in condition) {
base += ` below ${condition.below}`;
}
return base;
}
// Time condition
@@ -902,7 +933,7 @@ const tryDescribeCondition = (
`ui.panel.config.automation.editor.conditions.type.time.weekdays.${d}`
)
);
result += " day is " + disjunctionFormatter.format(localizedDays);
result += " day is " + formatListWithOrs(hass.locale, localizedDays);
}
return result;
@@ -981,8 +1012,8 @@ const tryDescribeCondition = (
);
}
const entitiesString = disjunctionFormatter.format(entities);
const zonesString = disjunctionFormatter.format(zones);
const entitiesString = formatListWithOrs(hass.locale, entities);
const zonesString = formatListWithOrs(hass.locale, zones);
return hass.localize(
`${conditionsTranslationBaseKey}.zone.description.full`,
{

View File

@@ -1,7 +1,5 @@
import { EntityFilter } from "../common/entity/entity_filter";
import { PlaceholderContainer } from "../panels/config/automation/thingtalk/dialog-thingtalk";
import { HomeAssistant } from "../types";
import { AutomationConfig } from "./automation";
interface CloudStatusNotLoggedIn {
logged_in: false;
@@ -13,6 +11,7 @@ export interface CertificateInformation {
common_name: string;
expire_date: string;
fingerprint: string;
alternative_names: string[];
}
export interface CloudPreferences {
@@ -66,11 +65,6 @@ export interface CloudWebhook {
managed?: boolean;
}
export interface ThingTalkConversion {
config: Partial<AutomationConfig>;
placeholders: PlaceholderContainer;
}
export const cloudLogin = (
hass: HomeAssistant,
email: string,
@@ -136,9 +130,6 @@ export const disconnectCloudRemote = (hass: HomeAssistant) =>
export const fetchCloudSubscriptionInfo = (hass: HomeAssistant) =>
hass.callWS<SubscriptionInfo>({ type: "cloud/subscription" });
export const convertThingTalk = (hass: HomeAssistant, query: string) =>
hass.callWS<ThingTalkConversion>({ type: "cloud/thingtalk/convert", query });
export const updateCloudPref = (
hass: HomeAssistant,
prefs: {

View File

@@ -1,46 +1,25 @@
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
import { HomeAssistant } from "../types";
interface EntitySourceConfigEntry {
source: "config_entry";
interface EntitySource {
domain: string;
custom_component: boolean;
config_entry: string;
}
interface EntitySourcePlatformConfig {
source: "platform_config";
domain: string;
custom_component: boolean;
}
export type EntitySources = Record<string, EntitySource>;
export type EntitySources = Record<
string,
EntitySourceConfigEntry | EntitySourcePlatformConfig
>;
const fetchEntitySources = (
hass: HomeAssistant,
entity_id?: string
): Promise<EntitySources> =>
hass.callWS({
type: "entity/source",
entity_id,
});
const fetchEntitySources = (hass: HomeAssistant): Promise<EntitySources> =>
hass.callWS({ type: "entity/source" });
export const fetchEntitySourcesWithCache = (
hass: HomeAssistant,
entity_id?: string
hass: HomeAssistant
): Promise<EntitySources> =>
entity_id
? fetchEntitySources(hass, entity_id)
: timeCachePromiseFunc(
"_entitySources",
// cache for 30 seconds
30000,
fetchEntitySources,
// We base the cache on number of states. If number of states
// changes we force a refresh
(hass2) => Object.keys(hass2.states).length,
hass
);
timeCachePromiseFunc(
"_entitySources",
// cache for 30 seconds
30000,
fetchEntitySources,
// We base the cache on number of states. If number of states
// changes we force a refresh
(hass2) => Object.keys(hass2.states).length,
hass
);

View File

@@ -17,12 +17,14 @@ export interface LovelacePanelConfig {
mode: "yaml" | "storage";
}
export type LovelaceStrategyConfig = {
type: string;
[key: string]: any;
};
export interface LovelaceConfig {
title?: string;
strategy?: {
type: string;
options?: Record<string, unknown>;
};
strategy?: LovelaceStrategyConfig;
views: LovelaceViewConfig[];
background?: string;
}
@@ -81,10 +83,7 @@ export interface LovelaceViewConfig {
index?: number;
title?: string;
type?: string;
strategy?: {
type: string;
options?: Record<string, unknown>;
};
strategy?: LovelaceStrategyConfig;
badges?: Array<string | LovelaceBadgeConfig>;
cards?: LovelaceCardConfig[];
path?: string;

View File

@@ -5,7 +5,6 @@ import {
mdiCodeBraces,
mdiDevices,
mdiGestureDoubleTap,
mdiHomeAssistant,
mdiMapMarker,
mdiMapMarkerRadius,
mdiMessageAlert,
@@ -18,6 +17,8 @@ import {
mdiWebhook,
} from "@mdi/js";
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
export const TRIGGER_TYPES = {
calendar: mdiCalendar,
device: mdiDevices,

12
src/data/wake_word.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { HomeAssistant } from "../types";
export interface WakeWord {
id: string;
name: string;
}
export const fetchWakeWordInfo = (hass: HomeAssistant, entity_id: string) =>
hass.callWS<{ wake_words: WakeWord[] }>({
type: "wake_word/info",
entity_id,
});

View File

@@ -36,7 +36,9 @@ export const enum WeatherEntityFeature {
FORECAST_TWICE_DAILY = 4,
}
export type ForecastType = "legacy" | "hourly" | "daily" | "twice_daily";
export type ModernForecastType = "hourly" | "daily" | "twice_daily";
export type ForecastType = ModernForecastType | "legacy";
interface ForecastAttribute {
temperature: number;
@@ -636,7 +638,7 @@ export const getForecast = (
export const subscribeForecast = (
hass: HomeAssistant,
entity_id: string,
forecast_type: "daily" | "hourly" | "twice_daily",
forecast_type: ModernForecastType,
callback: (forecastevent: ForecastEvent) => void
) =>
hass.connection.subscribeMessage<ForecastEvent>(callback, {
@@ -645,6 +647,22 @@ export const subscribeForecast = (
entity_id,
});
export const getSupportedForecastTypes = (
stateObj: HassEntityBase
): ModernForecastType[] => {
const supported: ModernForecastType[] = [];
if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) {
supported.push("daily");
}
if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_TWICE_DAILY)) {
supported.push("twice_daily");
}
if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_HOURLY)) {
supported.push("hourly");
}
return supported;
};
export const getDefaultForecastType = (stateObj: HassEntityBase) => {
if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) {
return "daily";

View File

@@ -6,7 +6,12 @@ export interface RenderTemplateResult {
listeners: TemplateListeners;
}
interface TemplateListeners {
export interface RenderTemplateError {
error: string;
level: "ERROR" | "WARNING";
}
export interface TemplateListeners {
all: boolean;
domains: string[];
entities: string[];
@@ -18,6 +23,7 @@ export type TemplatePreview = TemplatePreviewState | TemplatePreviewError;
interface TemplatePreviewState {
state: string;
attributes: Record<string, any>;
listeners: TemplateListeners;
}
interface TemplatePreviewError {
@@ -26,19 +32,23 @@ interface TemplatePreviewError {
export const subscribeRenderTemplate = (
conn: Connection,
onChange: (result: RenderTemplateResult) => void,
onChange: (result: RenderTemplateResult | RenderTemplateError) => void,
params: {
template: string;
entity_ids?: string | string[];
variables?: Record<string, unknown>;
timeout?: number;
strict?: boolean;
report_errors?: boolean;
}
): Promise<UnsubscribeFunc> =>
conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
type: "render_template",
...params,
});
conn.subscribeMessage(
(msg: RenderTemplateResult | RenderTemplateError) => onChange(msg),
{
type: "render_template",
...params,
}
);
export const subscribePreviewTemplate = (
hass: HomeAssistant,

View File

@@ -192,7 +192,7 @@ export interface ZWaveJSController {
supported_function_types: number[];
suc_node_id: number;
supports_timers: boolean;
is_heal_network_active: boolean;
is_rebuilding_routes: boolean;
inclusion_state: InclusionState;
nodes: ZWaveJSNodeStatus[];
}
@@ -278,9 +278,9 @@ export interface ZWaveJSRefreshNodeStatusMessage {
stage?: string;
}
export interface ZWaveJSHealNetworkStatusMessage {
export interface ZWaveJSRebuildRoutesStatusMessage {
event: string;
heal_node_status: { [key: number]: string };
rebuild_routes_status: { [key: number]: string };
}
export interface ZWaveJSControllerStatisticsUpdatedMessage {
@@ -651,12 +651,12 @@ export const reinterviewZwaveNode = (
}
);
export const healZwaveNode = (
export const rebuildZwaveNodeRoutes = (
hass: HomeAssistant,
device_id: string
): Promise<boolean> =>
hass.callWS({
type: "zwave_js/heal_node",
type: "zwave_js/rebuild_node_routes",
device_id,
});
@@ -673,33 +673,33 @@ export const removeFailedZwaveNode = (
}
);
export const healZwaveNetwork = (
export const rebuildZwaveNetworkRoutes = (
hass: HomeAssistant,
entry_id: string
): Promise<UnsubscribeFunc> =>
hass.callWS({
type: "zwave_js/begin_healing_network",
type: "zwave_js/begin_rebuilding_routes",
entry_id,
});
export const stopHealZwaveNetwork = (
export const stopRebuildingZwaveNetworkRoutes = (
hass: HomeAssistant,
entry_id: string
): Promise<UnsubscribeFunc> =>
hass.callWS({
type: "zwave_js/stop_healing_network",
type: "zwave_js/stop_rebuilding_routes",
entry_id,
});
export const subscribeHealZwaveNetworkProgress = (
export const subscribeRebuildZwaveNetworkRoutesProgress = (
hass: HomeAssistant,
entry_id: string,
callbackFunction: (message: ZWaveJSHealNetworkStatusMessage) => void
callbackFunction: (message: ZWaveJSRebuildRoutesStatusMessage) => void
): Promise<UnsubscribeFunc> =>
hass.connection.subscribeMessage(
(message: any) => callbackFunction(message),
{
type: "zwave_js/subscribe_heal_network_progress",
type: "zwave_js/subscribe_rebuild_routes_progress",
entry_id,
}
);

View File

@@ -1,137 +0,0 @@
import "@material/mwc-button/mwc-button";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-alert";
import "../../components/ha-dialog";
import { haStyle, haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { AliasesDialogParams } from "./show-dialog-aliases";
import "../../components/ha-aliases-editor";
@customElement("dialog-aliases")
class DialogAliases extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _params?: AliasesDialogParams;
@state() private _aliases!: string[];
@state() private _submitting = false;
public async showDialog(params: AliasesDialogParams): Promise<void> {
this._params = params;
this._error = undefined;
this._aliases =
this._params.aliases?.length > 0
? [...this._params.aliases].sort()
: [""];
await this.updateComplete;
}
public closeDialog(): void {
this._error = "";
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._params) {
return nothing;
}
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${this.hass.localize("ui.dialogs.aliases.heading", {
name: this._params.name,
})}
>
<div>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-aliases-editor
.hass=${this.hass}
.aliases=${this._aliases}
@value-changed=${this._aliasesChanged}
></ha-aliases-editor>
</div>
<mwc-button
slot="secondaryAction"
@click=${this.closeDialog}
.disabled=${this._submitting}
>
${this.hass.localize("ui.common.cancel")}
</mwc-button>
<mwc-button
slot="primaryAction"
@click=${this._updateAliases}
.disabled=${this._submitting}
>
${this.hass.localize("ui.dialogs.aliases.save")}
</mwc-button>
</ha-dialog>
`;
}
private _aliasesChanged(ev: CustomEvent): void {
this._aliases = ev.detail.value;
}
private async _updateAliases(): Promise<void> {
this._submitting = true;
const noEmptyAliases = this._aliases
.map((alias) => alias.trim())
.filter((alias) => alias);
try {
await this._params!.updateAliases(noEmptyAliases);
this.closeDialog();
} catch (err: any) {
this._error =
err.message || this.hass.localize("ui.dialogs.aliases.unknown_error");
} finally {
this._submitting = false;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
.row {
margin-bottom: 8px;
}
ha-textfield {
display: block;
}
ha-icon-button {
display: block;
}
mwc-button {
margin-left: 8px;
}
#alias_input {
margin-top: 8px;
}
.alias {
border: 1px solid var(--divider-color);
border-radius: 4px;
margin-top: 4px;
--mdc-icon-button-size: 24px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-aliases": DialogAliases;
}
}

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