Compare commits

..

178 Commits

Author SHA1 Message Date
Bram Kragten
536fe6a23c Fix safe area 2023-04-06 18:13:18 +02:00
Bram Kragten
d8ddbefcf6 Fix background theme in Lovelace view 2023-04-06 17:31:55 +02:00
Bram Kragten
0364c5e493 Revert "Fix theme background of view" (#16094
Revert "Fix theme background of view (#16083)"

This reverts commit 723b3844ac.
2023-04-06 17:19:12 +02:00
Bram Kragten
d1ad72c6ff Bumped version to 20230406.0 2023-04-06 15:36:29 +02:00
Bram Kragten
723b3844ac Fix theme background of view (#16083) 2023-04-06 15:34:49 +02:00
Bram Kragten
54f8d33b1f Dont show options button for helpers that dont support it (#16089) 2023-04-06 15:32:16 +02:00
Bram Kragten
4c702ac7c2 fix analytics translations in onboarding (#16086) 2023-04-06 12:41:38 +00:00
Bram Kragten
c7b3c4df27 fix integration filter menu disappearing (#16088) 2023-04-06 14:40:02 +02:00
Bram Kragten
56cc4e8d4d Fix tooltip being cutoff in history panel (#16087) 2023-04-06 14:39:53 +02:00
Paul Bottein
0e8c280763 Change graph zone unit (#16082) 2023-04-06 14:14:24 +02:00
Paul Bottein
ff715c6cb7 Don't check for entity id format for card editors (#16081)
Don't check for entity id format for cards
2023-04-06 14:14:13 +02:00
Bram Kragten
81ebdf1448 Bumped version to 20230405.0 2023-04-05 12:29:59 +02:00
Bram Kragten
c640d9edf2 update url of pipline-debug (#16066) 2023-04-05 12:29:13 +02:00
Bram Kragten
6d29b1cfb8 Fix chart mouse over in more info dialog (#16060) 2023-04-05 11:35:14 +02:00
Bram Kragten
e784205e1e fix panel card height (#16059) 2023-04-05 11:35:06 +02:00
Bram Kragten
1596578f96 Fix automation editor (#16057) 2023-04-05 10:20:21 +02:00
Bram Kragten
28304bb1dc Make deprecation warning instead of error (#16055) 2023-04-04 15:01:33 +02:00
Paul Bottein
32bbc9421b Translate theme settings (#16053) 2023-04-04 14:04:05 +02:00
Paul Bottein
6e35f841cc Fix more info vacuum status (#16051) 2023-04-04 13:36:45 +02:00
Paul Bottein
99e6547807 Fix fan speed for new more info and tile feature (#16050) 2023-04-04 13:36:33 +02:00
renovate[bot]
9764a0f23f Update dependency @webcomponents/webcomponentsjs to v2.8.0 (#16026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 00:35:27 -04:00
renovate[bot]
12918580ac Update dependency webpack-dev-server to v4.13.2 (#16049)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 03:48:54 +00:00
renovate[bot]
61a7652ae1 Update dependency @webcomponents/scoped-custom-element-registry to v0.0.9 (#16025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 23:29:49 -04:00
Paulus Schoutsen
c885d08a32 Merge runPipeline code and allow picking language (#16018)
* Merge runPipeline code and allow picking language

* Add download button

* Allow to continue audio conversations once stopped
2023-04-03 16:43:59 -04:00
Paul Bottein
6068d5e5cd Use state as mode for alarm mode tile feature (#16045
* Use state as mode for alarm mode tile feature

* reorder modes
2023-04-03 20:56:54 +02:00
Bram Kragten
890be2c177 Fix drawer in RTL (#16039
* Fix drawer in RTL

* format

* make it update when lang changes

* Update ha-drawer.ts

* Update ha-drawer.ts
2023-04-03 20:55:38 +02:00
Bram Kragten
423709dd23 Add deprecation warning Polymer (#16044) 2023-04-03 20:37:49 +02:00
Bram Kragten
4b73baa098 Bumped version to 20230403.0 2023-04-03 20:18:25 +02:00
Bram Kragten
dba16edabc make supported feature selector any instead of all (#16040)
make filterSupportedFeature any instead of all
2023-04-03 13:53:37 -04:00
Bram Kragten
975f371ba8 Add channel to thread network info (#16041) 2023-04-03 13:53:12 -04:00
Bram Kragten
36b2a1bca3 Add swipe to close action to drawer in modal mode (#16042) 2023-04-03 13:52:54 -04:00
Bram Kragten
fdf36adc3c Fix elements below scrollbar in Safari (#16037) 2023-04-03 19:44:40 +02:00
Paul Bottein
b506791535 Fix history scrollbar (#16036) 2023-04-03 18:41:51 +02:00
renovate[bot]
206574eb9f Update babel monorepo to v7.21.4 (#16029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 12:10:23 -04:00
Bram Kragten
19ab29d130 Handle safe areas (#16032) 2023-04-03 15:52:02 +02:00
Paul Bottein
f61f0e4e52 Fix for long translation in alarm more info in ios (#16034) 2023-04-03 13:11:57 +00:00
Bram Kragten
a32e6a9ac9 Don't open sidebar when a dialog is open (#16030) 2023-04-03 14:59:42 +02:00
Bram Kragten
d7b8823234 Prevent document scroll when modal drawer is open (#16033) 2023-04-03 14:59:31 +02:00
Steve Repsher
d9b0d5765a Add automatic retries to translation fetches (#16020) 2023-04-02 21:03:18 +02:00
Joakim Sørensen
6eb3fb1076 Use the name and size from the new disks property (#15954)
* Use the name and size from the new disks property

* Move SN down to secondary
2023-04-02 08:07:08 -04:00
Bram Kragten
ddfe02eb70 Bumped version to 20230401.0 2023-04-01 18:20:48 +02:00
Bram Kragten
acaaf25500 Use ha-drawer instead of mwc-drawer (#16013) 2023-04-01 16:18:52 +00:00
renovate[bot]
c6c3e63101 Update dependency @web/dev-server to v0.1.37 (#16008) 2023-04-01 10:31:47 -04:00
renovate[bot]
e0fe4631f9 Update dependency eslint to v8.37.0 (#16007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 01:56:46 -04:00
Paulus Schoutsen
232f70d44c Allow having conversations (#15961)
* Allow having conversations

* Add timeout to run-start data

* willUpdate
2023-03-31 15:44:32 -04:00
Bram Kragten
273904a6eb Add support for supported_features for entity selector (#16003) 2023-03-31 15:07:41 -04:00
Steve Repsher
91caffc4e1 Improve bundle chunk hashes and names (#15991) 2023-03-31 12:49:25 -04:00
Bram Kragten
abcb904def Bumped version to 20230331.0 2023-03-31 16:37:33 +02:00
Bram Kragten
36c5d70597 Add sidebar actions to external bus (#15999) 2023-03-31 16:36:11 +02:00
Paul Bottein
b0b7998757 Fix for long translation in alarm more info (#16000) 2023-03-31 14:24:03 +00:00
Paul Bottein
33ec1e15a9 Fix labels on new more info (#15983) 2023-03-31 15:57:35 +02:00
Paul Bottein
d97ddcd31a Catch alarm control panel errors (#15998) 2023-03-31 15:51:44 +02:00
Paul Bottein
73c286a493 Fix ha header bar height (#15996)
* Fix ha header bar height in more info

* Fix ha header bar height in more places
2023-03-31 15:44:22 +02:00
Franck Nijhof
3e954eef02 Extend get_states (#15985) 2023-03-31 15:29:24 +02:00
Paul Bottein
a94b211d3e Fix ha-settings-row overflow (#15993) 2023-03-31 13:02:36 +02:00
renovate[bot]
1293e5f61f Update typescript-eslint monorepo to v5.57.0 (#15986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-30 15:35:02 -04:00
Bram Kragten
287b0b9235 Bumped version to 20230330.0 2023-03-30 16:58:32 +02:00
Bram Kragten
3d6743ae3e Align header paddings (#15981) 2023-03-30 16:57:25 +02:00
Bram Kragten
5193f2c6a4 Fix header when sidebar always hidden (#15980) 2023-03-30 16:46:31 +02:00
Paul Bottein
e6772e8b89 Fix fan more info state display (#15979) 2023-03-30 16:20:30 +02:00
Bram Kragten
dcac853b71 Improve graph tooltips (#15887
* Improve graph tooltips

* Use xy for state charts

* intersect, bigger hitRadius

* improve energy

* fix position tooltips
2023-03-30 16:12:26 +02:00
Bram Kragten
0df096d68b Fix overflow, change position of action-handler (#15978) 2023-03-30 16:10:54 +02:00
Bram Kragten
ef10cc77f7 Fix lovelace background, tweak headers (#15977) 2023-03-30 16:10:43 +02:00
Paul Bottein
e52b2c49a6 Fix mouse event for remove icon in ha file upload (#15976) 2023-03-30 11:59:50 +00:00
Paul Bottein
78cc75c57c Fix cover more info state display (#15974) 2023-03-30 09:55:13 +00:00
Bram Kragten
2b38a1ce33 Fix dialogs under sidebar, headers (#15973) 2023-03-30 11:46:34 +02:00
Steve Repsher
1f1898fa46 Make module types explicit and convert some to ESM (#15964) 2023-03-30 11:23:43 +02:00
renovate[bot]
fcc95825e3 Update dependency sinon to v15.0.3 (#15972) 2023-03-29 21:03:33 -04:00
Bram Kragten
bdfdab439a Merge branch 'master' into dev 2023-03-29 17:54:53 +02:00
Bram Kragten
8c59537032 Bumped version to 20230329.0 2023-03-29 17:48:14 +02:00
Bram Kragten
f5a4affdec Use mwc-drawer (#10335
* Use mwc-drawer

* Update home-assistant-main.ts

* Implement top-app-bar

* Update home-assistant-main.ts

* update hui-root

* WIP

tabs don't work yet

* migrate most panels

* fixed

* notifications-drawer

* developer tools

* lovelace

* Update hui-root.ts

* Update hui-root.ts

* Update notification-drawer.ts

* fix dev tools

* Update ha-panel-developer-tools.ts
2023-03-29 17:46:34 +02:00
Bram Kragten
f19fdeacba bump @lit-labs/context, fix bug in button card + optimise (#15968) 2023-03-29 12:45:52 +00:00
Paul Bottein
e0f7544d2f Alarm mode tile feature (#15967) 2023-03-29 12:27:39 +02:00
Paul Bottein
4d2d7cd125 Use nothing in more lit template (#15966)
* Use nothing in more lit template

* Use nothing in more lit template
2023-03-29 12:20:25 +02:00
Bram Kragten
0f5320c6fb Drop polymer from authorize and onboarding entrypoint (#15760)
* Drop polymer from authorize and onboarding entrypoint

* Update ha-icon.ts

* dedupe
2023-03-29 12:17:42 +02:00
karwosts
4d52913a01 Add missing import for duration selector (#15960) 2023-03-28 20:25:06 -04:00
karwosts
0f97a76428 Fix map card error rendering (#15910) 2023-03-28 16:15:56 +00:00
Paul Bottein
f2cf598f98 Fan speed tile feature (#15958)
* Move fan speed rules outside fan more info

* Add fan speed tile feature

* Improve select style
2023-03-28 17:59:07 +02:00
Franck Nijhof
a6f9482bf6 Improve/extend description of conditions (#15943) 2023-03-28 16:43:00 +02:00
Paul Bottein
48c74c8660 Add more info alarm control panel (#15893)
* Add more info alarm control panel

* Improve buttons sizes

* Add triggered, arming and pending state

* Add keypad

* Improve alarm code dialog

* Fix code condition

* Clean code

* Fix mode selection with code

* Use nothing
2023-03-28 16:31:25 +02:00
Paul Bottein
3a700aebcc Improve padding to avoid input number overflow (#15888) 2023-03-28 14:40:02 +02:00
Paul Bottein
cd6aac85d2 Update oscillating icon (#15948) 2023-03-28 13:28:52 +02:00
renovate[bot]
77b227a7d1 Update vaadinWebComponents monorepo to v23.3.9 (#15951) 2023-03-27 21:11:59 -04:00
Stefan Agner
b785fedef2 Add-on store: Use more formal wording (#15952)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2023-03-27 22:18:18 +02:00
renovate[bot]
66a202be7e Update dependency @lezer/highlight to v1.1.4 (#15949) 2023-03-27 09:31:45 -04:00
renovate[bot]
0ad02013ba Update dependency prettier to v2.8.7 (#15950) 2023-03-27 09:30:40 -04:00
Paul Bottein
1b2eaedba0 Don't wait for slider release to update color temp (#15914) 2023-03-27 10:34:05 +02:00
Paul Bottein
8ea350a488 Add cover new more info (#15694)
* Add cover new  more info

* Improve tilt cover control

* Improve controls

* Better handle group

* Refactor toggle component

* Use deep purple for cover and adjust deep purple color

* Update purple color

* Feedbacks

* Change color

* Improve tilt backgroud constrast
2023-03-27 10:11:28 +02:00
dependabot[bot]
8ff56bd8f5 Bump actions/stale from 7.0.0 to 8.0.0 (#15945)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 09:41:03 +02:00
dependabot[bot]
dd7ec07f29 Bump actions/checkout from 3.4.0 to 3.5.0 (#15946)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 09:29:50 +02:00
Paulus Schoutsen
520f489830 Pipeline updates (#15926)
* Allow using TTS

* Allow streaming audio from frontens to STT

* Improve stop recording

* Even better stop
2023-03-26 22:42:08 -04:00
renovate[bot]
395358b192 Update dependency vis-network to v9.1.6 (#15944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-26 21:42:10 -04:00
Raman Gupta
a19ff5aef5 Add milliseconds support for duration sensors (#15895) 2023-03-26 23:52:18 +02:00
renovate[bot]
34f8b48fbe Update dependency glob to v9.3.2 (#15938) 2023-03-25 19:18:59 +00:00
renovate[bot]
01f8b4e1c4 Update dependency marked to v4.3.0 (#15935) 2023-03-25 19:02:09 +00:00
renovate[bot]
9e1cdf8215 Update dependency vis-data to v7.1.6 (#15929) 2023-03-25 14:41:15 -04:00
renovate[bot]
551127b844 Update dependency vis-network to v9.1.5 (#15925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 14:34:05 -04:00
renovate[bot]
afe42629b2 Update dependency systemjs to v6.14.1 (#15924)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 14:32:17 -04:00
renovate[bot]
848d12e6fe Update dependency intl-messageformat to v10.3.3 (#15920) 2023-03-24 08:38:44 -04:00
renovate[bot]
65bd373af4 Update dependency glob to v9.3.1 (#15919) 2023-03-24 08:37:01 -04:00
renovate[bot]
91c099632d Update dependency prettier to v2.8.6 (#15921) 2023-03-24 08:35:01 -04:00
Sven Serlier
9053bc7b78 Update URLs (#15915)
* Update URL

* Update URL
2023-03-24 11:27:45 +01:00
renovate[bot]
9c4b0259a8 Update typescript-eslint monorepo to v5.56.0 (#15917) 2023-03-23 19:14:28 -04:00
renovate[bot]
7930f3879d Update dependency eslint-config-prettier to v8.8.0 (#15916) 2023-03-23 19:09:19 -04:00
Paulus Schoutsen
d9dbb69e62 Update voice debug for new API (#15913)
* Update voice debug for new API

* Update imports

* Remove wrong key

* Some HTML formatting
2023-03-23 14:44:23 -04:00
Bram Kragten
74cfccaac7 Add context providers and transform decorator (#15902) 2023-03-23 18:31:01 +01:00
Bram Kragten
4ba7e5cf0f Fix css minifying (#15912) 2023-03-23 18:28:23 +01:00
renovate[bot]
fc76d8f1cf Update dependency @polymer/polymer to v3.5.1 (#15720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-23 16:18:31 +00:00
renovate[bot]
3eb07e9bc3 Update dependency fs-extra to v11.1.1 (#15911)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-23 16:58:04 +01:00
karwosts
4e6ed61e2b Add default placeholders for various number fields in cards (#15903) 2023-03-23 15:15:38 +01:00
renovate[bot]
ca6d1544d1 Update dependency @mdi/js to v7.2.96 (#15904)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-23 13:00:01 +00:00
renovate[bot]
81e9bc894b Update CodeMirror (#15908)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-23 12:55:38 +00:00
renovate[bot]
173d13ae66 Update dependency @mdi/svg to v7.2.96 (#15905)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-23 08:42:32 -04:00
renovate[bot]
886ae791bc Update dependency prettier to v2.8.5 (#15907)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-23 08:38:30 -04:00
renovate[bot]
50b7e72688 Lock file maintenance (#15865)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-03-22 17:24:36 +00:00
karwosts
d07ae1cf48 Fix statistics graph days_to_show can't be erased (#15892) 2023-03-22 17:56:04 +01:00
karwosts
da2de3c7d2 Fix problems in map card when hours_to_show is 0 (#15900) 2023-03-22 17:55:30 +01:00
karwosts
7c62b08fdd Allow map card to render passive zones (#15901) 2023-03-22 17:51:47 +01:00
renovate[bot]
4f5fca7c60 Update dependency intl-messageformat to v10.3.2 (#15899)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-22 10:03:42 -04:00
Paul Bottein
45c153d374 Add fan new more info (#15843)
* Add more info fan

* Change icon

* Use backend translations

* Fix computeAttributeValueDisplay

* Clean code

* Clean code

* Fix some styles

* Improve ha-select rounded style

* Use button instead of select

* Show fan speed percentage
2023-03-22 13:20:14 +01:00
Franck Nijhof
cd2996734c Compute (attribute) states in automation trigger descriptions (#15848) 2023-03-22 11:44:43 +01:00
Steve Repsher
4abc2a65cb Migrate to html-minifier-terser and improve some minification (#15864) 2023-03-22 11:42:16 +01:00
karwosts
89decd2f31 Force app-datepicker calendar to render in ltr (#15894) 2023-03-22 11:24:16 +01:00
renovate[bot]
04a16812d3 Update dependency webpack-dev-server to v4.13.1 (#15896) 2023-03-21 19:16:59 -04:00
renovate[bot]
cadbc501e2 Update dependency webpack-dev-server to v4.13.0 (#15891)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 22:27:15 +00:00
renovate[bot]
0d95d856c1 Update dependency hls.js to v1.3.5 (#15890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-20 18:11:37 -04:00
Paul Bottein
d272783258 Mock history stream for demo (#15886)
* Mock history stream for demo

* Fix type error
2023-03-20 20:39:01 +01:00
Paul Bottein
65d3af6fd6 Add white mode to more info light (#15774) 2023-03-20 20:38:48 +01:00
Bram Kragten
24c3ddb96b Allow reset of otbr network, thread panel fixes (#15815) 2023-03-20 20:06:40 +01:00
dependabot[bot]
c9d709152a Bump actions/checkout from 3.3.0 to 3.4.0 (#15883)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 09:07:14 +01:00
renovate[bot]
d5bc892bae Update Yarn to v3.5.0 (#15879) 2023-03-19 21:40:00 +00:00
Paulus Schoutsen
a5ea7b33b0 Add duration for stage (#15857)
* Add duration for stage

* formatNumber
2023-03-19 05:16:31 -04:00
renovate[bot]
c29568d164 Update dependency @open-wc/dev-server-hmr to v0.1.4 (#15859)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 19:53:39 +00:00
renovate[bot]
57e1769c06 Update dependency @web/dev-server-rollup to v0.4.0 (#15852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 19:39:16 +00:00
renovate[bot]
abd2070011 Update dependency eslint-plugin-lit-a11y to v2.4.0 (#15862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 19:34:56 +00:00
renovate[bot]
840450d9e5 Update dependency @web/dev-server to v0.1.36 (#15851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 15:22:27 -04:00
renovate[bot]
178163cbc7 Update dependency @types/sortablejs to v1.15.1 (#15858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 15:19:17 -04:00
renovate[bot]
dcca02477a Update dependency webpack-dev-server to v4.12.0 (#15855) 2023-03-17 19:46:43 -04:00
renovate[bot]
c06990f309 Update dependency @types/leaflet to v1.9.3 (#15854) 2023-03-17 19:42:41 -04:00
Paulus Schoutsen
1c9e3915e8 Improve pipeline debug styling (#15849) 2023-03-17 13:04:14 -04:00
renovate[bot]
30b8dc258a Update dependency glob to v9.3.0 (#15847)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 12:35:09 -04:00
renovate[bot]
f31043cfdc Update dependency @babel/core to v7.21.3 (#15850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 12:34:21 -04:00
Paul Bottein
c0c83d3721 Add analytics translations (#15821)
* Add analytics translations

* Move translations to panels

* Fix learn more link
2023-03-17 16:26:53 +01:00
Franck Nijhof
dd08909fef Show unknown attribute state as Unknown instead of a dash (#15846) 2023-03-17 16:21:23 +01:00
karwosts
952028a7be Only pick entities with location in map card editor (#15839) 2023-03-17 16:19:43 +01:00
Jesse Moody
b0f3006c4b Update rollup links (#15835) 2023-03-17 16:17:55 +01:00
Paul Bottein
27098c5f3f Create control select component (#15819) 2023-03-17 16:16:51 +01:00
Franck Nijhof
fec061f5d1 Extend attribute to hide from (numeric) state triggers (#15836) 2023-03-17 16:15:34 +01:00
Paulus Schoutsen
db08c5029b Voice assistant (#15841)
* Add basic debug panel for voice assistant pipelines

* Add more info to start event

* Copy on change

* Use latest data model

* Apply suggestions from code review

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>

* Fix CSS

* Also use ha-button

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-03-17 10:38:08 -04:00
renovate[bot]
c5be2acd46 Update dependency glob to v9 (#15726)
* Update dependency glob to v9

* Adjust to new API

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
2023-03-17 13:28:51 +00:00
Franck Nijhof
0232c11bc2 Translate entity attribute names & attribute states (#15822) 2023-03-17 11:43:59 +01:00
renovate[bot]
dfd7acd713 Update typescript-eslint monorepo to v5.55.0 (#15837) 2023-03-17 00:35:13 -04:00
Jesse Moody
24f1677809 Fix can not typo (#15830) 2023-03-16 16:18:31 +01:00
Steve Repsher
aeb7f8ff36 Fix webpack source maps (#15663) 2023-03-16 14:38:55 +01:00
Paul Bottein
db62e9f922 Add constant selector (#15783) 2023-03-16 14:22:19 +01:00
renovate[bot]
1861547d9b Update dependency core-js to v3.29.1 (#15833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 13:54:45 +01:00
renovate[bot]
43684795a4 Update dependency @types/leaflet to v1.9.2 (#15831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-16 13:52:23 +01:00
Franck Nijhof
0158d7e3e5 Adjust backend translations for entity components (#15820)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-03-16 13:51:38 +01:00
Erik Montnemery
77dcace95e Drop unused Google entity settings from cloud (#15832) 2023-03-16 11:08:57 +01:00
renovate[bot]
60d106d9c3 Update dependency sinon to v15.0.2 (#15829) 2023-03-15 20:24:42 -04:00
karwosts
020aab0584 Align charts when show_names: false (#15782)
* Align charts when show_names: false

* fixes per code review
2023-03-15 12:29:45 +01:00
renovate[bot]
27808c9853 Update dependency eslint to v8.36.0 (#15816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-14 09:02:43 -04:00
renovate[bot]
1a9b9da0db Update dependency @codemirror/commands to v6.2.2 (#15814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-13 21:23:13 +01:00
renovate[bot]
fe19963ea9 Update dependency lint-staged to v13.2.0 (#15810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-13 20:47:02 +01:00
Paul Bottein
3afe1f83c7 Add back button to history and logbook when coming from more info dialog (#15811) 2023-03-13 20:13:22 +01:00
Paul Bottein
6cb4b5f429 Fix displayed month when opening date range picker (#15808) 2023-03-13 20:12:28 +01:00
Paul Bottein
7b676cbd91 Display cover position in tile card when opening and closing (#15809) 2023-03-13 17:33:14 +01:00
Paul Bottein
bf1cabca6e Fix discover blueprint tips not displayed in automation editor (#15812) 2023-03-13 17:19:21 +01:00
Paul Bottein
effb4b9f7a Force using history for all entity domains on map card (#15804) 2023-03-13 11:52:27 +01:00
Bram Kragten
1bc7bb0169 Fix suffix in zwave config panel, send config on change instead of input (#15779) 2023-03-13 11:47:44 +01:00
karwosts
3abc5c42d0 Fix long entity names overflowing energy panel settings (#15786) 2023-03-13 11:06:30 +01:00
Bram Kragten
cc011a4d47 Bump style-mod (#15805) 2023-03-13 11:05:08 +01:00
renovate[bot]
ca0338436c Update dependency @material/web to v1.0.0-pre.4 (#15796)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-13 11:03:45 +01:00
renovate[bot]
84d31cb9d5 Update dependency terser-webpack-plugin to v5.3.7 (#15797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-12 14:33:54 -04:00
renovate[bot]
69264b3448 Update dependency @codemirror/view to v6.9.2 (#15793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-11 15:24:17 -05:00
renovate[bot]
01a098e5aa Update dependency lint-staged to v13.1.4 (#15790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-10 13:47:32 -05:00
renovate[bot]
11b1e056dc Update dependency rollup-plugin-terser to v7 (#15767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-09 16:17:29 -05:00
renovate[bot]
4e99e32ca2 Update typescript-eslint monorepo to v5.54.1 (#15789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-09 16:03:43 -05:00
359 changed files with 10957 additions and 5633 deletions

View File

@@ -20,7 +20,7 @@
"settings": {
"import/resolver": {
"webpack": {
"config": "./webpack.config.js"
"config": "./webpack.config.cjs"
}
}
},

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.3.0
uses: actions/checkout@v3.5.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.3.0
uses: actions/checkout@v3.5.0
with:
ref: master

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.0
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
@@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.0
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
@@ -66,7 +66,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.0
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
@@ -84,7 +84,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.0
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.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

@@ -23,7 +23,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.3.0
uses: actions/checkout@v3.5.0
with:
ref: dev
@@ -59,7 +59,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.3.0
uses: actions/checkout@v3.5.0
with:
ref: master

View File

@@ -17,7 +17,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.3.0
uses: actions/checkout@v3.5.0
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0

View File

@@ -22,7 +22,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.3.0
uses: actions/checkout@v3.5.0
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0

View File

@@ -21,7 +21,7 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
@@ -43,7 +43,7 @@ jobs:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Bump version
run: script/version_bump.js nightly
run: script/version_bump.cjs nightly
- name: Build nightly Python wheels
run: |

View File

@@ -24,7 +24,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.0
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.4.1.cjs
yarnPath: .yarn/releases/yarn-3.5.0.cjs

View File

@@ -1,6 +1,15 @@
const path = require("path");
const env = require("./env.js");
const paths = require("./paths.js");
const env = require("./env.cjs");
const paths = require("./paths.cjs");
// GitHub base URL to use for production source maps
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
module.exports.sourceMapURL = () => {
const ref = env.version().endsWith("dev")
? process.env.GITHUB_SHA || "dev"
: env.version();
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}`;
};
// Files from NPM Packages that should not be imported
// eslint-disable-next-line unused-imports/no-unused-vars
@@ -53,7 +62,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
...defineOverlay,
});
const htmlMinifierOptions = {
module.exports.htmlMinifierOptions = {
caseSensitive: true,
collapseWhitespace: true,
conservativeCollapse: true,
@@ -61,17 +70,18 @@ const htmlMinifierOptions = {
removeComments: true,
removeRedundantAttributes: true,
minifyCSS: {
level: 0,
compatibility: "*,-properties.zeroUnits",
},
};
module.exports.terserOptions = (latestBuild) => ({
module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
safari10: !latestBuild,
ecma: latestBuild ? undefined : 5,
output: { comments: false },
format: { comments: false },
sourceMap: !isTestBuild,
});
module.exports.babelOptions = ({ latestBuild, isProdBuild }) => ({
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
babelrc: false,
compact: false,
presets: [
@@ -89,7 +99,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild }) => ({
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.js"
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
),
{
modules: ["@mdi/js"],
@@ -125,7 +135,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild }) => ({
"@polymer/polymer/lib/utils/html-tag": ["html"],
},
strictCSS: true,
htmlMinifier: htmlMinifierOptions,
htmlMinifier: module.exports.htmlMinifierOptions,
failOnError: true, // we can turn this off in case of false positives
},
],
@@ -135,8 +145,11 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild }) => ({
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
sourceMaps: !isTestBuild,
});
const nameSuffix = (latestBuild) => (latestBuild ? "-latest" : "-es5");
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
@@ -159,14 +172,17 @@ BundleConfig {
latestBuild: boolean,
// If we're doing a stats build (create nice chunk names)
isStatsBuild: boolean,
// If it's just a test build in CI, skip time on source map generation
isTestBuild: boolean,
// Names of entrypoints that should not be hashed
dontHash: Set<string>
}
*/
module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild, isWDS }) {
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
return {
name: "app" + nameSuffix(latestBuild),
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
app: "./src/entrypoints/app.ts",
@@ -180,12 +196,14 @@ module.exports.config = {
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isWDS,
};
},
demo({ isProdBuild, latestBuild, isStatsBuild }) {
return {
name: "demo" + nameSuffix(latestBuild),
entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
},
@@ -215,6 +233,7 @@ module.exports.config = {
}
return {
name: "cast" + nameSuffix(latestBuild),
entry,
outputPath: outputPath(paths.cast_output_root, latestBuild),
publicPath: publicPath(latestBuild),
@@ -226,8 +245,9 @@ module.exports.config = {
};
},
hassio({ isProdBuild, latestBuild }) {
hassio({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) {
return {
name: "supervisor" + nameSuffix(latestBuild),
entry: {
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
},
@@ -235,6 +255,8 @@ module.exports.config = {
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isHassioBuild: true,
defineOverlay: {
__SUPERVISOR__: true,
@@ -244,6 +266,7 @@ module.exports.config = {
gallery({ isProdBuild, latestBuild }) {
return {
name: "gallery" + nameSuffix(latestBuild),
entry: {
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
},

View File

@@ -1,6 +1,6 @@
const fs = require("fs");
const path = require("path");
const paths = require("./paths.js");
const paths = require("./paths.cjs");
module.exports = {
useRollup() {
@@ -17,7 +17,7 @@ module.exports = {
isStatsBuild() {
return process.env.STATS === "1";
},
isTest() {
isTestBuild() {
return process.env.IS_TEST === "true";
},
isNetlify() {

View File

@@ -1,19 +1,18 @@
// Run HA develop mode
const gulp = require("gulp");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./locale-data.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./compress.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
require("./wds.js");
const env = require("../env.cjs");
require("./clean.cjs");
require("./translations.cjs");
require("./locale-data.cjs");
require("./gen-icons-json.cjs");
require("./gather-static.cjs");
require("./compress.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
require("./wds.cjs");
gulp.task(
"develop-app",
@@ -50,7 +49,7 @@ gulp.task(
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]),
...(env.isTestBuild() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",

View File

@@ -1,14 +1,13 @@
const gulp = require("gulp");
const env = require("../env.cjs");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
require("./clean.cjs");
require("./translations.cjs");
require("./gather-static.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
gulp.task(
"develop-cast",

View File

@@ -1,7 +1,7 @@
const del = import("del");
const gulp = require("gulp");
const paths = require("../paths");
require("./translations");
const paths = require("../paths.cjs");
require("./translations.cjs");
gulp.task(
"clean",

View File

@@ -4,7 +4,7 @@ const gulp = require("gulp");
const zopfli = require("gulp-zopfli-green");
const merge = require("merge-stream");
const path = require("path");
const paths = require("../paths");
const paths = require("../paths.cjs");
const zopfliOptions = { threshold: 150 };

View File

@@ -1,16 +1,15 @@
// Run demo develop mode
const gulp = require("gulp");
const env = require("../env.cjs");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
require("./clean.cjs");
require("./translations.cjs");
require("./gen-icons-json.cjs");
require("./gather-static.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
gulp.task(
"develop-demo",

View File

@@ -3,9 +3,10 @@ const gulp = require("gulp");
const fs = require("fs-extra");
const path = require("path");
const template = require("lodash.template");
const minify = require("html-minifier").minify;
const paths = require("../paths.js");
const env = require("../env.js");
const { minify } = require("html-minifier-terser");
const paths = require("../paths.cjs");
const env = require("../env.cjs");
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
const templatePath = (tpl) =>
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
@@ -39,10 +40,12 @@ const renderGalleryTemplate = (pth, data = {}) =>
const minifyHtml = (content) =>
minify(content, {
collapseWhitespace: true,
minifyJS: true,
minifyCSS: true,
removeComments: true,
...htmlMinifierOptions,
conservativeCollapse: false,
minifyJS: terserOptions({
latestBuild: false, // Shared scripts should be ES5
isTestBuild: true, // Don't need source maps
}),
});
const PAGES = ["onboarding", "authorize"];
@@ -63,7 +66,7 @@ gulp.task("gen-pages-dev", (done) => {
done();
});
gulp.task("gen-pages-prod", (done) => {
gulp.task("gen-pages-prod", async () => {
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
@@ -73,19 +76,23 @@ gulp.task("gen-pages-prod", (done) => {
"manifest.json"
));
const minifiedHTML = [];
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
es5PageJS: es5Manifest[`${page}.js`],
});
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
minifyHtml(content)
minifiedHTML.push(
minifyHtml(content).then((minified) =>
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
minified
)
)
);
}
done();
await Promise.all(minifiedHTML);
});
gulp.task("gen-index-app-dev", (done) => {
@@ -118,7 +125,7 @@ gulp.task("gen-index-app-dev", (done) => {
done();
});
gulp.task("gen-index-app-prod", (done) => {
gulp.task("gen-index-app-prod", async () => {
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
@@ -136,13 +143,15 @@ gulp.task("gen-index-app-prod", (done) => {
es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
});
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
const minified = (await minifyHtml(content)).replace(
/#THEMEC/g,
"{{ theme_color }}"
);
fs.outputFileSync(
path.resolve(paths.app_output_root, "index.html"),
minified
);
done();
});
gulp.task("gen-index-cast-dev", (done) => {
@@ -244,7 +253,7 @@ gulp.task("gen-index-demo-dev", (done) => {
done();
});
gulp.task("gen-index-demo-prod", (done) => {
gulp.task("gen-index-demo-prod", async () => {
const latestManifest = require(path.resolve(
paths.demo_output_latest,
"manifest.json"
@@ -258,13 +267,12 @@ gulp.task("gen-index-demo-prod", (done) => {
es5DemoJS: es5Manifest["main.js"],
});
const minified = minifyHtml(content);
const minified = await minifyHtml(content);
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
minified
);
done();
});
gulp.task("gen-index-gallery-dev", (done) => {
@@ -279,7 +287,7 @@ gulp.task("gen-index-gallery-dev", (done) => {
done();
});
gulp.task("gen-index-gallery-prod", (done) => {
gulp.task("gen-index-gallery-prod", async () => {
const latestManifest = require(path.resolve(
paths.gallery_output_latest,
"manifest.json"
@@ -287,13 +295,12 @@ gulp.task("gen-index-gallery-prod", (done) => {
const content = renderGalleryTemplate("index", {
latestGalleryJS: latestManifest["entrypoint.js"],
});
const minified = minifyHtml(content);
const minified = await minifyHtml(content);
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
minified
);
done();
});
gulp.task("gen-index-hassio-dev", async () => {

View File

@@ -8,6 +8,7 @@ const gulp = require("gulp");
const jszip = require("jszip");
const tar = require("tar");
const { Octokit } = require("@octokit/rest");
const { retry } = require("@octokit/plugin-retry");
const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device");
const MAX_AGE = 24; // hours
@@ -95,7 +96,7 @@ gulp.task("fetch-nightly-translations", async function () {
// Authenticate with token and request workflow runs from GitHub
console.log("Fetching new translations...");
const octokit = new Octokit({
const octokit = new (Octokit.plugin(retry))({
userAgent: "Fetch Nightly Translations",
auth: tokenAuth.token,
});

View File

@@ -6,21 +6,21 @@ const { marked } = require("marked");
const glob = require("glob");
const yaml = require("js-yaml");
const env = require("../env");
const paths = require("../paths");
const env = require("../env.cjs");
const paths = require("../paths.cjs");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
require("./clean.cjs");
require("./translations.cjs");
require("./gen-icons-json.cjs");
require("./gather-static.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
gulp.task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
const files = glob.sync(path.resolve(pageDir, "**/*"));
const files = await glob(path.resolve(pageDir, "**/*"));
const galleryBuild = path.resolve(paths.gallery_dir, "build");
fs.mkdirSync(galleryBuild, { recursive: true });
@@ -89,9 +89,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
// Generate sidebar
const sidebarPath = path.resolve(paths.gallery_dir, "sidebar.js");
// To make watch work during development
delete require.cache[sidebarPath];
const sidebar = require(sidebarPath);
const sidebar = (await import(sidebarPath)).default;
const pagesToProcess = {};
for (const key of processed) {

View File

@@ -3,7 +3,7 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs-extra");
const paths = require("../paths");
const paths = require("../paths.cjs");
const npmPath = (...parts) =>
path.resolve(paths.polymer_dir, "node_modules", ...parts);

View File

@@ -134,11 +134,11 @@ gulp.task("gen-icons-json", (done) => {
});
const file = fs.readFileSync(PACKAGE_PATH, { encoding });
const package = JSON.parse(file);
const packageMeta = JSON.parse(file);
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
JSON.stringify({ version: package.version, parts })
JSON.stringify({ version: packageMeta.version, parts })
);
fs.writeFileSync(

View File

@@ -1,15 +1,13 @@
const gulp = require("gulp");
const env = require("../env");
require("./clean.js");
require("./gen-icons-json.js");
require("./webpack.js");
require("./compress.js");
require("./rollup.js");
require("./gather-static.js");
require("./translations.js");
require("./gen-icons-json.js");
const env = require("../env.cjs");
require("./clean.cjs");
require("./compress.cjs");
require("./entry-html.cjs");
require("./gather-static.cjs");
require("./gen-icons-json.cjs");
require("./rollup.cjs");
require("./translations.cjs");
require("./webpack.cjs");
gulp.task(
"develop-hassio",
@@ -43,6 +41,6 @@ gulp.task(
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod",
...// Don't compress running tests
(env.isTest() ? [] : ["compress-hassio"])
(env.isTestBuild() ? [] : ["compress-hassio"])
)
);

View File

@@ -2,7 +2,7 @@ const del = import("del");
const path = require("path");
const gulp = require("gulp");
const fs = require("fs");
const paths = require("../paths");
const paths = require("../paths.cjs");
const outDir = "build/locale-data";

View File

@@ -6,8 +6,8 @@ const handler = require("serve-handler");
const http = require("http");
const log = require("fancy-log");
const open = require("open");
const rollupConfig = require("../rollup");
const paths = require("../paths");
const rollupConfig = require("../rollup.cjs");
const paths = require("../paths.cjs");
const bothBuilds = (createConfigFunc, params) =>
gulp.series(
@@ -46,7 +46,7 @@ function createServer(serveOptions) {
);
}
function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
function watchRollup(createConfig, extraWatchSrc = [], serveOptions = null) {
const { inputOptions, outputOptions } = createConfig({
isProdBuild: false,
latestBuild: true,

View File

@@ -5,7 +5,7 @@ const path = require("path");
const fs = require("fs-extra");
const workboxBuild = require("workbox-build");
const sourceMapUrl = require("source-map-url");
const paths = require("../paths.js");
const paths = require("../paths.cjs");
const swDest = path.resolve(paths.app_output_root, "service_worker.js");

View File

@@ -9,11 +9,11 @@ const flatmap = require("gulp-flatmap");
const merge = require("gulp-merge-json");
const rename = require("gulp-rename");
const transform = require("gulp-json-transform");
const { mapFiles } = require("../util");
const env = require("../env");
const paths = require("../paths");
const { mapFiles } = require("../util.cjs");
const env = require("../env.cjs");
const paths = require("../paths.cjs");
require("./fetch-nightly-translations");
require("./fetch-nightly-translations.cjs");
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";

View File

@@ -5,14 +5,15 @@ const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const log = require("fancy-log");
const path = require("path");
const paths = require("../paths");
const env = require("../env.cjs");
const paths = require("../paths.cjs");
const {
createAppConfig,
createDemoConfig,
createCastConfig,
createHassioConfig,
createGalleryConfig,
} = require("../webpack");
} = require("../webpack.cjs");
const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }),
@@ -104,6 +105,8 @@ gulp.task("webpack-prod-app", () =>
prodBuild(
bothBuilds(createAppConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
})
)
);
@@ -161,6 +164,8 @@ gulp.task("webpack-prod-hassio", () =>
prodBuild(
bothBuilds(createHassioConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
})
)
);

View File

@@ -103,7 +103,7 @@ module.exports = function (opts = {}) {
}
delete optionsObject.type;
if (!new RegExp("^.*/").test(workerFile)) {
if (!/^.*\//.test(workerFile)) {
this.warn(
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
);

View File

@@ -3,18 +3,18 @@ const path = require("path");
const commonjs = require("@rollup/plugin-commonjs");
const resolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json");
const babel = require("@rollup/plugin-babel").babel;
const { babel } = require("@rollup/plugin-babel");
const replace = require("@rollup/plugin-replace");
const visualizer = require("rollup-plugin-visualizer");
const { string } = require("rollup-plugin-string");
const { terser } = require("rollup-plugin-terser");
const manifest = require("./rollup-plugins/manifest-plugin");
const worker = require("./rollup-plugins/worker-plugin");
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
const ignore = require("./rollup-plugins/ignore-plugin");
const manifest = require("./rollup-plugins/manifest-plugin.cjs");
const worker = require("./rollup-plugins/worker-plugin.cjs");
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin.cjs");
const ignore = require("./rollup-plugins/ignore-plugin.cjs");
const bundle = require("./bundle");
const paths = require("./paths");
const bundle = require("./bundle.cjs");
const paths = require("./paths.cjs");
const extensions = [".js", ".ts"];
@@ -39,7 +39,7 @@ const createRollupConfig = ({
inputOptions: {
input: entry,
// Some entry points contain no JavaScript. This setting silences a warning about that.
// https://rollupjs.org/guide/en/#preserveentrysignatures
// https://rollupjs.org/configuration-options/#preserveentrysignatures
preserveEntrySignatures: false,
plugins: [
ignore({
@@ -76,7 +76,7 @@ const createRollupConfig = ({
}),
!isWDS && worker(),
!isWDS && dontHashPlugin({ dontHash }),
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)),
!isWDS && isProdBuild && terser(bundle.terserOptions({ latestBuild })),
!isWDS &&
isStatsBuild &&
visualizer({
@@ -90,20 +90,20 @@ const createRollupConfig = ({
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/guide/en/#outputdir
// https://rollupjs.org/configuration-options/#output-dir
dir: outputPath,
// https://rollupjs.org/guide/en/#outputformat
// https://rollupjs.org/configuration-options/#output-format
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/guide/en/#outputexternallivebindings
// https://rollupjs.org/configuration-options/#output-externallivebindings
externalLiveBindings: false,
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
// https://rollupjs.org/configuration-options/#output-entryfilenames
// https://rollupjs.org/configuration-options/#output-chunkfilenames
// https://rollupjs.org/configuration-options/#output-assetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap
// https://rollupjs.org/configuration-options/#output-sourcemap
sourcemap: isProdBuild ? true : "inline",
},
});

View File

@@ -4,8 +4,8 @@ const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
const paths = require("./paths.js");
const bundle = require("./bundle.js");
const paths = require("./paths.cjs");
const bundle = require("./bundle.cjs");
class LogStartCompilePlugin {
ignoredFirst = false;
@@ -22,6 +22,7 @@ class LogStartCompilePlugin {
}
const createWebpackConfig = ({
name,
entry,
outputPath,
publicPath,
@@ -29,6 +30,7 @@ const createWebpackConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isHassioBuild,
dontHash,
}) => {
@@ -37,10 +39,16 @@ const createWebpackConfig = ({
}
const ignorePackages = bundle.ignorePackages({ latestBuild });
return {
name,
mode: isProdBuild ? "production" : "development",
target: ["web", latestBuild ? "es2017" : "es5"],
devtool: isProdBuild
? "cheap-module-source-map"
// For tests/CI, source maps are skipped to gain build speed
// For production, generate source maps for accurate stack traces without source code
// For development, generate "cheap" versions that can map to original line numbers
devtool: isTestBuild
? false
: isProdBuild
? "nosources-source-map"
: "eval-cheap-module-source-map",
entry,
node: false,
@@ -51,11 +59,14 @@ const createWebpackConfig = ({
use: {
loader: "babel-loader",
options: {
...bundle.babelOptions({ latestBuild, isProdBuild }),
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
},
resolve: {
fullySpecified: false,
},
},
{
test: /\.css$/,
@@ -68,7 +79,7 @@ const createWebpackConfig = ({
new TerserPlugin({
parallel: true,
extractComments: true,
terserOptions: bundle.terserOptions(latestBuild),
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
@@ -141,18 +152,37 @@ const createWebpackConfig = ({
},
},
output: {
filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
filename: ({ chunk }) =>
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
? "[name].js"
: "[name]-[contenthash].js",
chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
assetModuleFilename:
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
hashFunction: "xxhash64",
hashDigest: "base64url",
hashDigestLength: 11, // full length of 64 bit base64url
path: outputPath,
publicPath,
// To silence warning in worker plugin
globalObject: "self",
// Since production source maps don't include sources, we need to point to them elsewhere
// For dependencies, just provide the path (no source in browser)
// Otherwise, point to the raw code on GitHub for browser to load
devtoolModuleFilenameTemplate:
!isTestBuild && isProdBuild
? (info) => {
const sourcePath = info.resourcePath.replace(/^\.\//, "");
if (
sourcePath.startsWith("node_modules") ||
sourcePath.startsWith("webpack")
) {
return `no-source/${sourcePath}`;
}
return `${bundle.sourceMapURL()}/${sourcePath}`;
}
: undefined,
},
experiments: {
topLevelAwait: true,
@@ -160,9 +190,14 @@ const createWebpackConfig = ({
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
const createAppConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
}) =>
createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
);
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
@@ -173,8 +208,20 @@ const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
const createCastConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
const createHassioConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
const createHassioConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
}) =>
createWebpackConfig(
bundle.config.hassio({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
})
);
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));

View File

@@ -1,5 +1,5 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const config = rollup.createCastConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createCastConfig({
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };
export default { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,8 +1,8 @@
const { createCastConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
import webpack from "../build-scripts/webpack.cjs";
import env from "../build-scripts/env.cjs";
module.exports = createCastConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
export default webpack.createCastConfig({
isProdBuild: env.isProdBuild(),
isStatsBuild: env.isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,5 +1,5 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const config = rollup.createDemoConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createDemoConfig({
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };
export default { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,39 +1,19 @@
import { HassEntity } from "home-assistant-js-websocket";
import { HistoryStates } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams {
filter_entity_id: string;
end_time: string;
}
const parseQuery = <T>(queryString: string) => {
const query: any = {};
const items = queryString.split("&");
for (const item of items) {
const parts = item.split("=");
const key = decodeURIComponent(parts[0]);
const value = parts.length > 1 ? decodeURIComponent(parts[1]) : undefined;
query[key] = value;
}
return query as T;
};
const getTime = (minutesAgo) => {
const ts = new Date(Date.now() - minutesAgo * 60 * 1000);
return ts.toISOString();
};
const randomTimeAdjustment = (diff) => Math.random() * diff - diff / 2;
const maxTime = 1440;
const generateHistory = (state, deltas) => {
const generateStateHistory = (
state: HassEntity,
deltas,
start_date: Date,
end_date: Date
) => {
const changes =
typeof deltas[0] === "object"
? deltas
: deltas.map((st) => ({ state: st }));
const timeDiff = 900 / changes.length;
const timeDiff = (end_date.getTime() - start_date.getTime()) / changes.length;
return changes.map((change, index) => {
let attributes;
@@ -47,17 +27,13 @@ const generateHistory = (state, deltas) => {
attributes = { ...state.attributes, ...change.attributes };
}
const time =
index === 0
? getTime(maxTime)
: getTime(maxTime - index * timeDiff + randomTimeAdjustment(timeDiff));
const time = start_date.getTime() + timeDiff * index;
return {
attributes,
entity_id: state.entity_id,
state: change.state || state.state,
last_changed: time,
last_updated: time,
a: attributes,
s: change.state || state.state,
lc: time / 1000,
lu: time / 1000,
};
});
};
@@ -65,15 +41,29 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"];
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(
/history\/period\/.+/,
(hass, _method, path, _parameters) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
mockHass.mockWS(
"history/stream",
(
{
entity_ids,
start_time,
end_time,
}: {
entity_ids: string[];
start_time: string;
end_time?: string;
},
hass,
onChange
) => {
const states: HistoryStates = {};
const results: HassEntity[][] = [];
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
for (const entityId of entity_ids) {
states[entityId] = [];
for (const entityId of entities) {
const state = hass.states[entityId];
if (!state) {
@@ -81,7 +71,12 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
}
if (!state.attributes.unit_of_measurement) {
results.push(generateHistory(state, [state.state]));
states[entityId] = generateStateHistory(
state,
[state.state],
start,
end
);
continue;
}
@@ -120,17 +115,23 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
states[entityId] = generateStateHistory(
state,
Array.from({ length: statesToGenerate }, genFunc),
start,
end
);
}
return results;
setTimeout(() => {
onChange?.({
states,
start_time: start,
end_time: end,
});
}, 1);
return () => {};
}
);
};

View File

@@ -1,12 +1,11 @@
const { createDemoConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
import webpack from "../build-scripts/webpack.cjs";
import env from "../build-scripts/env.cjs";
// File just used for stats builds
const latestBuild = true;
module.exports = createDemoConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
export default webpack.createDemoConfig({
isProdBuild: env.isProdBuild(),
isStatsBuild: env.isStatsBuild(),
latestBuild,
});

View File

@@ -1,5 +1,5 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const config = rollup.createGalleryConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createGalleryConfig({
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };
export default { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,4 +1,4 @@
module.exports = [
export default [
{
// This section has no header and so all page links are shown directly in the sidebar
category: "concepts",

View File

@@ -0,0 +1,3 @@
---
title: Control Select
---

View File

@@ -0,0 +1,212 @@
import { mdiFanOff, mdiFanSpeed1, mdiFanSpeed2, mdiFanSpeed3 } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { repeat } from "lit/directives/repeat";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import type { ControlSelectOption } from "../../../../src/components/ha-control-select";
const fullOptions: ControlSelectOption[] = [
{
value: "off",
label: "Off",
path: mdiFanOff,
},
{
value: "low",
label: "Low",
path: mdiFanSpeed1,
},
{
value: "medium",
label: "Medium",
path: mdiFanSpeed2,
},
{
value: "high",
label: "High",
path: mdiFanSpeed3,
},
];
const iconOptions: ControlSelectOption[] = [
{
value: "off",
path: mdiFanOff,
},
{
value: "low",
path: mdiFanSpeed1,
},
{
value: "medium",
path: mdiFanSpeed2,
},
{
value: "high",
path: mdiFanSpeed3,
},
];
const labelOptions: ControlSelectOption[] = [
{
value: "off",
label: "Off",
},
{
value: "low",
label: "Low",
},
{
value: "medium",
label: "Medium",
},
{
value: "high",
label: "High",
},
];
const selects: {
id: string;
label: string;
class?: string;
options: ControlSelectOption[];
disabled?: boolean;
}[] = [
{
id: "label",
label: "Select with labels",
options: labelOptions,
},
{
id: "icon",
label: "Select with icons",
options: iconOptions,
},
{
id: "icon",
label: "Disabled select",
options: iconOptions,
disabled: true,
},
{
id: "custom",
label: "Select and custom style",
class: "custom",
options: fullOptions,
},
];
@customElement("demo-components-ha-control-select")
export class DemoHaControlSelect extends LitElement {
@state() private value?: string = "off";
handleValueChanged(e: CustomEvent) {
this.value = e.detail.value as string;
}
protected render(): TemplateResult {
return html`
<ha-card>
<div class="card-content">
<p><b>Slider values</b></p>
<table>
<tbody>
<tr>
<td>value</td>
<td>${this.value ?? "-"}</td>
</tr>
</tbody>
</table>
</div>
</ha-card>
${repeat(selects, (select) => {
const { id, label, options, ...config } = select;
return html`
<ha-card>
<div class="card-content">
<label id=${id}>${label}</label>
<pre>Config: ${JSON.stringify(config)}</pre>
<ha-control-select
.value=${this.value}
.options=${options}
class=${ifDefined(config.class)}
@value-changed=${this.handleValueChanged}
aria-labelledby=${id}
disabled=${ifDefined(config.disabled)}
>
</ha-control-select>
</div>
</ha-card>
`;
})}
<ha-card>
<div class="card-content">
<p class="title"><b>Vertical</b></p>
<div class="vertical-selects">
${repeat(selects, (select) => {
const { id, label, options, ...config } = select;
return html`
<ha-control-select
.value=${this.value}
.options=${options}
vertical
class=${ifDefined(config.class)}
@value-changed=${this.handleValueChanged}
aria-labelledby=${id}
disabled=${ifDefined(config.disabled)}
>
</ha-control-select>
`;
})}
</div>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
pre {
margin-top: 0;
margin-bottom: 8px;
}
p {
margin: 0;
}
label {
font-weight: 600;
}
.custom {
--mdc-icon-size: 24px;
--control-select-color: var(--state-fan-active-color);
--control-select-thickness: 100px;
--control-select-border-radius: 24px;
}
.vertical-selects {
height: 300px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
p.title {
margin-bottom: 12px;
}
.vertical-selects > *:not(:last-child) {
margin-right: 4px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-control-select": DemoHaControlSelect;
}
}

View File

@@ -1,8 +1,8 @@
const { createGalleryConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
import webpack from "../build-scripts/webpack.cjs";
import env from "../build-scripts/env.cjs";
module.exports = createGalleryConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
export default webpack.createGalleryConfig({
isProdBuild: env.isProdBuild(),
isStatsBuild: env.isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,3 +1,13 @@
var requireDir = require("require-dir");
import { globIterate } from "glob";
requireDir("./build-scripts/gulp/");
const gulpImports = [];
for await (const gulpModule of globIterate("build-scripts/gulp/*.?(c|m)js", {
dotRelative: true,
})) {
gulpImports.push(import(gulpModule));
}
// Since all tasks are currently registered with gulp.task(), this is enough
// If any are converted to named exports, need to loop and aggregate exports here
await Promise.all(gulpImports);

View File

@@ -1,5 +1,5 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const config = rollup.createHassioConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createHassioConfig({
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };
export default { ...config.inputOptions, output: config.outputOptions };

View File

@@ -92,11 +92,7 @@ export class HassioAddonStore extends LitElement {
.route=${this.route}
.header=${this.supervisor.localize("panel.store")}
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -114,9 +114,6 @@ class HassioAddonAudio extends LitElement {
ha-card {
display: block;
}
paper-item {
width: 450px;
}
.card-actions {
text-align: right;
}

View File

@@ -168,7 +168,7 @@ class HassioAddonConfig extends LitElement {
${this.supervisor.localize("addon.configuration.options.header")}
</h2>
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-button-menu @action=${this._handleAction}>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -195,11 +195,7 @@ export class HassioBackups extends LitElement {
: "/config"}
supervisor
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
<ha-icon-button
.label=${this.supervisor?.localize("common.menu")}
.path=${mdiDotsVertical}
@@ -248,9 +244,9 @@ export class HassioBackups extends LitElement {
class="warning"
@click=${this._deleteSelected}
></ha-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
<simple-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip>
</simple-tooltip>
`}
</div>
</div> `

View File

@@ -50,20 +50,7 @@ class HassioMarkdownDialog extends LitElement {
haStyleDialog,
hassioStyle,
css`
app-toolbar {
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
app-toolbar [main-title] {
margin-left: 16px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
ha-markdown {
padding: 16px;
}

View File

@@ -597,10 +597,6 @@ export class DialogHassioNetwork
margin-left: 8px;
}
:host([rtl]) app-toolbar {
direction: rtl;
text-align: right;
}
.container {
padding: 0 8px 4px;
}

View File

@@ -4,7 +4,7 @@ import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-tooltip/paper-tooltip";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -128,7 +128,7 @@ class HassioRepositoriesDialog extends LitElement {
@click=${this._removeRepository}
>
</ha-icon-button>
<paper-tooltip
<simple-tooltip
animation-delay="0"
position="bottom"
offset="1"
@@ -138,7 +138,7 @@ class HassioRepositoriesDialog extends LitElement {
? "dialog.repositories.used"
: "dialog.repositories.remove"
)}
</paper-tooltip>
</simple-tooltip>
</div>
</paper-item>
`

View File

@@ -184,7 +184,7 @@ class HassioHostInfo extends LitElement {
`
: ""}
<ha-button-menu corner="BOTTOM_START">
<ha-button-menu>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -1,8 +1,8 @@
const { createHassioConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
import webpack from "../build-scripts/webpack.cjs";
import env from "../build-scripts/env.cjs";
module.exports = createHassioConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
export default webpack.createHassioConfig({
isProdBuild: env.isProdBuild(),
isStatsBuild: env.isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,5 +1,5 @@
module.exports = {
"*.{js,ts}": ["prettier --write", "eslint --fix"],
export default {
"*.?(c|m){js,ts}": ["eslint --fix", "prettier --write"],
"!(/translations)*.{json,css,md,html}": "prettier --write",
"translations/*/*.json": (files) =>
'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' +

View File

@@ -19,19 +19,20 @@
"postinstall": "husky install",
"prepack": "pinst --disable",
"postpack": "pinst --enable",
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@braintree/sanitize-url": "6.0.2",
"@codemirror/autocomplete": "6.4.2",
"@codemirror/commands": "6.2.1",
"@codemirror/commands": "6.2.2",
"@codemirror/language": "6.6.0",
"@codemirror/legacy-modes": "6.3.1",
"@codemirror/search": "6.2.3",
"@codemirror/legacy-modes": "6.3.2",
"@codemirror/search": "6.3.0",
"@codemirror/state": "6.2.0",
"@codemirror/view": "6.9.1",
"@codemirror/view": "6.9.3",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.5.1",
"@formatjs/intl-getcanonicallocales": "2.1.0",
@@ -39,14 +40,16 @@
"@formatjs/intl-numberformat": "8.3.5",
"@formatjs/intl-pluralrules": "5.1.10",
"@formatjs/intl-relativetimeformat": "11.1.10",
"@fullcalendar/core": "6.1.4",
"@fullcalendar/daygrid": "6.1.4",
"@fullcalendar/interaction": "6.1.4",
"@fullcalendar/list": "6.1.4",
"@fullcalendar/timegrid": "6.1.4",
"@lezer/highlight": "1.1.3",
"@fullcalendar/core": "6.1.5",
"@fullcalendar/daygrid": "6.1.5",
"@fullcalendar/interaction": "6.1.5",
"@fullcalendar/list": "6.1.5",
"@fullcalendar/timegrid": "6.1.5",
"@lezer/highlight": "1.1.4",
"@lit-labs/context": "0.3.0",
"@lit-labs/motion": "1.0.3",
"@lit-labs/virtualizer": "1.0.1",
"@lrnwebcomponents/simple-tooltip": "4.1.0",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-button": "0.27.0",
@@ -69,38 +72,36 @@
"@material/mwc-tab-bar": "0.27.0",
"@material/mwc-textarea": "0.27.0",
"@material/mwc-textfield": "0.27.0",
"@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.3",
"@mdi/js": "7.1.96",
"@mdi/svg": "7.1.96",
"@material/web": "=1.0.0-pre.4",
"@mdi/js": "7.2.96",
"@mdi/svg": "7.2.96",
"@polymer/app-layout": "3.1.0",
"@polymer/iron-flex-layout": "3.0.1",
"@polymer/iron-icon": "3.0.1",
"@polymer/iron-input": "3.0.1",
"@polymer/iron-resizable-behavior": "3.0.1",
"@polymer/paper-input": "3.2.1",
"@polymer/paper-item": "3.0.1",
"@polymer/paper-listbox": "3.0.1",
"@polymer/paper-slider": "3.0.1",
"@polymer/paper-styles": "3.0.1",
"@polymer/paper-tabs": "3.1.0",
"@polymer/paper-toast": "3.0.1",
"@polymer/paper-tooltip": "3.0.1",
"@polymer/polymer": "3.4.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "23.3.8",
"@vaadin/vaadin-themable-mixin": "23.3.8",
"@vaadin/combo-box": "23.3.9",
"@vaadin/vaadin-themable-mixin": "23.3.9",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.8",
"@webcomponents/webcomponentsjs": "2.7.0",
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"chart.js": "3.3.2",
"comlink": "4.4.1",
"core-js": "3.29.0",
"core-js": "3.29.1",
"cropperjs": "1.5.13",
"date-fns": "2.29.3",
"date-fns-tz": "2.0.0",
@@ -108,15 +109,15 @@
"deep-freeze": "0.0.1",
"fuse.js": "6.6.2",
"google-timezones-json": "1.0.2",
"hls.js": "1.3.4",
"hls.js": "1.3.5",
"home-assistant-js-websocket": "8.0.1",
"idb-keyval": "6.2.0",
"intl-messageformat": "10.3.1",
"intl-messageformat": "10.3.3",
"js-yaml": "4.1.0",
"leaflet": "1.9.3",
"leaflet-draw": "1.0.4",
"lit": "2.6.1",
"marked": "4.2.12",
"lit": "2.7.0",
"marked": "4.3.0",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
@@ -133,8 +134,8 @@
"tsparticles-engine": "2.9.3",
"tsparticles-preset-links": "2.9.3",
"unfetch": "5.0.0",
"vis-data": "7.1.4",
"vis-network": "9.1.4",
"vis-data": "7.1.6",
"vis-network": "9.1.6",
"vue": "2.7.14",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
@@ -147,7 +148,7 @@
"xss": "1.0.14"
},
"devDependencies": {
"@babel/core": "7.21.0",
"@babel/core": "7.21.4",
"@babel/plugin-external-helpers": "7.18.6",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.21.0",
@@ -157,12 +158,13 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-syntax-import-meta": "7.10.4",
"@babel/plugin-syntax-top-level-await": "7.14.5",
"@babel/preset-env": "7.20.2",
"@babel/preset-typescript": "7.21.0",
"@babel/preset-env": "7.21.4",
"@babel/preset-typescript": "7.21.4",
"@koa/cors": "4.0.0",
"@octokit/auth-oauth-device": "4.0.4",
"@octokit/plugin-retry": "4.1.3",
"@octokit/rest": "19.0.7",
"@open-wc/dev-server-hmr": "0.1.3",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "24.0.1",
"@rollup/plugin-json": "6.0.0",
@@ -172,50 +174,51 @@
"@types/chromecast-caf-sender": "1.0.5",
"@types/esprima": "4.0.3",
"@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.0",
"@types/js-yaml": "4.0.5",
"@types/leaflet": "1.9.1",
"@types/leaflet": "1.9.3",
"@types/leaflet-draw": "1.0.6",
"@types/marked": "4.0.8",
"@types/mocha": "10.0.1",
"@types/qrcode": "1.5.0",
"@types/serve-handler": "6.1.1",
"@types/sortablejs": "1.15.0",
"@types/sortablejs": "1.15.1",
"@types/tar": "6.1.4",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.54.0",
"@web/dev-server": "0.1.35",
"@web/dev-server-rollup": "0.3.21",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"@web/dev-server": "0.1.37",
"@web/dev-server-rollup": "0.4.0",
"babel-loader": "9.1.2",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.7",
"del": "7.0.0",
"eslint": "8.35.0",
"eslint": "8.37.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.0.0",
"eslint-config-prettier": "8.7.0",
"eslint-config-prettier": "8.8.0",
"eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-lit": "1.8.2",
"eslint-plugin-lit-a11y": "2.3.0",
"eslint-plugin-lit-a11y": "2.4.0",
"eslint-plugin-unused-imports": "2.0.0",
"eslint-plugin-wc": "1.4.0",
"esprima": "4.0.1",
"fancy-log": "2.0.0",
"fs-extra": "11.1.0",
"glob": "8.1.0",
"fs-extra": "11.1.1",
"glob": "9.3.2",
"gulp": "4.0.2",
"gulp-flatmap": "1.0.2",
"gulp-json-transform": "0.4.8",
"gulp-merge-json": "2.1.2",
"gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1",
"html-minifier": "4.0.0",
"html-minifier-terser": "7.1.0",
"husky": "8.0.3",
"instant-mocha": "1.5.0",
"jszip": "3.10.1",
"lint-staged": "13.1.2",
"lint-staged": "13.2.0",
"lit-analyzer": "1.2.1",
"lodash.template": "4.5.0",
"magic-string": "0.30.0",
@@ -225,37 +228,36 @@
"object-hash": "3.0.0",
"open": "8.4.2",
"pinst": "3.0.0",
"prettier": "2.8.4",
"require-dir": "1.2.0",
"prettier": "2.8.7",
"rollup": "2.79.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "5.3.1",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.9.0",
"serve-handler": "6.1.5",
"sinon": "15.0.1",
"sinon": "15.0.3",
"source-map-url": "0.4.1",
"systemjs": "6.14.0",
"systemjs": "6.14.1",
"tar": "6.1.13",
"terser-webpack-plugin": "5.3.6",
"terser-webpack-plugin": "5.3.7",
"ts-lit-plugin": "1.2.1",
"typescript": "4.9.5",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
"webpack": "=5.72.1",
"webpack-cli": "5.0.1",
"webpack-dev-server": "4.11.1",
"webpack-dev-server": "4.13.2",
"webpack-manifest-plugin": "5.0.0",
"webpackbar": "5.0.2",
"workbox-build": "6.5.4"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch"
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@material/mwc-button@^0.25.3": "^0.27.0"
},
"main": "src/home-assistant.js",
"prettier": {
"trailingComma": "es5",
"arrowParens": "always"
},
"packageManager": "yarn@3.4.1"
"packageManager": "yarn@3.5.0"
}

View File

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

View File

@@ -1,5 +1,5 @@
const rollup = require("./build-scripts/rollup.js");
const env = require("./build-scripts/env.js");
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const config = rollup.createAppConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createAppConfig({
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };
export default { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,9 +1,9 @@
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@material/mwc-list";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import "../components/ha-icon-next";
import "../components/ha-list-item";
import { AuthProvider } from "../data/auth";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
@@ -20,18 +20,21 @@ export class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
protected render() {
return html`
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
${this.authProviders.map(
(provider) => html`
<paper-item
role="button"
.auth_provider=${provider}
@click=${this._handlePick}
>
<paper-item-body>${provider.name}</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
`
)}
<mwc-list>
${this.authProviders.map(
(provider) => html`
<ha-list-item
hasMeta
role="button"
.auth_provider=${provider}
@click=${this._handlePick}
>
${provider.name}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
`
)}</mwc-list
>
`;
}
@@ -40,11 +43,12 @@ export class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
}
static styles = css`
paper-item {
cursor: pointer;
}
p {
margin-top: 0;
}
mwc-list {
margin: 0 -16px;
--mdc-list-side-padding: 16px;
}
`;
}

View File

@@ -1,16 +1,19 @@
import secondsToDuration from "./seconds_to_duration";
import millisecondsToDuration from "./milliseconds_to_duration";
const DAY_IN_SECONDS = 86400;
const HOUR_IN_SECONDS = 3600;
const MINUTE_IN_SECONDS = 60;
const DAY_IN_MILLISECONDS = 86400000;
const HOUR_IN_MILLISECONDS = 3600000;
const MINUTE_IN_MILLISECONDS = 60000;
const SECOND_IN_MILLISECONDS = 1000;
export const UNIT_TO_SECOND_CONVERT = {
s: 1,
min: MINUTE_IN_SECONDS,
h: HOUR_IN_SECONDS,
d: DAY_IN_SECONDS,
export const UNIT_TO_MILLISECOND_CONVERT = {
ms: 1,
s: SECOND_IN_MILLISECONDS,
min: MINUTE_IN_MILLISECONDS,
h: HOUR_IN_MILLISECONDS,
d: DAY_IN_MILLISECONDS,
};
export const formatDuration = (duration: string, units: string): string =>
secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) ||
"0";
millisecondsToDuration(
parseFloat(duration) * UNIT_TO_MILLISECOND_CONVERT[units]
) || "0";

View File

@@ -0,0 +1,25 @@
const leftPad = (num: number, digits = 2) => {
let paddedNum = "" + num;
for (let i = 1; i < digits; i++) {
paddedNum = parseInt(paddedNum) < 10 ** i ? `0${paddedNum}` : paddedNum;
}
return paddedNum;
};
export default function millisecondsToDuration(d: number) {
const h = Math.floor(d / 1000 / 3600);
const m = Math.floor(((d / 1000) % 3600) / 60);
const s = Math.floor(((d / 1000) % 3600) % 60);
const ms = Math.floor(d % 1000);
if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
}
if (m > 0) {
return `${m}:${leftPad(s)}`;
}
if (s > 0 || ms > 0) {
return `${s}${ms > 0 ? `.${leftPad(ms, 3)}` : ``}`;
}
return null;
}

View File

@@ -0,0 +1,111 @@
import { PropertyDeclaration, PropertyValues, ReactiveElement } from "lit";
import { ClassElement } from "../../types";
import { shallowEqual } from "../util/shallow-equal";
/**
* Transform function type.
*/
export interface Transformer<T = any, V = any> {
(value: V): T;
}
type ReactiveTransformElement = ReactiveElement & {
_transformers: Map<PropertyKey, Transformer>;
_watching: Map<PropertyKey, Set<PropertyKey>>;
};
type ReactiveElementClassWithTransformers = typeof ReactiveElement & {
prototype: ReactiveTransformElement;
};
/**
* Specifies an tranformer callback that is run when the value of the decorated property, or any of the properties in the watching array, changes.
* The result of the tranformer is assigned to the decorated property.
* The tranformer receives the current as arguments.
*/
export const transform =
<T, V>(config: {
transformer: Transformer<T, V>;
watch?: PropertyKey[];
propertyOptions?: PropertyDeclaration;
}): any =>
(clsElement: ClassElement) => {
const key = String(clsElement.key);
return {
...clsElement,
kind: "method",
descriptor: {
set(this: ReactiveTransformElement, value: V) {
const oldValue = this[`__transform_${key}`];
const trnsformr: Transformer<T, V> | undefined =
this._transformers.get(key);
if (trnsformr) {
this[`__transform_${key}`] = trnsformr.call(this, value);
} else {
this[`__transform_${key}`] = value;
}
this[`__original_${key}`] = value;
this.requestUpdate(key, oldValue);
},
get(): T {
return this[`__transform_${key}`];
},
enumerable: true,
configurable: true,
},
finisher(cls: ReactiveElementClassWithTransformers) {
// if we haven't wrapped `willUpdate` in this class, do so
if (!cls.prototype._transformers) {
cls.prototype._transformers = new Map<PropertyKey, Transformer>();
cls.prototype._watching = new Map<PropertyKey, Set<PropertyKey>>();
// @ts-ignore
const userWillUpdate = cls.prototype.willUpdate;
// @ts-ignore
cls.prototype.willUpdate = function (
this: ReactiveTransformElement,
changedProperties: PropertyValues
) {
userWillUpdate.call(this, changedProperties);
const keys = new Set<PropertyKey>();
changedProperties.forEach((_v, k) => {
const watchers = this._watching;
const ks: Set<PropertyKey> | undefined = watchers.get(k);
if (ks !== undefined) {
ks.forEach((wk) => keys.add(wk));
}
});
keys.forEach((k) => {
// trigger setter
this[k] = this[`__original_${String(k)}`];
});
};
// clone any existing observers (superclasses)
// eslint-disable-next-line no-prototype-builtins
} else if (!cls.prototype.hasOwnProperty("_transformers")) {
const tranformers = cls.prototype._transformers;
cls.prototype._transformers = new Map();
tranformers.forEach((v: any, k: PropertyKey) =>
cls.prototype._transformers.set(k, v)
);
}
// set this method
cls.prototype._transformers.set(clsElement.key, config.transformer);
if (config.watch) {
// store watchers
config.watch.forEach((k) => {
let curWatch = cls.prototype._watching.get(k);
if (!curWatch) {
curWatch = new Set();
cls.prototype._watching.set(k, curWatch);
}
curWatch.add(clsElement.key);
});
}
cls.createProperty(clsElement.key, {
noAccessor: true,
hasChanged: (v: any, o: any) => !shallowEqual(v, o),
...config.propertyOptions,
});
},
};
};

View File

@@ -93,7 +93,7 @@ export const applyThemesOnElement = (
}
// Nothing was changed
if (element._themes?.cacheKey === cacheKey) {
if (element.__themes?.cacheKey === cacheKey) {
return;
}
}
@@ -119,7 +119,7 @@ export const applyThemesOnElement = (
}
}
if (!element._themes?.keys && !Object.keys(themeRules).length) {
if (!element.__themes?.keys && !Object.keys(themeRules).length) {
// No styles to reset, and no styles to set
return;
}
@@ -130,8 +130,8 @@ export const applyThemesOnElement = (
: undefined;
// Add previous set keys to reset them, and new theme
const styles = { ...element._themes?.keys, ...newTheme?.styles };
element._themes = { cacheKey, keys: newTheme?.keys };
const styles = { ...element.__themes?.keys, ...newTheme?.styles };
element.__themes = { cacheKey, keys: newTheme?.keys };
// Set and/or reset styles
if (element.updateStyles) {

View File

@@ -1,30 +1,126 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, TemplateResult } from "lit";
import { until } from "lit/directives/until";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { HomeAssistant } from "../../types";
import checkValidDate from "../datetime/check_valid_date";
import { formatDate } from "../datetime/format_date";
import { formatDateTimeWithSeconds } from "../datetime/format_date_time";
import { formatNumber } from "../number/format_number";
import { capitalizeFirstLetter } from "../string/capitalize-first-letter";
import { isDate } from "../string/is_date";
import { isTimestamp } from "../string/is_timestamp";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
import { FrontendLocaleData } from "../../data/translation";
let jsYamlPromise: Promise<typeof import("../../resources/js-yaml-dump")>;
export const computeAttributeValueDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
entities: HomeAssistant["entities"],
attribute: string,
value?: any
): string => {
const entityId = stateObj.entity_id;
): string | TemplateResult => {
const attributeValue =
value !== undefined ? value : stateObj.attributes[attribute];
// Null value, the state is unknown
if (attributeValue === null) {
return localize("state.default.unknown");
}
// Number value, return formatted number
if (typeof attributeValue === "number") {
return formatNumber(attributeValue, locale);
}
// Special handling in case this is a string with an known format
if (typeof attributeValue === "string") {
// URL handling
if (attributeValue.startsWith("http")) {
try {
// If invalid URL, exception will be raised
const url = new URL(attributeValue);
if (url.protocol === "http:" || url.protocol === "https:")
return html`<a target="_blank" rel="noreferrer" href=${value}
>${attributeValue}</a
>`;
} catch (_) {
// Nothing to do here
}
}
// Date handling
if (isDate(attributeValue, true)) {
// Timestamp handling
if (isTimestamp(attributeValue)) {
const date = new Date(attributeValue);
if (checkValidDate(date)) {
return formatDateTimeWithSeconds(date, locale);
}
}
// Value was not a timestamp, so only do date formatting
const date = new Date(attributeValue);
if (checkValidDate(date)) {
return formatDate(date, locale);
}
}
}
// Values are objects, render object
if (
(Array.isArray(attributeValue) &&
attributeValue.some((val) => val instanceof Object)) ||
(!Array.isArray(attributeValue) && attributeValue instanceof Object)
) {
if (!jsYamlPromise) {
jsYamlPromise = import("../../resources/js-yaml-dump");
}
const yaml = jsYamlPromise.then((jsYaml) => jsYaml.dump(attributeValue));
return html`<pre>${until(yaml, "")}</pre>`;
}
// If this is an array, try to determine the display value for each item
if (Array.isArray(attributeValue)) {
return attributeValue
.map((item) =>
computeAttributeValueDisplay(
localize,
stateObj,
locale,
entities,
attribute,
item
)
)
.join(", ");
}
// We've explored all known value handling, so now we'll try to find a
// translation for the value.
const entityId = stateObj.entity_id;
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
const translationKey = entity?.translation_key;
const deviceClass = stateObj.attributes.device_class;
const registryEntry = entities[entityId] as
| EntityRegistryDisplayEntry
| undefined;
const translationKey = registryEntry?.translation_key;
return (
(translationKey &&
localize(
`component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.state.${attributeValue}`
`component.${registryEntry.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.state.${attributeValue}`
)) ||
(deviceClass &&
localize(
`component.${domain}.entity_component.${deviceClass}.state_attributes.${attribute}.state.${attributeValue}`
)) ||
localize(
`component.${domain}.state_attributes._.${attribute}.state.${attributeValue}`
`component.${domain}.entity_component._.state_attributes.${attribute}.state.${attributeValue}`
) ||
attributeValue
);
@@ -37,6 +133,7 @@ export const computeAttributeNameDisplay = (
attribute: string
): string => {
const entityId = stateObj.entity_id;
const deviceClass = stateObj.attributes.device_class;
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
const translationKey = entity?.translation_key;
@@ -46,7 +143,20 @@ export const computeAttributeNameDisplay = (
localize(
`component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.name`
)) ||
localize(`component.${domain}.state_attributes._.${attribute}.name`) ||
attribute
(deviceClass &&
localize(
`component.${domain}.entity_component.${deviceClass}.state_attributes.${attribute}.name`
)) ||
localize(
`component.${domain}.entity_component._.state_attributes.${attribute}.name`
) ||
capitalizeFirstLetter(
attribute
.replace(/_/g, " ")
.replace(/\bid\b/g, "ID")
.replace(/\bip\b/g, "IP")
.replace(/\bmac\b/g, "MAC")
.replace(/\bgps\b/g, "GPS")
)
);
};

View File

@@ -7,7 +7,10 @@ import {
UPDATE_SUPPORT_PROGRESS,
} from "../../data/update";
import { HomeAssistant } from "../../types";
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
import {
formatDuration,
UNIT_TO_MILLISECOND_CONVERT,
} from "../datetime/duration";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
@@ -57,7 +60,7 @@ export const computeStateDisplayFromEntityAttributes = (
if (
attributes.device_class === "duration" &&
attributes.unit_of_measurement &&
UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement]
UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement]
) {
try {
return formatDuration(state, attributes.unit_of_measurement);
@@ -214,10 +217,10 @@ export const computeStateDisplayFromEntityAttributes = (
// Return device class translation
(attributes.device_class &&
localize(
`component.${domain}.state.${attributes.device_class}.${state}`
`component.${domain}.entity_component.${attributes.device_class}.state.${state}`
)) ||
// Return default translation
localize(`component.${domain}.state._.${state}`) ||
localize(`component.${domain}.entity_component._.state.${state}`) ||
// We don't know! Return the raw state.
state
);

View File

@@ -118,24 +118,40 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
"window",
],
},
device_tracker: {
source_type: ["bluetooth", "bluetooth_le", "gps", "router"],
},
fan: {
direction: ["forward", "reverse"],
},
humidifier: {
device_class: ["humidifier", "dehumidifier"],
},
media_player: {
device_class: ["tv", "speaker", "receiver"],
media_content_type: [
"album",
"app",
"artist",
"channel",
"channels",
"composer",
"contibuting_artist",
"episode",
"game",
"genre",
"image",
"movie",
"music",
"playlist",
"podcast",
"season",
"track",
"tvshow",
"url",
"video",
],
repeat: ["off", "one", "all"],
},
number: {
device_class: ["temperature"],

View File

@@ -1,6 +1,7 @@
/** Return an color representing a state. */
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE } from "../../data/entity";
import { computeGroupDomain, GroupEntity } from "../../data/group";
import { computeCssVariable } from "../../resources/css-variables";
import { slugify } from "../string/slugify";
import { batteryStateColorProperty } from "./color/battery_color";
@@ -52,11 +53,11 @@ export const stateColorCss = (stateObj: HassEntity, state?: string) => {
};
export const domainStateColorProperties = (
domain: string,
stateObj: HassEntity,
state?: string
): string[] => {
const compareState = state !== undefined ? state : stateObj.state;
const domain = computeDomain(stateObj.entity_id);
const active = stateActive(stateObj, state);
const properties: string[] = [];
@@ -95,8 +96,16 @@ export const stateColorProperties = (
}
}
// Special rules for group coloring
if (domain === "group") {
const groupDomain = computeGroupDomain(stateObj as GroupEntity);
if (groupDomain && STATE_COLORED_DOMAIN.has(groupDomain)) {
return domainStateColorProperties(groupDomain, stateObj, state);
}
}
if (STATE_COLORED_DOMAIN.has(domain)) {
return domainStateColorProperties(stateObj, state);
return domainStateColorProperties(domain, stateObj, state);
}
return undefined;

View File

@@ -1,17 +0,0 @@
import { refine, string } from "superstruct";
const isEntityId = (value: string): boolean => value.includes(".");
export const entityId = () =>
refine(string(), "entity ID (domain.entity)", isEntityId);
const isEntityIdOrAll = (value: string): boolean => {
if (value === "all") {
return true;
}
return isEntityId(value);
};
export const entityIdOrAll = () =>
refine(string(), "entity ID (domain.entity or all)", isEntityIdOrAll);

View File

@@ -0,0 +1,108 @@
/**
* Compares two values for shallow equality, only 1 level deep.
*/
export const shallowEqual = (a: any, b: any): boolean => {
if (a === b) {
return true;
}
if (a && b && typeof a === "object" && typeof b === "object") {
if (a.constructor !== b.constructor) {
return false;
}
let i: number | [any, any];
let length: number;
if (Array.isArray(a)) {
length = a.length;
if (length !== b.length) {
return false;
}
for (i = length; i-- !== 0; ) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size) {
return false;
}
for (i of a.entries()) {
if (!b.has(i[0])) {
return false;
}
}
for (i of a.entries()) {
if (i[1] !== b.get(i[0])) {
return false;
}
}
return true;
}
if (a instanceof Set && b instanceof Set) {
if (a.size !== b.size) {
return false;
}
for (i of a.entries()) {
if (!b.has(i[0])) {
return false;
}
}
return true;
}
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
// @ts-ignore
length = a.length;
// @ts-ignore
if (length !== b.length) {
return false;
}
for (i = length; i-- !== 0; ) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
if (a.constructor === RegExp) {
return a.source === b.source && a.flags === b.flags;
}
if (a.valueOf !== Object.prototype.valueOf) {
return a.valueOf() === b.valueOf();
}
if (a.toString !== Object.prototype.toString) {
return a.toString() === b.toString();
}
const keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) {
return false;
}
for (i = length; i-- !== 0; ) {
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {
return false;
}
}
for (i = length; i-- !== 0; ) {
const key = keys[i];
if (a[key] !== b[key]) {
return false;
}
}
return true;
}
// true if both NaN, false otherwise
// eslint-disable-next-line no-self-compare
return a !== a && b !== b;
};

View File

@@ -276,7 +276,11 @@ export default class HaChartBase extends LitElement {
top: this.chart!.canvas.offsetTop + context.tooltip.caretY + 12 + "px",
left:
this.chart!.canvas.offsetLeft +
clamp(context.tooltip.caretX, 100, this.clientWidth - 100) -
clamp(
context.tooltip.caretX,
100,
this.clientWidth - 100 - this.paddingYAxis
) -
100 +
"px",
};
@@ -302,6 +306,7 @@ export default class HaChartBase extends LitElement {
return css`
:host {
display: block;
position: var(--chart-base-position, relative);
}
.chartContainer {
overflow: hidden;

View File

@@ -61,6 +61,10 @@ class StateHistoryChartLine extends LitElement {
this._chartOptions = {
parsing: false,
animation: false,
interaction: {
mode: "nearest",
axis: "x",
},
scales: {
x: {
type: "time",
@@ -108,7 +112,6 @@ class StateHistoryChartLine extends LitElement {
},
plugins: {
tooltip: {
mode: "nearest",
callbacks: {
label: (context) =>
`${context.dataset.label}: ${formatNumber(
@@ -127,16 +130,13 @@ class StateHistoryChartLine extends LitElement {
},
},
},
hover: {
mode: "nearest",
},
elements: {
line: {
tension: 0.1,
borderWidth: 1.5,
},
point: {
hitRadius: 5,
hitRadius: 50,
},
},
// @ts-expect-error

View File

@@ -143,11 +143,16 @@ export class StateHistoryChartTimeline extends LitElement {
}
},
afterUpdate: (y) => {
const yWidth = this.showNames
? y.width ?? 0
: computeRTL(this.hass)
? 0
: y.left ?? 0;
if (
this._yWidth !== Math.floor(y.width) &&
this._yWidth !== Math.floor(yWidth) &&
y.ticks.length === this.data.length
) {
this._yWidth = Math.floor(y.width);
this._yWidth = Math.floor(yWidth);
fireEvent(this, "y-width-changed", {
value: this._yWidth,
chartIndex: this.chartIndex,

View File

@@ -175,15 +175,14 @@ export class StateHistoryCharts extends LitElement {
if (changedProps.has("_chartCount")) {
if (this._chartCount < this._childYWidths.length) {
this._childYWidths.length = this._chartCount;
this._maxYWidth =
this._childYWidths.length === 0 ? 0 : Math.max(...this._childYWidths);
this._maxYWidth = Math.max(...Object.values(this._childYWidths), 0);
}
}
}
private _yWidthChanged(e: CustomEvent<HASSDomEvents["y-width-changed"]>) {
this._childYWidths[e.detail.chartIndex] = e.detail.value;
this._maxYWidth = Math.max(...this._childYWidths);
this._maxYWidth = Math.max(...Object.values(this._childYWidths), 0);
}
private _isHistoryEmpty(): boolean {

View File

@@ -102,6 +102,7 @@ class StatisticsChart extends LitElement {
if (
changedProps.has("statisticsData") ||
changedProps.has("statTypes") ||
changedProps.has("chartType") ||
changedProps.has("hideLegend")
) {
this._generateData();
@@ -149,6 +150,10 @@ class StatisticsChart extends LitElement {
this._chartOptions = {
parsing: false,
animation: false,
interaction: {
mode: "nearest",
axis: "x",
},
scales: {
x: {
type: "time",
@@ -186,7 +191,6 @@ class StatisticsChart extends LitElement {
},
plugins: {
tooltip: {
mode: "nearest",
callbacks: {
label: (context) =>
`${context.dataset.label}: ${formatNumber(
@@ -208,9 +212,6 @@ class StatisticsChart extends LitElement {
},
},
},
hover: {
mode: "nearest",
},
elements: {
line: {
tension: 0.4,
@@ -219,7 +220,7 @@ class StatisticsChart extends LitElement {
},
bar: { borderWidth: 1.5, borderRadius: 4 },
point: {
hitRadius: 5,
hitRadius: 50,
},
},
// @ts-expect-error
@@ -316,6 +317,7 @@ class StatisticsChart extends LitElement {
}
statDataSets.forEach((d, i) => {
if (
this.chartType === "line" &&
prevEndTime &&
prevValues &&
prevEndTime.getTime() !== start.getTime()

View File

@@ -48,8 +48,8 @@ class HaDataTableIcon extends LitElement {
outline: none;
font-size: 10px;
line-height: 1;
background-color: var(--paper-tooltip-background, #616161);
color: var(--paper-tooltip-text-color, white);
background-color: var(--simple-tooltip-background, #616161);
color: var(--simple-tooltip-text-color, white);
padding: 8px;
border-radius: 2px;
}

View File

@@ -6,6 +6,21 @@ import DateRangePicker from "vue2-daterange-picker";
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
import { fireEvent } from "../common/dom/fire_event";
// Set the current date to the left picker instead of the right picker because the right is hidden
const CustomDateRangePicker = Vue.extend({
mixins: [DateRangePicker],
methods: {
selectMonthDate() {
const dt: Date = this.end || new Date();
// @ts-ignore
this.changeLeftMonth({
year: dt.getFullYear(),
month: dt.getMonth() + 1,
});
},
},
});
const Component = Vue.extend({
props: {
timePicker: {
@@ -47,7 +62,7 @@ const Component = Vue.extend({
},
render(createElement) {
// @ts-expect-error
return createElement(DateRangePicker, {
return createElement(CustomDateRangePicker, {
props: {
"time-picker": this.timePicker,
"auto-apply": this.autoApply,

View File

@@ -1,7 +1,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { formatAttributeName } from "../../data/entity_attributes";
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-combo-box";
@@ -54,7 +54,12 @@ class HaEntityAttributePicker extends LitElement {
.filter((key) => !this.hideAttributes?.includes(key))
.map((key) => ({
value: key,
label: formatAttributeName(key),
label: computeAttributeNameDisplay(
this.hass.localize,
state,
this.hass.entities,
key
),
}))
: [];
}
@@ -68,7 +73,14 @@ class HaEntityAttributePicker extends LitElement {
return html`
<ha-combo-box
.hass=${this.hass}
.value=${this.value ? formatAttributeName(this.value) : ""}
.value=${this.value
? computeAttributeNameDisplay(
this.hass.localize,
this.hass.states[this.entityId!],
this.hass.entities,
this.value
)
: ""}
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize(

View File

@@ -4,7 +4,7 @@ import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { computeStateDisplay } from "../../common/entity/compute_state_display";
import { getStates } from "../../common/entity/get_states";
import { formatAttributeValue } from "../../data/entity_attributes";
import { computeAttributeValueDisplay } from "../../common/entity/compute_attribute_display";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-combo-box";
@@ -58,7 +58,14 @@ class HaEntityStatePicker extends LitElement {
this.hass.entities,
key
)
: formatAttributeValue(this.hass, key),
: computeAttributeValueDisplay(
this.hass.localize,
state,
this.hass.locale,
this.hass.entities,
this.attribute,
key
),
}))
: [];
}

View File

@@ -1,4 +1,4 @@
import "@polymer/paper-tooltip/paper-tooltip";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
@@ -45,7 +45,7 @@ class StateInfo extends LitElement {
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
<paper-tooltip animation-delay="0" for="last_changed">
<simple-tooltip animation-delay="0" for="last_changed">
<div>
<div class="row">
<span class="column-name">
@@ -72,7 +72,7 @@ class StateInfo extends LitElement {
></ha-relative-time>
</div>
</div>
</paper-tooltip>
</simple-tooltip>
</div>`
: html`<div class="extra-info"><slot></slot></div>`}
</div>`;

View File

@@ -1,11 +0,0 @@
import { html } from "lit";
import { HomeAssistant } from "../types";
import { documentationUrl } from "../util/documentation-url";
export const analyticsLearnMore = (hass: HomeAssistant) => html`<a
.href=${documentationUrl(hass, "/integrations/analytics/")}
target="_blank"
rel="noreferrer"
>
How we process your data
</a>`;

View File

@@ -1,26 +1,15 @@
import "@polymer/paper-tooltip/paper-tooltip";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-settings-row";
import "./ha-switch";
import type { HaSwitch } from "./ha-switch";
const ADDITIONAL_PREFERENCES = [
{
key: "usage",
title: "Usage",
description: "Details of what you use with Home Assistant",
},
{
key: "statistics",
title: "Statistical data",
description: "Counts containing total number of datapoints",
},
];
const ADDITIONAL_PREFERENCES = ["usage", "statistics"] as const;
declare global {
interface HASSDomEvents {
@@ -30,19 +19,29 @@ declare global {
@customElement("ha-analytics")
export class HaAnalytics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localize!: LocalizeFunc;
@property({ attribute: false }) public analytics?: Analytics;
@property({ attribute: "translation_key_panel" }) public translationKeyPanel:
| "page-onboarding"
| "config" = "config";
protected render(): TemplateResult {
const loading = this.analytics === undefined;
const baseEnabled = !loading && this.analytics!.preferences.base;
return html`
<ha-settings-row>
<span slot="heading" data-for="base"> Basic analytics </span>
<span slot="heading" data-for="base">
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.title`
)}
</span>
<span slot="description" data-for="base">
This includes information about your system.
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.description`
)}
</span>
<ha-switch
@change=${this._handleRowClick}
@@ -57,26 +56,31 @@ export class HaAnalytics extends LitElement {
(preference) =>
html`
<ha-settings-row>
<span slot="heading" data-for=${preference.key}>
${preference.title}
<span slot="heading" data-for=${preference}>
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.title`
)}
</span>
<span slot="description" data-for=${preference.key}>
${preference.description}
<span slot="description" data-for=${preference}>
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.description`
)}
</span>
<span>
<ha-switch
@change=${this._handleRowClick}
.checked=${this.analytics?.preferences[preference.key]}
.preference=${preference.key}
name=${preference.key}
.checked=${this.analytics?.preferences[preference]}
.preference=${preference}
name=${preference}
>
</ha-switch>
${!baseEnabled
? html`
<paper-tooltip animation-delay="0" position="right">
You need to enable basic analytics for this option to be
available
</paper-tooltip>
<simple-tooltip animation-delay="0" position="right">
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
)}
</simple-tooltip>
`
: ""}
</span>
@@ -84,9 +88,15 @@ export class HaAnalytics extends LitElement {
`
)}
<ha-settings-row>
<span slot="heading" data-for="diagnostics"> Diagnostics </span>
<span slot="heading" data-for="diagnostics">
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.title`
)}
</span>
<span slot="description" data-for="diagnostics">
Share crash reports when unexpected errors occur.
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.description`
)}
</span>
<ha-switch
@change=${this._handleRowClick}
@@ -132,7 +142,7 @@ export class HaAnalytics extends LitElement {
preferences[preference] = target.checked;
if (
ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) &&
ADDITIONAL_PREFERENCES.some((entry) => entry === preference) &&
target.checked
) {
preferences.base = true;

View File

@@ -1,18 +1,11 @@
import { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
TemplateResult,
nothing,
} from "lit";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
formatAttributeName,
formatAttributeValue,
STATE_ATTRIBUTES,
} from "../data/entity_attributes";
computeAttributeNameDisplay,
computeAttributeValueDisplay,
} from "../common/entity/compute_attribute_display";
import { STATE_ATTRIBUTES } from "../data/entity_attributes";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
@@ -56,9 +49,22 @@ class HaAttributes extends LitElement {
${attributes.map(
(attribute) => html`
<div class="data-entry">
<div class="key">${formatAttributeName(attribute)}</div>
<div class="key">
${computeAttributeNameDisplay(
this.hass.localize,
this.stateObj!,
this.hass.entities,
attribute
)}
</div>
<div class="value">
${this.formatAttribute(attribute)}
${computeAttributeValueDisplay(
this.hass.localize,
this.stateObj!,
this.hass.locale,
this.hass.entities,
attribute
)}
</div>
</div>
`
@@ -128,14 +134,6 @@ class HaAttributes extends LitElement {
);
}
private formatAttribute(attribute: string): string | TemplateResult {
if (!this.stateObj) {
return "—";
}
const value = this.stateObj.attributes[attribute];
return formatAttributeValue(this.hass, value);
}
private expandedChanged(ev) {
this._expanded = ev.detail.expanded;
}

View File

@@ -10,7 +10,7 @@ import type { HaIconButton } from "./ha-icon-button";
export class HaButtonMenu extends LitElement {
protected readonly [FOCUS_TARGET];
@property() public corner: Corner = "TOP_START";
@property() public corner: Corner = "BOTTOM_START";
@property() public menuCorner: MenuCorner = "START";

View File

@@ -35,7 +35,7 @@ interface FilterValue {
export class HaRelatedFilterButtonMenu extends LitElement {
@property() public hass!: HomeAssistant;
@property() public corner: Corner = "TOP_START";
@property() public corner: Corner = "BOTTOM_START";
@property({ type: Boolean, reflect: true }) public narrow = false;

View File

@@ -1,13 +1,6 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import {
css,
CSSResultGroup,
html,
LitElement,
TemplateResult,
unsafeCSS,
} from "lit";
import { css, CSSResultGroup, html, LitElement, nothing, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-chip")
@@ -18,14 +11,14 @@ export class HaChip extends LitElement {
@property({ type: Boolean }) public noText = false;
protected render(): TemplateResult {
protected render() {
return html`
<div class="mdc-chip ${this.noText ? "no-text" : ""}">
${this.hasIcon
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
<slot name="icon"></slot>
</div>`
: null}
: nothing}
<div class="mdc-chip__ripple"></div>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
@@ -36,7 +29,7 @@ export class HaChip extends LitElement {
? html`<div class="mdc-chip__icon mdc-chip__icon--trailing">
<slot name="trailing-icon"></slot>
</div>`
: null}
: nothing}
</div>
`;
}

View File

@@ -27,6 +27,7 @@ class HaClimateState extends LitElement {
${computeAttributeValueDisplay(
this.hass.localize,
this.stateObj,
this.hass.locale,
this.hass.entities,
"preset_mode"
)}`
@@ -142,6 +143,7 @@ class HaClimateState extends LitElement {
? `${computeAttributeValueDisplay(
this.hass.localize,
this.stateObj,
this.hass.locale,
this.hass.entities,
"hvac_action"
)} (${stateString})`

View File

@@ -85,6 +85,7 @@ export class HaControlButton extends LitElement {
--control-button-background-opacity: 0.2;
--control-button-border-radius: 10px;
--mdc-icon-size: 20px;
color: var(--primary-text-color);
width: 40px;
height: 40px;
-webkit-tap-highlight-color: transparent;
@@ -107,8 +108,11 @@ export class HaControlButton extends LitElement {
outline: none;
overflow: hidden;
background: none;
z-index: 1;
--mdc-ripple-color: var(--control-button-background-color);
/* For safari border-radius overflow */
z-index: 0;
font-size: inherit;
color: inherit;
}
.button::before {
content: "";

View File

@@ -0,0 +1,344 @@
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { repeat } from "lit/directives/repeat";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-icon";
import "./ha-svg-icon";
export type ControlSelectOption = {
value: string;
label?: string;
icon?: string;
path?: string;
};
@customElement("ha-control-select")
export class HaControlSelect extends LitElement {
@property({ type: Boolean, reflect: true }) disabled = false;
@property() public options?: ControlSelectOption[];
@property() public value?: string;
@property({ type: Boolean, reflect: true })
public vertical = false;
@property({ type: Boolean, attribute: "hide-label" })
public hideLabel = false;
@state() private _activeIndex?: number;
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this.setAttribute("role", "listbox");
if (!this.hasAttribute("tabindex")) {
this.setAttribute("tabindex", "0");
}
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_activeIndex")) {
const activeValue =
this._activeIndex != null
? this.options?.[this._activeIndex]?.value
: undefined;
const activedescendant =
activeValue != null ? `option-${activeValue}` : undefined;
this.setAttribute("aria-activedescendant", activedescendant ?? "");
}
if (changedProps.has("vertical")) {
const orientation = this.vertical ? "vertical" : "horizontal";
this.setAttribute("aria-orientation", orientation);
}
}
public connectedCallback(): void {
super.connectedCallback();
this._setupListeners();
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._destroyListeners();
}
private _setupListeners() {
this.addEventListener("focus", this._handleFocus);
this.addEventListener("blur", this._handleBlur);
this.addEventListener("keydown", this._handleKeydown);
}
private _destroyListeners() {
this.removeEventListener("focus", this._handleFocus);
this.removeEventListener("blur", this._handleBlur);
this.removeEventListener("keydown", this._handleKeydown);
}
private _handleFocus() {
if (this.disabled) return;
this._activeIndex =
(this.value != null
? this.options?.findIndex((option) => option.value === this.value)
: undefined) ?? 0;
}
private _handleBlur() {
this._activeIndex = undefined;
}
private _handleKeydown(ev: KeyboardEvent) {
if (!this.options || this._activeIndex == null || this.disabled) return;
const value = this.options[this._activeIndex].value;
switch (ev.key) {
case " ":
this.value = value;
fireEvent(this, "value-changed", { value });
break;
case "ArrowUp":
case "ArrowLeft":
this._activeIndex =
this._activeIndex <= 0
? this.options.length - 1
: this._activeIndex - 1;
break;
case "ArrowDown":
case "ArrowRight":
this._activeIndex = (this._activeIndex + 1) % this.options.length;
break;
case "Home":
this._activeIndex = 0;
break;
case "End":
this._activeIndex = this.options.length - 1;
break;
default:
return;
}
ev.preventDefault();
}
private _handleOptionClick(ev: MouseEvent) {
if (this.disabled) return;
const value = (ev.target as any).value;
this.value = value;
fireEvent(this, "value-changed", { value });
}
private _handleOptionMouseDown(ev: MouseEvent) {
if (this.disabled) return;
ev.preventDefault();
const value = (ev.target as any).value;
this._activeIndex = this.options?.findIndex(
(option) => option.value === value
);
}
private _handleOptionMouseUp(ev: MouseEvent) {
ev.preventDefault();
this._activeIndex = undefined;
}
protected render() {
return html`
<div class="container">
${this.options
? repeat(
this.options,
(option) => option.value,
(option, idx) => this._renderOption(option, idx)
)
: nothing}
</div>
`;
}
private _renderOption(option: ControlSelectOption, index: number) {
return html`
<div
id=${`option-${option.value}`}
class=${classMap({
option: true,
selected: this.value === option.value,
focused: this._activeIndex === index,
})}
role="option"
.value=${option.value}
aria-selected=${this.value === option.value}
aria-label=${ifDefined(option.label)}
title=${ifDefined(option.label)}
@click=${this._handleOptionClick}
@mousedown=${this._handleOptionMouseDown}
@mouseup=${this._handleOptionMouseUp}
>
<div class="content">
${option.path
? html`<ha-svg-icon .path=${option.path}></ha-svg-icon>`
: option.icon
? html`<ha-icon .icon=${option.icon}></ha-icon> `
: nothing}
${option.label && !this.hideLabel
? html`<span>${option.label}</span>`
: nothing}
</div>
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
--control-select-color: var(--primary-color);
--control-select-focused-opacity: 0.2;
--control-select-selected-opacity: 1;
--control-select-background: var(--disabled-color);
--control-select-background-opacity: 0.2;
--control-select-thickness: 40px;
--control-select-border-radius: 10px;
--control-select-padding: 4px;
--control-select-button-border-radius: calc(
var(--control-select-border-radius) - var(--control-select-padding)
);
--mdc-icon-size: 20px;
height: var(--control-select-thickness);
width: 100%;
border-radius: var(--control-select-border-radius);
outline: none;
transition: box-shadow 180ms ease-in-out;
font-style: normal;
font-weight: 500;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
:host(:focus-visible) {
box-shadow: 0 0 0 2px var(--control-select-color);
}
:host([vertical]) {
width: var(--control-select-thickness);
height: 100%;
}
.container {
position: relative;
height: 100%;
width: 100%;
border-radius: var(--control-select-border-radius);
transform: translateZ(0);
overflow: hidden;
display: flex;
flex-direction: row;
padding: var(--control-select-padding);
box-sizing: border-box;
}
.container::before {
position: absolute;
content: "";
top: 0;
left: 0;
height: 100%;
width: 100%;
background: var(--control-select-background);
opacity: var(--control-select-background-opacity);
}
.container > *:not(:last-child) {
margin-right: var(--control-select-padding);
margin-inline-end: var(--control-select-padding);
margin-inline-start: initial;
direction: var(--direction);
}
.option {
cursor: pointer;
position: relative;
flex: 1;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--control-select-button-border-radius);
overflow: hidden;
color: var(--primary-text-color);
/* For safari border-radius overflow */
z-index: 0;
}
.content > *:not(:last-child) {
margin-bottom: 4px;
}
.option::before {
position: absolute;
content: "";
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: var(--control-select-color);
opacity: 0;
transition: background-color ease-in-out 180ms, opacity ease-in-out 80ms;
}
.option.focused::before,
.option:hover::before {
opacity: var(--control-select-focused-opacity);
}
.option.selected {
color: white;
}
.option.selected::before {
opacity: var(--control-select-selected-opacity);
}
.option .content {
position: relative;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
padding: 2px;
width: 100%;
box-sizing: border-box;
}
.option .content span {
display: block;
width: 100%;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
:host([vertical]) {
width: var(--control-select-thickness);
height: auto;
}
:host([vertical]) .container {
flex-direction: column;
}
:host([vertical]) .container > *:not(:last-child) {
margin-right: initial;
margin-inline-end: initial;
margin-bottom: var(--control-select-padding);
}
:host([disabled]) {
--control-select-color: var(--disabled-color);
--control-select-focused-opacity: 0;
}
:host([disabled]) .option {
cursor: not-allowed;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-control-select": HaControlSelect;
}
}

View File

@@ -29,19 +29,6 @@ const A11Y_KEY_CODES = new Set([
"End",
]);
const getPercentageFromEvent = (e: HammerInput, vertical: boolean) => {
if (vertical) {
const y = e.center.y;
const offset = e.target.getBoundingClientRect().top;
const total = e.target.clientHeight;
return Math.max(Math.min(1, 1 - (y - offset) / total), 0);
}
const x = e.center.x;
const offset = e.target.getBoundingClientRect().left;
const total = e.target.clientWidth;
return Math.max(Math.min(1, (x - offset) / total), 0);
};
@customElement("ha-control-slider")
export class HaControlSlider extends LitElement {
@property({ type: Boolean, reflect: true })
@@ -157,7 +144,7 @@ export class HaControlSlider extends LitElement {
});
this._mc.on("panmove", (e) => {
if (this.disabled) return;
const percentage = getPercentageFromEvent(e, this.vertical);
const percentage = this._getPercentageFromEvent(e);
this.value = this.percentageToValue(percentage);
const value = this.steppedValue(this.value);
fireEvent(this, "slider-moved", { value });
@@ -165,7 +152,7 @@ export class HaControlSlider extends LitElement {
this._mc.on("panend", (e) => {
if (this.disabled) return;
this.pressed = false;
const percentage = getPercentageFromEvent(e, this.vertical);
const percentage = this._getPercentageFromEvent(e);
this.value = this.steppedValue(this.percentageToValue(percentage));
fireEvent(this, "slider-moved", { value: undefined });
fireEvent(this, "value-changed", { value: this.value });
@@ -173,7 +160,7 @@ export class HaControlSlider extends LitElement {
this._mc.on("singletap", (e) => {
if (this.disabled) return;
const percentage = getPercentageFromEvent(e, this.vertical);
const percentage = this._getPercentageFromEvent(e);
this.value = this.steppedValue(this.percentageToValue(percentage));
fireEvent(this, "value-changed", { value: this.value });
});
@@ -234,6 +221,19 @@ export class HaControlSlider extends LitElement {
fireEvent(this, "value-changed", { value: this.value });
}
private _getPercentageFromEvent = (e: HammerInput) => {
if (this.vertical) {
const y = e.center.y;
const offset = e.target.getBoundingClientRect().top;
const total = e.target.clientHeight;
return Math.max(Math.min(1, 1 - (y - offset) / total), 0);
}
const x = e.center.x;
const offset = e.target.getBoundingClientRect().left;
const total = e.target.clientWidth;
return Math.max(Math.min(1, (x - offset) / total), 0);
};
protected render(): TemplateResult {
return html`
<div
@@ -244,13 +244,13 @@ export class HaControlSlider extends LitElement {
})}
>
<div class="slider-track-background"></div>
<slot name="background"></slot>
${this.mode === "cursor"
? this.value != null
? html`
<div
class=${classMap({
"slider-track-cursor": true,
vertical: this.vertical,
})}
></div>
`
@@ -259,7 +259,6 @@ export class HaControlSlider extends LitElement {
<div
class=${classMap({
"slider-track-bar": true,
vertical: this.vertical,
[this.mode ?? "start"]: true,
"show-handle": this.showHandle,
})}
@@ -312,6 +311,13 @@ export class HaControlSlider extends LitElement {
background: var(--control-slider-background);
opacity: var(--control-slider-background-opacity);
}
::slotted([slot="background"]) {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.slider .slider-track-bar {
--border-radius: var(--control-slider-border-radius);
--handle-size: 4px;
@@ -369,7 +375,7 @@ export class HaControlSlider extends LitElement {
left: var(--handle-margin);
}
.slider .slider-track-bar.vertical {
:host([vertical]) .slider .slider-track-bar {
bottom: 0;
left: 0;
transform: translate3d(
@@ -379,7 +385,7 @@ export class HaControlSlider extends LitElement {
);
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.slider .slider-track-bar.vertical:after {
:host([vertical]) .slider .slider-track-bar:after {
top: var(--handle-margin);
right: 0;
left: 0;
@@ -387,7 +393,7 @@ export class HaControlSlider extends LitElement {
width: 50%;
height: var(--handle-size);
}
.slider .slider-track-bar.vertical.end {
:host([vertical]) .slider .slider-track-bar.end {
top: 0;
bottom: initial;
transform: translate3d(
@@ -397,7 +403,7 @@ export class HaControlSlider extends LitElement {
);
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.slider .slider-track-bar.vertical.end::after {
:host([vertical]) .slider .slider-track-bar.end::after {
top: initial;
bottom: var(--handle-margin);
}
@@ -426,13 +432,14 @@ export class HaControlSlider extends LitElement {
bottom: 0;
left: calc(var(--value, 0) * (100% - var(--cursor-size)));
width: var(--cursor-size);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.slider .slider-track-cursor:after {
height: 50%;
width: var(--handle-size);
}
.slider .slider-track-cursor.vertical {
:host([vertical]) .slider .slider-track-cursor {
top: initial;
right: 0;
left: 0;
@@ -440,7 +447,7 @@ export class HaControlSlider extends LitElement {
height: var(--cursor-size);
width: 100%;
}
.slider .slider-track-cursor.vertical:after {
:host([vertical]) .slider .slider-track-cursor:after {
height: var(--handle-size);
width: 50%;
}

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