Compare commits

...

244 Commits

Author SHA1 Message Date
Bram Kragten
d06ffeeede Bumped version to 20210813.0 2021-08-13 19:42:25 +02:00
Bram Kragten
3479fb9d94 Add gas to energy dashboard (#9787) 2021-08-13 10:39:20 -07:00
Paulus Schoutsen
304bd002ae Netto -> Net (#9798) 2021-08-13 08:48:23 +02:00
Bram Kragten
5dad18c85f Make time inputs the same through the UI (#9766) 2021-08-12 22:52:26 +02:00
Bram Kragten
19e4c0657a Add battery to energy dashboard (#9757) 2021-08-12 08:40:21 -07:00
Bram Kragten
44548fdc33 Fix tracing graph (#9782) 2021-08-12 15:59:57 +02:00
Bram Kragten
d8929074b5 Use correct vars for gauge colors (#9727) 2021-08-12 15:59:42 +02:00
Bram Kragten
e11532ae92 Update translations (#9784) 2021-08-12 12:38:52 +02:00
Joakim Sørensen
eff48acdf4 Rename snapshot -> backup (#9393)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-12 11:56:13 +02:00
Onur-d
a9850f9641 Add max-width for Logos to 100% (#9779) 2021-08-11 17:19:12 +02:00
Joakim Sørensen
aab0b8a3ce Request darktheme optimized brand images if dark theme is used (#9777) 2021-08-11 15:15:12 +02:00
Philip Allgaier
b12e062d94 Fix "wan't" typos in energy panel (#9775) 2021-08-11 09:32:32 +02:00
J. Nick Koston
b36e342f15 Pass the width and height when requesting camera images (#9683)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-10 21:42:37 -07:00
Bram Kragten
f686816c86 Cleanup of tracing graph (#9564) 2021-08-11 01:23:07 +02:00
Bram Kragten
dc50e54afc Add period selection to energy dashboard (#9756) 2021-08-11 01:22:27 +02:00
Paulus Schoutsen
3897e3d452 Fix language string (#9767) 2021-08-10 21:22:38 +02:00
Bram Kragten
2557b03b11 Catch failing to fetch forecasts (#9765) 2021-08-10 19:47:09 +02:00
Paulus Schoutsen
29d29a337f Do not query energy prefs if it's not loaded (#9763) 2021-08-10 10:40:51 -07:00
Bram Kragten
34f8e5e28d Bumped version to 20210809.0 2021-08-09 20:44:31 +02:00
Bram Kragten
afd1a68c62 Change some wording (#9742) 2021-08-09 20:43:54 +02:00
Bram Kragten
dbcf1cb907 Group solar forecasts by hour (#9728) 2021-08-09 17:56:25 +02:00
Paulus Schoutsen
9ca64f9789 Show reconnecting when not connected and auto-connect is on (#9738) 2021-08-07 11:46:26 +02:00
Bram Kragten
4c247ac49d Merge branch 'master' into dev 2021-08-04 20:18:00 +02:00
Bram Kragten
e8a406526b Bumped version to 20210804.0 2021-08-04 20:17:05 +02:00
Bram Kragten
7fcea16c6b Increase font size tooltips + position then static (#9713) 2021-08-04 09:19:38 -07:00
Bram Kragten
028b799d2c Default energy collection key should not be defined (#9710) 2021-08-04 08:25:42 -07:00
Bram Kragten
3485296e23 Fix typo (#9707) 2021-08-04 12:36:15 +02:00
Paulus Schoutsen
03078cdd45 TOML 2021-08-03 22:05:14 -07:00
Paulus Schoutsen
740310800d SET NODE_OPTIONS 2021-08-03 22:05:14 -07:00
Paulus Schoutsen
6d7c558482 TOML 2021-08-03 22:02:38 -07:00
Paulus Schoutsen
fdb10515c3 SET NODE_OPTIONS 2021-08-03 22:00:13 -07:00
Paulus Schoutsen
5a0f13c485 Specify YARN version on netlify (#9705) 2021-08-03 21:56:02 -07:00
Paulus Schoutsen
80b330ad7b Specify YARN version on netlify (#9705) 2021-08-03 21:55:15 -07:00
Bram Kragten
d929e1c134 Add support for statistics card to demo (#9703) 2021-08-03 15:16:21 -07:00
Bram Kragten
5e40dcdc38 Add support for statistics card to demo (#9703) 2021-08-03 15:16:08 -07:00
Paulus Schoutsen
1dd3e2a83b 20210803.2 (#9704)
* Add energy distribution card to arsaboo demo (#9702)

* Add energy distribution card to arsaboo demo

* Make link dashboard conform

* Bumped version to 20210803.2
2021-08-03 15:15:21 -07:00
Paulus Schoutsen
a62742fad9 Bumped version to 20210803.2 2021-08-03 15:14:46 -07:00
Paulus Schoutsen
1f9c45b11c Add energy distribution card to arsaboo demo (#9702)
* Add energy distribution card to arsaboo demo

* Make link dashboard conform
2021-08-03 22:09:08 +00:00
Bram Kragten
68bec5e158 Merge pull request #9701 from home-assistant/dev
20210803.1
2021-08-03 23:36:27 +02:00
Bram Kragten
37f1bd7d63 Bumped version to 20210803.1 2021-08-03 23:22:46 +02:00
Bram Kragten
5ba24211e2 Add messages when no data (#9700)
* Add messages when no data

* 2 hours

* comment
2021-08-03 23:22:32 +02:00
Bram Kragten
d699647418 Fix calculate sum growth + refreshing too often (#9699) 2021-08-03 23:19:26 +02:00
Bram Kragten
b246502cb6 Energy demo (#9698)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-03 09:50:31 -07:00
Bram Kragten
9b33ead8aa Final energy tweaks (#9694) 2021-08-03 08:54:10 -07:00
Milan Meulemans
32e8c1dc6d Remove energy currency translation (#9695) 2021-08-03 11:00:21 +00:00
Paulus Schoutsen
e09ef7862e Merge pull request #9692 from home-assistant/dev 2021-08-02 21:10:24 -07:00
Paulus Schoutsen
85420304d0 Bumped version to 20210803.0 2021-08-02 20:51:41 -07:00
Bram Kragten
1c097a669d Add currency to onboarding (#9691) 2021-08-02 21:02:57 +00:00
Bram Kragten
4e1497c5da Change layout of period selector (#9688) 2021-08-02 13:57:59 -07:00
Bram Kragten
49d426675f Masonry (#9689) 2021-08-02 13:57:39 -07:00
Paulus Schoutsen
dc6ac668b4 Merge pull request #9687 from home-assistant/add-description-devices-dialog
Add description to statistic picker in add device dialog
2021-08-02 10:30:24 -07:00
Paulus Schoutsen
4ee24b0845 Add description to statistic picker in add device dialog 2021-08-02 10:18:59 -07:00
Paulus Schoutsen
ba20aef206 Merge pull request #9685 from home-assistant/dev 2021-08-02 10:07:14 -07:00
Paulus Schoutsen
41ef6133c1 Bumped version to 20210802.0 2021-08-02 09:51:10 -07:00
Joakim Sørensen
50bd5ee8f7 Add my support for energy (#9681)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-02 09:50:47 -07:00
Joakim Sørensen
285f3fe330 Fix text color for hass-tabs-subpage with narrow (#9680) 2021-08-02 09:50:24 -07:00
Bram Kragten
4d01199986 Clear all energy collection prefs (#9684) 2021-08-02 09:39:01 -07:00
Paulus Schoutsen
bcc0052fe0 Update text 2021-08-02 08:45:05 -07:00
Paulus Schoutsen
4b592d81bd Simplify date picker and put in sidebar (#9677)
* Simplify date picker and put in sidebar

* Convert picker to reusable component

* Put date picker in top bar on desktop

* Chefs
2021-08-02 08:18:49 -07:00
Bram Kragten
884e323288 Make clear it is showing today (#9679) 2021-08-02 07:22:36 -07:00
Bram Kragten
78b799dd05 Add cost stat ids (#9678) 2021-08-02 07:22:17 -07:00
Paulus Schoutsen
847fa2e700 Add energy distribution card to auto lovelace if energy configured (#9675)
* Add energy distribution card to auto lovelace if energy configured

* Add import

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-02 12:36:13 +00:00
Paulus Schoutsen
481a79e311 Allow specifying collection keys so energy dashboard has own (#9674) 2021-08-02 14:23:15 +02:00
Paulus Schoutsen
f19f2ff321 Add tooltips to the gauges (#9676) 2021-08-02 14:21:41 +02:00
Bram Kragten
6dc4d7bb70 Merge pull request #9673 from home-assistant/dev
20210801.0
2021-08-01 23:40:48 +02:00
Bram Kragten
83460a34f4 Bumped version to 20210801.0 2021-08-01 23:25:24 +02:00
Bram Kragten
2adbb47373 Refresh stats at 20 minutes past the hour every hour (#9667) 2021-08-01 21:24:41 +00:00
Bram Kragten
2159a5419a Fix device energy graph height (#9666)
* Fix device energy graph height

* Update state-history-chart-timeline.ts

* Update hui-energy-devices-graph-card.ts
2021-08-01 13:59:34 -07:00
Bram Kragten
044d6a15d9 Add view type selector to view editor (#9671) 2021-08-01 22:55:51 +02:00
Bram Kragten
b6055062c6 Don's start at zero so growth is beter visible + calc sum state correctly (#9672) 2021-08-01 22:55:41 +02:00
Bram Kragten
6fd85e043b Sidebar view: Allow to move card position from UI (#9669) 2021-08-01 18:17:34 +02:00
Bram Kragten
e07ac52356 Add UOM to stats chart, fix coloring of bands (#9665) 2021-07-31 20:36:27 +02:00
Paulus Schoutsen
0f16ba9325 Speed up data loading and allow embedding individual energy cards (#9660)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-31 18:33:41 +02:00
Bram Kragten
378e6d28bc Merge pull request #9659 from home-assistant/dev 2021-07-30 22:29:51 +02:00
Bram Kragten
539d2b880c Bumped version to 20210730.0 2021-07-30 22:14:21 +02:00
Bram Kragten
2982adbfa7 Fix return compensation not being negative (#9654) 2021-07-30 12:57:27 -07:00
Bram Kragten
5147dff670 Sidebar view: Move all cards to 1 column on mobile (#9656) 2021-07-30 12:57:01 -07:00
Bram Kragten
2cdf78c504 Add grid neutrality gauge (#9655) 2021-07-30 12:55:58 -07:00
Bram Kragten
cfad45b7c2 fix and finish statistics card (#9658) 2021-07-30 12:52:38 -07:00
Bram Kragten
5234e9bce5 still round numbers when not formatting them (#9653) 2021-07-30 17:36:31 +02:00
Bram Kragten
0ed5454d02 fix self consumed solar (#9651) 2021-07-30 12:28:47 +02:00
Thomas Lovén
03080973be Add needle option to ha-gauge and gauge card (#9637) 2021-07-30 10:51:21 +02:00
Paulus Schoutsen
a4f51b0cb3 Fix label for device consumption (#9648) 2021-07-30 09:42:54 +02:00
Paulus Schoutsen
749079c1c3 Bumped version to 20210729.0 2021-07-29 12:02:34 -07:00
Bram Kragten
ae10ff42e1 Update state-history-chart-line.ts 2021-07-29 20:45:25 +02:00
Bram Kragten
d4cbdab4a3 Fix energy calculations (#9647)
* Fix calculations

* max.. not min...
2021-07-29 18:29:49 +00:00
Bram Kragten
1bd6392a4c Add text when no statistics found (#9642)
* Add text when no statistics found

* Update src/components/entity/ha-statistic-picker.ts

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

* fix typos

* Update src/components/entity/ha-statistic-picker.ts

* Prettier

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-07-29 20:26:22 +02:00
Bram Kragten
1531e99528 Energy dashboard tweaks and fixes (#9643)
* Energy dashboard tweaks and fixes

* Make headers smaller

* Change button styling in onboarding

* Disable add when no stat choosen

* Oops

* Update hui-energy-carbon-consumed-gauge-card.ts

* Update hui-energy-distribution-card.ts
2021-07-29 10:44:33 -07:00
Julien Ehrhart
6e7af18494 Add support for DEVICE_CLASS_MONETARY (#9640) 2021-07-29 15:20:12 +00:00
Paulus Schoutsen
e1bae65aeb Remove currency from initial config (#9638) 2021-07-29 17:12:49 +02:00
Paulus Schoutsen
65bfdf94c9 Bumped version to 20210728.0 2021-07-28 11:05:28 -07:00
Paulus Schoutsen
9a92825954 Move energy panel up in sidebar (#9636)
* Move energy panel up in sidebar

* Remove headers from wizard

* Update text

* Always show all configured devices

* Make leaf clickable

* Bump HAWS
2021-07-28 11:05:00 -07:00
Paulus Schoutsen
469faf509b Remove the energy summary card since it's already in other cards (#9635) 2021-07-28 10:46:59 -07:00
Paulus Schoutsen
f87d4a5ab6 If we have solar defined, always make sure sum is number so it shows placeholders (#9634) 2021-07-28 10:46:51 -07:00
Bram Kragten
9c31b749d7 Format monetary sensor as currency (#9633) 2021-07-28 09:34:03 -07:00
Paulus Schoutsen
521d5df064 Show solar forecast even if no prod stats available (#9632) 2021-07-28 09:20:31 -07:00
Paulus Schoutsen
4d330fba8a Use historic CO2 data into account (#9626)
* Use historic CO2 data into account

* Also add to gauge

* Apply suggestions from code review

* Format

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-28 09:20:06 -07:00
Bram Kragten
2e04a55d5c Add statistics card to card picker (#9631)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
2021-07-28 08:41:18 -07:00
Bram Kragten
0d9d0aa18b Add legend to electricity graph, fix text color in table (#9629) 2021-07-28 08:40:43 -07:00
Bram Kragten
f8dd1795bc Add animation to distribution (#9625) 2021-07-28 08:39:12 -07:00
Bram Kragten
1d7007584c Use currency from core config (#9628) 2021-07-28 08:38:04 -07:00
Charles Garwood
8cd9f891fb Add remove failed node support to Z-Wave JS devices (#9560)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-28 13:38:50 +00:00
Franck Nijhof
6ab0f1db57 Add currency core configuration (#9620)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-07-28 12:48:39 +02:00
Paulus Schoutsen
37d754d069 Bumped version to 20210727.0 2021-07-27 15:25:12 -07:00
Charles Garwood
e12b194d41 Add Z-Wave JS Heal Node wizard (#9562) 2021-07-28 00:16:19 +02:00
Michelle Fuchs
07d7fa26fe Show value of 'Before'-time in automation editor #9142 (#9598) 2021-07-28 00:13:38 +02:00
Bram Kragten
73b9b87ef3 Update energy dashboard (#9624) 2021-07-27 13:57:09 -07:00
Bram Kragten
0c0091375c Fix grid onboarding (#9621) 2021-07-27 21:34:43 +02:00
Bram Kragten
21a29ed3a5 Statistic sums requires at least 2 values (#9616) 2021-07-26 22:47:50 +00:00
Bram Kragten
a6312f4279 Fix sidebar view edit mode (#9615) 2021-07-26 22:11:34 +00:00
Bram Kragten
f459abdf85 Fix statistics graph (#9607) 2021-07-26 15:03:31 -07:00
Bram Kragten
586fa1d0f0 Fix zoom setting in maps card (#9613)
Fixes #9611
2021-07-26 15:02:50 -07:00
Bram Kragten
bf4192a1f0 Bump Vaadin elements (#9609) 2021-07-26 15:02:23 -07:00
Bram Kragten
ac31eedf65 Bump material elements (#9610) 2021-07-26 15:02:01 -07:00
Bram Kragten
b05dc5141c Fix chart tooltip footer always rendering (#9614) 2021-07-26 15:01:32 -07:00
Raman Gupta
32c6fb14dd Re-add success/failure indicator on call service button in dev tools (#9600)
* Re-add success/failure indicator on call service button in dev tools

* move success outside of try block

* Export HaProgressButton
2021-07-26 23:59:49 +02:00
Raman Gupta
982c940381 Try to fix download of zwave_js logs (#9612) 2021-07-26 23:26:22 +02:00
Bram Kragten
a7a8aaa887 Merge pull request #9608 from home-assistant/dev
20210726.0
2021-07-26 23:04:05 +02:00
Philip Allgaier
bf83a9980e Add bottom margin to <ha-map> in more-info-person (#9604) 2021-07-26 22:45:23 +02:00
Bram Kragten
11be603ed3 Bumped version to 20210726.0 2021-07-26 22:18:41 +02:00
Bram Kragten
a432cf8405 Fix selected icon of picker elements (#9606) 2021-07-26 22:17:29 +02:00
Bram Kragten
9dd6b3b72d Add Energy panel (#9445)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-07-26 09:57:59 -07:00
Thomas Lovén
faca62b55f Avoid gaps in grid card with conditional (#9507)
* Avoid gaps in grid card with conditional. Fix #9433

* Rename hidden parameter

* Restore devcontainer.json

* Fix accidental change

* Update src/panels/lovelace/cards/hui-grid-card.ts

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

* Prettier

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-26 14:43:14 +02:00
J. Nick Koston
a339de89f5 Add support for lock unlocking,locking and jammed states (#9537) 2021-07-20 10:52:24 -10:00
Charles Garwood
e40c90e9c0 Translate Z-Wave JS Re-Interview Device button (#9566) 2021-07-19 11:23:10 +02:00
Bram Kragten
3f447bb8a7 Bump Polymer (#9525)
* Bump Polymer

* Remove formatting from patch

* Cleanup

* Fix Vaadin

* Fix typing
2021-07-16 01:23:09 +02:00
Bram Kragten
21dca3fbf8 Use patch for @lit-labs/virtualizer (#9524)
* Use patch for `@lit-labs/virtualizer`

* Re-organize

* Use original for patch

* Also patch import of EventTarget polyfill

* Delete EventTarget-ponyfill.js

* Prettier
2021-07-16 01:16:24 +02:00
Bram Kragten
1078bb4287 Add statistics-graph-card (#9479)
* Add `statistics-graph-card`

* Make variable names clearer
2021-07-16 01:16:02 +02:00
Charles Garwood
daeed06e70 Clean up Z-Wave JS dialog CSS (#9561) 2021-07-16 01:13:00 +02:00
Bram Kragten
1206e2d75f Change Data Entry Flow loading step description logic + cleanups (#9558)
* Change Data Entry Flow loading step description logic + cleanups

Fixes #6251

* Lint

* Address comment
2021-07-15 21:07:25 +02:00
Bram Kragten
cc81239b9d Add struct for fire-dom-event action (#9556) 2021-07-15 12:08:33 +02:00
Bram Kragten
e797c01761 Update lint rules (#9563)
Updated deps

Added `unused-imports`, prefer arrow, and import order
2021-07-15 12:08:04 +02:00
Charles Garwood
12f7366968 Replace home ID with config entry title in Z-Wave device Info (#9488) 2021-07-13 18:05:22 +02:00
Bram Kragten
b7fd7abe85 increase memory demo action (#9553) 2021-07-13 10:15:59 +02:00
Jason Hunter
a5e1f3d165 bump hls.js (#9546)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-13 09:00:51 +02:00
Joakim Sørensen
212d047ada Place Z-Wave JS stages in grid (#9548) 2021-07-12 12:44:07 -04:00
Bram Kragten
2e16127fde Inline mdi icons with babel plugin + bump build deps (#9498) 2021-07-12 09:26:07 -07:00
Charles Garwood
e8b53a619d Show parameter number on ZWaveJS device config panel (#9494) 2021-07-12 09:41:54 +02:00
posixx
df1ca1fd96 HA frontend change for alarm panel vacation mode (#8326)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-08 16:21:09 +02:00
Bram Kragten
8f85132d48 Download translations on release (#9530)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-07-08 13:04:38 +00:00
Marius
349144599c Change text on Yaml configuration reloading (#9529)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-08 13:04:17 +00:00
Thomas Lovén
979093923b Colorize trace paths for choose without explicit default case (#9527)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-08 00:15:20 +02:00
Thomas Lovén
137f8ad4cb Revert "Colorize trace paths for choose without explicit default case"
This reverts commit c1f462b8f8.
2021-07-07 21:39:33 +00:00
Thomas Lovén
c1f462b8f8 Colorize trace paths for choose without explicit default case 2021-07-07 21:28:13 +00:00
Bram Kragten
6701c4c371 Dedupe dependencies (#9523)
+ yarn bump + CI check for duplicate dependencies
2021-07-07 17:31:37 +02:00
Bram Kragten
fc6e459c09 Upgrade to yarn 2 (#9500) 2021-07-07 16:33:24 +02:00
Philip Allgaier
9e28b3447e Fixed "ID" spelling in trigger field label (#9518) 2021-07-07 11:07:57 +02:00
Bram Kragten
5c737e1969 Merge pull request #9517 from home-assistant/dev 2021-07-07 09:58:19 +02:00
Bram Kragten
569765e77e Bumped version to 20210707.0 2021-07-07 09:43:20 +02:00
Bram Kragten
bc0d63ed12 Better fix for Safari IBD bug (#9514)
* Better fix for Safari IBD bug

* comment
2021-07-07 09:42:41 +02:00
GitHub Action
02f9893522 Translation update 2021-07-07 00:47:35 +00:00
Bram Kragten
b4bbe63f0f Fix device trigger clearing trigger id (#9511) 2021-07-06 11:53:21 +02:00
Bram Kragten
fabbcac99f Merge pull request #9510 from home-assistant/dev 2021-07-06 11:09:42 +02:00
Bram Kragten
b1b5ab6949 Bumped version to 20210706.0 2021-07-06 10:47:41 +02:00
Bram Kragten
4b9487183b Add tracing to scripts (#9486) 2021-07-06 10:46:51 +02:00
Bram Kragten
de5a817953 Add UI for trigger condition (#9505) 2021-07-06 10:43:07 +02:00
GitHub Action
4970f640fa Translation update 2021-07-06 00:47:02 +00:00
Bram Kragten
18996535b7 Fix race in translations loading (#9499) 2021-07-05 11:05:49 +02:00
GitHub Action
2a1e31b5e9 Translation update 2021-07-05 00:47:09 +00:00
GitHub Action
8ca9a0f409 Translation update 2021-07-04 00:47:10 +00:00
GitHub Action
fcc89a67ba Translation update 2021-07-03 00:46:50 +00:00
GitHub Action
1f377d43c5 Translation update 2021-07-02 00:47:14 +00:00
Joakim Sørensen
30d6c68908 Fix writing supervisor entrypoint (#9489) 2021-07-01 11:34:22 +02:00
Bram Kragten
dc781da93a Use ES5 build for Supervisor on Safari 12 and below (#9485) 2021-07-01 09:27:02 +02:00
Bram Kragten
36c20e4348 Limit height of charts to 400px (#9487) 2021-07-01 07:54:17 +02:00
GitHub Action
4466950bb8 Translation update 2021-07-01 00:47:12 +00:00
Joakim Sørensen
be29828454 Change path for codespaces (#9484) 2021-06-30 15:58:01 +02:00
Bram Kragten
7bab245073 Merge pull request #9483 from home-assistant/dev 2021-06-30 12:17:54 +02:00
Bram Kragten
f5dcf0b760 Bumped version to 20210630.0 2021-06-30 12:03:07 +02:00
Bram Kragten
8141f78a92 Use ES5 build for Safari 12 and below (#9482) 2021-06-30 12:02:01 +02:00
Joakim Sørensen
be244b3d00 Rename hassos -> haos (#9477) 2021-06-30 12:00:33 +02:00
Bram Kragten
805f5ff9b6 Recreate columns if cards change (#9480) 2021-06-30 11:52:36 +02:00
Bram Kragten
76daeb7e55 Fix wait for not loaded panel (#9478) 2021-06-30 11:50:49 +02:00
GitHub Action
9594c8106e Translation update 2021-06-30 00:47:15 +00:00
Joakim Sørensen
ed4052365c Allow placeholders in config and option flows (#9314) 2021-06-30 01:09:18 +02:00
Joakim Sørensen
377ebadc10 Show note about integrations not in UI even for non-advanced (#9457)
* Show note about integrations not in UI even for non-advanced

* Address comments
2021-06-30 01:08:58 +02:00
Charles Garwood
ed4809b71e Handle config entry not loaded on Z-Wave JS config panel (#9451)
* Handle config entry not loaded on Z-Wave JS config panel

* Move ERROR_STATES to data/config_entries and import
2021-06-29 19:04:42 -04:00
Shane Qi
db37dffdbb Fixed Logbook Card/Panel/Dialog Incorrect Entires for input_datetime Entities (#9399)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-30 00:32:19 +02:00
Bram Kragten
13cab7e301 Fix logbook card (#9476) 2021-06-29 18:32:51 +02:00
Charles Garwood
0a50fc66e5 Add ZWave JS heal network UI (#9449)
* Add Z-Wave JS heal network dialog

* progress bar tweak

* tweak package.json

* typing tweak

* Update yarn.lock

* Align versions

* address review comments

* Use indeterminate linear-progress instead of circular-progress

* cleanup circular-progress

* prettier

* additional review cleanup

* subscribe to status update if heal already running

* more cleanup

* more cleanup

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-29 11:14:50 -04:00
uvjustin
a3d1a3566d Fix ha-hls-player cleanup for lit 2 (#9388)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-29 15:44:18 +02:00
Philip Allgaier
ba0be927ed Set min value for history graph card rendered hours (#9464) 2021-06-29 15:01:42 +02:00
Philip Allgaier
4260606267 Set minimum = 1 hours to show (#9466) 2021-06-29 15:01:18 +02:00
Philip Allgaier
4665db4f27 Fix integration card rename dialog logic (#9467) 2021-06-29 15:00:27 +02:00
Franck Nijhof
43503ba085 Fix number entity row availability when state unknown (#9475) 2021-06-29 14:57:39 +02:00
Joakim Sørensen
0a83a704f1 Ignore previous versions in add-on changelog (#9474) 2021-06-29 14:57:25 +02:00
GitHub Action
08de941c90 Translation update 2021-06-29 00:47:16 +00:00
GitHub Action
62228ef144 Translation update 2021-06-28 00:47:17 +00:00
GitHub Action
9731257782 Translation update 2021-06-27 00:47:51 +00:00
GitHub Action
4ec9c9c16e Translation update 2021-06-26 00:46:57 +00:00
Franck Nijhof
45436731e2 Fix select entity disabled when no item selected (#9465) 2021-06-24 21:36:23 -07:00
GitHub Action
27730e65e7 Translation update 2021-06-25 00:47:31 +00:00
rianadon
a4aba93d57 Add input elements to login page for password managers (#9369) 2021-06-24 23:14:36 +02:00
Martin Hjelmare
d93db16963 Add button for zwave_js options flow (#9001)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-06-24 13:21:30 +02:00
GitHub Action
c327fe11b8 Translation update 2021-06-23 00:48:18 +00:00
GitHub Action
4fbc31d0b0 Translation update 2021-06-22 00:48:29 +00:00
Bram Kragten
9a4a1cb4ec Fix charts tooltips and legends (#9448) 2021-06-21 10:38:50 +02:00
Joakim Sørensen
202d6957bc Allow clearing values in optional selects (#9442)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-21 09:41:38 +02:00
Joakim Sørensen
14fcff7774 Fix secrets schema (#9446) 2021-06-21 09:41:06 +02:00
GitHub Action
2c9aa1cab4 Translation update 2021-06-21 00:48:39 +00:00
GitHub Action
7745c10d07 Translation update 2021-06-20 00:48:37 +00:00
Franck Nijhof
c1d571de42 Add Select entity (#9422)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-19 13:26:19 +02:00
GitHub Action
cecb66451c Translation update 2021-06-19 00:48:32 +00:00
Bram Kragten
0ef3421fa2 Bump chartjs to version 3 (#9401) 2021-06-18 11:15:07 +02:00
GitHub Action
f88e238d41 Translation update 2021-06-18 00:48:41 +00:00
Raman Gupta
ce3c8f264d Update zwave_js log subscription to handle core changes (#9262) 2021-06-17 17:19:18 +02:00
Bram Kragten
9fbd594f37 Check if /proc/version exists (#9438) 2021-06-17 15:23:20 +02:00
Bram Kragten
5ad95cad90 Support white color mode (#9386) 2021-06-17 15:23:08 +02:00
GitHub Action
7e507b40c4 Translation update 2021-06-17 00:48:10 +00:00
Joakim Sørensen
446a9b5c02 Don't clear action on expected error (#9428) 2021-06-16 11:13:30 +02:00
GitHub Action
e02e61384e Translation update 2021-06-16 00:48:25 +00:00
Raman Gupta
5deb570fdf Add button to download logs from zwave_js logs page (#9395) 2021-06-16 00:02:40 +02:00
Joakim Sørensen
915c46f144 Fix add-on configuration validation (#9424) 2021-06-15 21:00:28 +02:00
Philip Allgaier
30d6c5eaf3 Gracefully handle logbook retrieval errors (#9377) 2021-06-15 19:54:18 +02:00
Philip Allgaier
6e50d1166a Only attempt to get "trace/contexts" if admin (#9378)
* Only attempt to get "trace/contexts" if admin

* Changes from review
2021-06-15 16:38:37 +02:00
Philip Allgaier
0e3eed0563 Fix supervisor text "error_addon_not_started" (#9412) 2021-06-15 15:38:14 +02:00
Joakim Sørensen
1b1676cecc Use poll for webpack for WSL (#9425) 2021-06-15 15:34:15 +02:00
GitHub Action
d911fe6a0b Translation update 2021-06-15 00:48:36 +00:00
Bram Kragten
22253a3385 Add header and description to progress options flow (#9423) 2021-06-15 01:10:24 +02:00
GitHub Action
38640c99e3 Translation update 2021-06-14 00:48:26 +00:00
GitHub Action
d6df8bddea Translation update 2021-06-13 00:48:28 +00:00
GitHub Action
ddfc4bd98e Translation update 2021-06-12 00:48:12 +00:00
Shane Qi
3d6674325c Fix the issue that logbook card doesn't translate context.user_id to name if it's a user's id. (#9383)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-10 14:51:24 +02:00
Bram Kragten
194829f5b1 Generalize map (#9331)
* Generalize map

* Fix path opacity

* Add fitZones
2021-06-10 14:22:44 +02:00
GitHub Action
11a77253f4 Translation update 2021-06-10 00:49:02 +00:00
GitHub Action
67be2343f8 Translation update 2021-06-09 00:48:19 +00:00
Joakim Sørensen
e9b1b3d853 Fix issues with restoring snapshot during onboarding (#9385)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-08 17:57:53 +02:00
Bram Kragten
8a33d174d7 FIx selecting service/url path action after choosing default action (#9376)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-06-08 14:24:58 +02:00
Joakim Sørensen
226d6216b7 Build wheels for 3.9-alpine3.13 (#9390) 2021-06-08 13:07:02 +02:00
GitHub Action
1925bb01be Translation update 2021-06-08 00:49:18 +00:00
Bram Kragten
82a4806e01 Change line logic 2021-06-07 10:45:57 +02:00
Joakim Sørensen
ce419fae7b Add password confirmation to snapshot creation (#9349)
* Add password confirmation to snapshot creation

* Remove confirm_password before sending

* change layout

* style changes

* Adjust styling

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-07 10:45:20 +02:00
Bram Kragten
8666e6baae Merge pull request #9358 from home-assistant/dev 2021-06-03 23:12:30 +02:00
Bram Kragten
2db9f33c41 Merge pull request #9334 from home-assistant/dev 2021-06-01 21:53:08 +02:00
Bram Kragten
3d788d6056 Merge pull request #9324 from home-assistant/dev 2021-06-01 11:56:02 +02:00
Paulus Schoutsen
7560f98d70 Merge pull request #9320 from home-assistant/dev 2021-05-31 15:55:09 -07:00
Paulus Schoutsen
1533c22d5c Merge pull request #9310 from home-assistant/dev 2021-05-30 20:30:44 -07:00
Bram Kragten
cf03f103ab Merge pull request #9285 from home-assistant/dev 2021-05-28 12:45:21 +02:00
Bram Kragten
4a8a7c997e Merge pull request #9267 from home-assistant/dev 2021-05-26 17:33:32 +02:00
Bram Kragten
9612bc78fe Merge pull request #9097 from home-assistant/dev 2021-05-04 23:21:05 +02:00
Bram Kragten
2b86137388 Merge pull request #9079 from home-assistant/dev 2021-05-03 16:16:58 +02:00
Paulus Schoutsen
8fdbe447c1 Merge pull request #9060 from home-assistant/dev
20210430.0
2021-04-30 12:43:33 -07:00
Bram Kragten
764ae7e0b6 Merge pull request #9045 from home-assistant/dev 2021-04-29 22:21:03 +02:00
Paulus Schoutsen
6b7e78320d Merge pull request #9024 from home-assistant/dev 2021-04-28 10:47:16 -07:00
581 changed files with 54442 additions and 34038 deletions

View File

@@ -35,55 +35,51 @@
"es6": true "es6": true
}, },
"rules": { "rules": {
"class-methods-use-this": 0, "class-methods-use-this": "off",
"new-cap": 0, "new-cap": "off",
"prefer-template": 0, "prefer-template": "off",
"object-shorthand": 0, "object-shorthand": "off",
"func-names": 0, "func-names": "off",
"prefer-arrow-callback": 0, "no-underscore-dangle": "off",
"no-underscore-dangle": 0, "strict": "off",
"strict": 0, "no-plusplus": "off",
"prefer-spread": 0, "no-bitwise": "error",
"no-plusplus": 0, "comma-dangle": "off",
"no-bitwise": 2, "vars-on-top": "off",
"comma-dangle": 0, "no-continue": "off",
"vars-on-top": 0, "no-param-reassign": "off",
"no-continue": 0, "no-multi-assign": "off",
"no-param-reassign": 0, "no-console": "error",
"no-multi-assign": 0, "radix": "off",
"no-console": 2, "no-alert": "off",
"radix": 0, "no-nested-ternary": "off",
"no-alert": 0, "prefer-destructuring": "off",
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"], "no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": 0, "prefer-promise-reject-errors": "off",
"import/order": 0, "import/prefer-default-export": "off",
"import/prefer-default-export": 0, "import/no-default-export": "off",
"import/no-unresolved": 0, "import/no-unresolved": "off",
"import/no-cycle": 0, "import/no-cycle": "off",
"import/extensions": [ "import/extensions": [
2, "error",
"ignorePackages", "ignorePackages",
{ "ts": "never", "js": "never" } { "ts": "never", "js": "never" }
], ],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"], "no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": 0, "object-curly-newline": "off",
"default-case": 0, "default-case": "off",
"wc/no-self-class": 0, "wc/no-self-class": "off",
"no-shadow": 0, "no-shadow": "off",
"@typescript-eslint/camelcase": 0, "@typescript-eslint/camelcase": "off",
"@typescript-eslint/ban-ts-comment": 0, "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-use-before-define": 0, "@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": 0, "@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": 0, "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [ "@typescript-eslint/naming-convention": [
0, "off",
{ {
"selector": "default", "selector": "default",
"format": ["camelCase", "snake_case"], "format": ["camelCase", "snake_case"],
@@ -101,9 +97,20 @@
"format": ["PascalCase"] "format": ["PascalCase"]
} }
], ],
"lit/attribute-value-entities": 0 "@typescript-eslint/no-unused-vars": "off",
}, "unused-imports/no-unused-vars": [
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"], "error",
"processor": "disable/disable", {
"ignorePatterns": ["src/resources/lit-virtualizer/*"] "vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off"
},
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable"
} }

View File

@@ -10,26 +10,21 @@ on:
- dev - dev
- master - master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setting up Node.js - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache path cache: yarn
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
@@ -42,51 +37,35 @@ jobs:
run: yarn run lint:types run: yarn run lint:types
- name: Run prettier - name: Run prettier
run: yarn run lint:prettier run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setting up Node.js - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache path cache: yarn
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Run Mocha - name: Run Mocha
run: npm run mocha run: yarn run mocha
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setting up Node.js - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache path cache: yarn
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
@@ -101,20 +80,11 @@ jobs:
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setting up Node.js - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache path cache: yarn
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:

View File

@@ -4,26 +4,22 @@ on:
push: push:
branches: branches:
- dev - dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setting up Node.js - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache path cache: yarn
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:

View File

@@ -6,9 +6,9 @@ on:
- published - published
env: env:
WHEELS_TAG: 3.8-alpine3.12
PYTHON_VERSION: 3.8 PYTHON_VERSION: 3.8
NODE_VERSION: 12.1 NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs: jobs:
release: release:
@@ -30,7 +30,15 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download Translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Build and release package - name: Build and release package
run: | run: |
python3 -m pip install twine python3 -m pip install twine
@@ -64,6 +72,9 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.8-alpine3.12"
- "3.9-alpine3.13"
steps: steps:
- name: Download requirements.txt - name: Download requirements.txt
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
@@ -73,7 +84,7 @@ jobs:
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@master uses: home-assistant/wheels@master
with: with:
tag: ${{ env.WHEELS_TAG }} tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }} wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@@ -1,8 +1,6 @@
name: Translations name: Translations
on: on:
schedule:
- cron: "30 0 * * *"
push: push:
branches: branches:
- dev - dev
@@ -10,7 +8,7 @@ on:
- src/translations/en.json - src/translations/en.json
env: env:
NODE_VERSION: 12 NODE_VERSION: 14
jobs: jobs:
upload: upload:
@@ -20,46 +18,8 @@ jobs:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Upload Translations - name: Upload Translations
run: | run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base ./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push

10
.gitignore vendored
View File

@@ -8,9 +8,15 @@ hass_frontend/*
dist dist
# yarn # yarn
.yarn .yarn/*
yarn-error.log !.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
node_modules/* node_modules/*
yarn-error.log
npm-debug.log npm-debug.log
# Python stuff # Python stuff

2
.nvmrc
View File

@@ -1 +1 @@
12.1 14

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
@@ -1,14 +1,15 @@
-let _ET, ET;
+let _ET;
+let ET;
export default async function EventTarget() {
- return ET || init();
+ return ET || init();
}
async function init() {
- _ET = window.EventTarget;
- try {
- new _ET();
- }
- catch (_a) {
- _ET = (await import('event-target-shim')).EventTarget;
- }
- return (ET = _ET);
+ _ET = window.EventTarget;
+ try {
+ new _ET();
+ } catch (_a) {
+ _ET = (await import("event-target-shim")).default.EventTarget;
+ }
+ return (ET = _ET);
}

View File

@@ -0,0 +1,34 @@
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
--- a/lib/legacy/class.js
+++ b/lib/legacy/class.js
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
// only proceed if the generated class' prototype has not been registered.
const generatedProto = PolymerGenerated.prototype;
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
- generatedProto.__hasRegisterFinished = true;
+ // make sure legacy lifecycle is called on the *element*'s prototype
+ // and not the generated class prototype; if the element has been
+ // extended, these are *not* the same.
+ const proto = Object.getPrototypeOf(this);
+ // Only set flag when generated prototype itself is registered,
+ // as this element may be extended from, and needs to run `registered`
+ // on all behaviors on the subclass as well.
+ if (proto === generatedProto) {
+ generatedProto.__hasRegisterFinished = true;
+ }
// ensure superclass is registered first.
super._registered();
// copy properties onto the generated class lazily if we're optimizing,
- if (legacyOptimizations) {
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
+ generatedProto.__hasCopiedProperties = true;
copyPropertiesToProto(generatedProto);
}
- // make sure legacy lifecycle is called on the *element*'s prototype
- // and not the generated class prototype; if the element has been
- // extended, these are *not* the same.
- const proto = Object.getPrototypeOf(this);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

55
.yarn/releases/yarn-2.4.2.cjs vendored Executable file

File diff suppressed because one or more lines are too long

9
.yarnrc.yml Normal file
View File

@@ -0,0 +1,9 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.2.cjs

View File

@@ -0,0 +1,170 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
module.exports = function inlineConstants(babel, options, cwd) {
const t = babel.types;
if (!Array.isArray(options.modules)) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `modules` array to be passed"
);
}
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
throw new TypeError(
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
);
}
const ignoreModuleNotFound = options.ignoreModuleNotFound;
const resolveExtensions = options.resolveExtensions;
const hasRelativeModules = options.modules.some(
(module) => module.startsWith(".") || module.startsWith("/")
);
const modules = Object.fromEntries(
options.modules.map((module) => {
const absolute = module.startsWith(".")
? require.resolve(module, { paths: [cwd] })
: module;
// eslint-disable-next-line import/no-dynamic-require
return [absolute, require(absolute)];
})
);
const toLiteral = (value) => {
if (typeof value === "string") {
return t.stringLiteral(value);
}
if (typeof value === "number") {
return t.numericLiteral(value);
}
if (typeof value === "boolean") {
return t.booleanLiteral(value);
}
if (value === null) {
return t.nullLiteral();
}
throw new Error(
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
);
};
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
if (!state.filename) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `filename` to be set for files"
);
}
if (resolveExtensions && resolveExtensionIndex !== undefined) {
value += resolveExtensions[resolveExtensionIndex];
}
try {
return require.resolve(value, { paths: [path.dirname(state.filename)] });
} catch (error) {
if (
error.code === "MODULE_NOT_FOUND" &&
resolveExtensions &&
(resolveExtensionIndex === undefined ||
resolveExtensionIndex < resolveExtensions.length - 1)
) {
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
return resolveAbsolute(value, state, resolveExtensionIdx);
}
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
return undefined;
}
throw error;
}
};
const importDeclaration = (p, state) => {
if (p.node.type !== "ImportDeclaration") {
return;
}
const absolute =
hasRelativeModules && p.node.source.value.startsWith(".")
? resolveAbsolute(p.node.source.value, state)
: p.node.source.value;
if (!absolute || !(absolute in modules)) {
return;
}
const module = modules[absolute];
for (const specifier of p.node.specifiers) {
if (
specifier.type === "ImportDefaultSpecifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!("default" in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access default export from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module.default);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else if (
specifier.type === "ImportSpecifier" &&
specifier.imported &&
specifier.imported.type === "Identifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!(specifier.imported.name in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access `" +
specifier.imported.name +
"` from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module[specifier.imported.name]);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else {
throw new Error("Cannot handle specifier `" + specifier.type + "`");
}
}
p.remove();
};
return {
visitor: {
ImportDeclaration: importDeclaration,
},
};
};

View File

@@ -5,8 +5,6 @@ const paths = require("./paths.js");
// Files from NPM Packages that should not be imported // Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [ module.exports.ignorePackages = ({ latestBuild }) => [
// Bloats bundle and it's not used.
path.resolve(require.resolve("moment"), "../locale"),
// Part of yaml.js and only used for !!js functions that we don't use // Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"), require.resolve("esprima"),
]; ];
@@ -20,7 +18,8 @@ module.exports.emptyPackages = ({ latestBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"), require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN // Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"), require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"), require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Compatibility not needed for latest builds // Compatibility not needed for latest builds
latestBuild && latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists // wrapped in require.resolve so it blows up if file no longer exists
@@ -58,12 +57,23 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/preset-env", "@babel/preset-env",
{ {
useBuiltIns: "entry", useBuiltIns: "entry",
corejs: "3.6", corejs: "3.15",
bugfixes: true,
}, },
], ],
"@babel/preset-typescript", "@babel/preset-typescript",
].filter(Boolean), ].filter(Boolean),
plugins: [ plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.js"
),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2}) // Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [ !latestBuild && [
"@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-object-rest-spread",
@@ -76,8 +86,14 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }], ["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }], ["@babel/plugin-proposal-class-properties", { loose: true }],
].filter(Boolean), ].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
}); });
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>

View File

@@ -47,8 +47,8 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app", "copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
...// Don't compress running tests // Don't compress running tests
(env.isTest() ? [] : ["compress-app"]), ...(env.isTest() ? [] : ["compress-app"]),
gulp.parallel( gulp.parallel(
"gen-pages-prod", "gen-pages-prod",
"gen-index-app-prod", "gen-index-app-prod",

View File

@@ -302,15 +302,23 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) { function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true }); fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync( fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"), path.resolve(paths.hassio_output_root, "entrypoint.js"),
` `
try { function loadES5() {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script'); var el = document.createElement('script');
el.src = '${es5Entrypoint}'; el.src = '${es5Entrypoint}';
document.body.appendChild(el); document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
} }
`, `,
{ encoding: "utf-8" } { encoding: "utf-8" }

View File

@@ -2,7 +2,6 @@
const gulp = require("gulp"); const gulp = require("gulp");
const path = require("path"); const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra"); const fs = require("fs-extra");
const paths = require("../paths"); const paths = require("../paths");
@@ -13,7 +12,9 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) => const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = (staticDir) => (...parts) => const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts); path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) { function copyTranslations(staticDir) {
@@ -62,9 +63,12 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) { function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
// Local fonts // Local fonts
cpx.copySync( fs.copySync(
npmPath("roboto-fontface/fonts/roboto/*.woff2"), npmPath("roboto-fontface/fonts/roboto/"),
staticPath("fonts/roboto") staticPath("fonts/roboto/"),
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
); );
} }

View File

@@ -1,4 +1,5 @@
// Tasks to run webpack. // Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp"); const gulp = require("gulp");
const webpack = require("webpack"); const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server"); const WebpackDevServer = require("webpack-dev-server");
@@ -18,6 +19,13 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }), createConfigFunc({ ...params, latestBuild: false }),
]; ];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/** /**
* @param {{ * @param {{
* compiler: import("webpack").Compiler, * compiler: import("webpack").Compiler,
@@ -78,8 +86,15 @@ const prodBuild = (conf) =>
gulp.task("webpack-watch-app", () => { gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler // This command will run forever because we don't close compiler
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch( webpack(
{ ignored: /build-translations/ }, process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch(
{
ignored: /build-translations/,
poll: isWsl,
},
doneHandler() doneHandler()
); );
gulp.watch( gulp.watch(
@@ -137,7 +152,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false, isProdBuild: false,
latestBuild: true, latestBuild: true,
}) })
).watch({ ignored: /build-translations/ }, doneHandler()); ).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),

View File

@@ -49,12 +49,16 @@ const createWebpackConfig = ({
test: /\.m?js$|\.ts$/, test: /\.m?js$|\.ts$/,
use: { use: {
loader: "babel-loader", loader: "babel-loader",
options: bundle.babelOptions({ latestBuild }), options: {
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
}, },
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: "raw-loader", type: "asset/source",
}, },
], ],
}, },
@@ -66,6 +70,8 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild), terserOptions: bundle.terserOptions(latestBuild),
}), }),
], ],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
}, },
plugins: [ plugins: [
new WebpackManifestPlugin({ new WebpackManifestPlugin({
@@ -112,16 +118,6 @@ const createWebpackConfig = ({
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")), new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
path.resolve(paths.polymer_dir, "src/util/empty.js") path.resolve(paths.polymer_dir, "src/util/empty.js")
), ),
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
new webpack.NormalModuleReplacementPlugin(
new RegExp(
path.resolve(
paths.polymer_dir,
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
)
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(), !isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
@@ -134,15 +130,13 @@ const createWebpackConfig = ({
}, },
output: { output: {
filename: ({ chunk }) => { filename: ({ chunk }) => {
if (!isProdBuild || dontHash.has(chunk.name)) { if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`; return `${chunk.name}.js`;
} }
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
}, },
chunkFilename: chunkFilename:
isProdBuild && !isStatsBuild isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: outputPath, path: outputPath,
publicPath, publicPath,
// To silence warning in worker plugin // To silence warning in worker plugin

View File

@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in to the Cloud. You can validate this behavior in
<a <a
href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast" href="https://github.com/home-assistant/frontend/tree/dev/cast"
target="_blank" target="_blank"
>the source code</a >the source code</a
>. >.

View File

@@ -5,8 +5,8 @@ import {
import { castContext } from "../cast_context"; import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => { export const castDemoLovelace: () => LovelaceConfig = () => {
const touchSupported = castContext.getDeviceCapabilities() const touchSupported =
.touch_input_supported; castContext.getDeviceCapabilities().touch_input_supported;
return { return {
views: [ views: [
{ {

View File

@@ -29,6 +29,11 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
}, },
], ],
}, },
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{ {
type: "thermostat", type: "thermostat",
entity: "climate.upstairs", entity: "climate.upstairs",
@@ -113,8 +118,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -196,8 +200,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -277,8 +280,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -315,8 +317,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {

View File

@@ -1,5 +1,6 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types"; import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types"; import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
@@ -12,9 +13,8 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
// eslint-disable-next-line import/no-mutable-exports // eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfigIndex = 0; export let selectedDemoConfigIndex = 0;
// eslint-disable-next-line import/no-mutable-exports // eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[ export let selectedDemoConfig: Promise<DemoConfig> =
selectedDemoConfigIndex demoConfigs[selectedDemoConfigIndex]();
]();
export const setDemoConfig = async ( export const setDemoConfig = async (
hass: MockHomeAssistant, hass: MockHomeAssistant,
@@ -28,6 +28,7 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm; selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true); hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize)); lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme()); hass.mockTheme(config.theme());
}; };

View File

@@ -980,8 +980,7 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-off", icon: "mdi:account-off",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
templates: { templates: {
icon: icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon_color: icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n", "if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
}, },
@@ -1005,8 +1004,7 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-multiple-minus", icon: "mdi:account-multiple-minus",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
templates: { templates: {
icon: icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon_color: icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n", "if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
}, },

View File

@@ -19,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant; @property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching?: boolean; @state() private _switching = false;
private _hidden = localStorage.hide_demo_card; private _hidden = localStorage.hide_demo_card;
@@ -27,12 +27,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2; return this._hidden ? 0 : 2;
} }
public setConfig( public setConfig(_config: LovelaceCardConfig) {}
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
config: LovelaceCardConfig
// eslint-disable-next-line @typescript-eslint/no-empty-function
) {}
protected render(): TemplateResult { protected render(): TemplateResult {
if (this._hidden) { if (this._hidden) {

View File

@@ -1,5 +1,3 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style"; import "../../src/resources/ha-style";
import "../../src/resources/roboto"; import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch"; import "../../src/resources/safari-14-attachshadow-patch";

View File

@@ -20,6 +20,10 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log"; import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template"; import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
import { mockForecastSolar } from "./stubs/forecast_solar";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() { protected async _initializeHass() {
@@ -47,8 +51,13 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass); mockEvents(hass);
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockEnergy(hass);
mockForecastSolar(hass);
mockConfig(hass);
mockPersistentNotification(hass); mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme. // Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => { ([conf, localize]) => {

41
demo/src/stubs/config.ts Normal file
View File

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

83
demo/src/stubs/energy.ts Normal file
View File

@@ -0,0 +1,83 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => {
hass.mockWS("energy/get_prefs", () => ({
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_from: "sensor.energy_consumption_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_from: "sensor.energy_consumption_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation: "sensor.energy_production_tarif_1_compensation",
entity_energy_to: "sensor.energy_production_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation: "sensor.energy_production_tarif_2_compensation",
entity_energy_to: "sensor.energy_production_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
config_entry_solar_forecast: ["solar_forecast"],
},
{
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
},
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
stat_cost: "sensor.energy_gas_cost",
entity_energy_from: "sensor.energy_gas",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
},
{
stat_consumption: "sensor.energy_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
},
],
}));
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
};

178
demo/src/stubs/entities.ts Normal file
View File

@@ -0,0 +1,178 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const energyEntities = () =>
convertEntities({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",
attributes: {
unit_of_measurement: "%",
},
},
"sensor.solar_production": {
entity_id: "sensor.solar_production",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Solar",
unit_of_measurement: "kWh",
},
},
"sensor.battery_input": {
entity_id: "sensor.battery_input",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Input",
unit_of_measurement: "kWh",
},
},
"sensor.battery_output": {
entity_id: "sensor.battery_output",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Output",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1": {
entity_id: "sensor.energy_consumption_tarif_1 ",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_2": {
entity_id: "sensor.energy_consumption_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_1": {
entity_id: "sensor.energy_production_tarif_1",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_2": {
entity_id: "sensor.energy_production_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1_cost": {
entity_id: "sensor.energy_consumption_tarif_1_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_consumption_tarif_2_cost": {
entity_id: "sensor.energy_consumption_tarif_2_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_1_compensation": {
entity_id: "sensor.energy_production_tarif_1_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_2_compensation": {
entity_id: "sensor.energy_production_tarif_2_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas": {
entity_id: "sensor.energy_gas",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Gas",
unit_of_measurement: "m³",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Electric car",
unit_of_measurement: "kWh",
},
},
"sensor.energy_ac": {
entity_id: "sensor.energy_ac",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Air conditioning",
unit_of_measurement: "kWh",
},
},
"sensor.energy_washing_machine": {
entity_id: "sensor.energy_washing_machine",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Washing machine",
unit_of_measurement: "kWh",
},
},
"sensor.energy_dryer": {
entity_id: "sensor.energy_dryer",
state: "5.5",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Dryer",
unit_of_measurement: "kWh",
},
},
"sensor.energy_heat_pump": {
entity_id: "sensor.energy_heat_pump",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Heat pump",
unit_of_measurement: "kWh",
},
},
"sensor.energy_boiler": {
entity_id: "sensor.energy_boiler",
state: "7",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Boiler",
unit_of_measurement: "kWh",
},
},
});

View File

@@ -0,0 +1,55 @@
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { ForecastSolarForecast } from "../../../src/data/forecast_solar";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockForecastSolar = (hass: MockHomeAssistant) => {
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
"forecast_solar/forecasts",
(): Record<string, ForecastSolarForecast> => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
};

View File

@@ -1,4 +1,6 @@
import { addHours, differenceInHours, endOfDay } from "date-fns";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams { interface HistoryQueryParams {
@@ -64,17 +66,219 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"]; const incrementalUnits = ["clients", "queries", "ads"];
const generateMeanStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = lastVal + delta;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean,
min: mean - Math.random() * maxDiff,
max: mean + Math.random() * maxDiff,
last_reset: "1970-01-01T00:00:00+00:00",
state: mean,
sum: null,
});
lastVal = mean;
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateSumStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum,
});
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateCurvedStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number,
metered: boolean
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const hours = differenceInHours(end, start) - 1;
let i = 0;
let half = false;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += i * add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum: metered ? sum : null,
});
currentDate = addHours(currentDate, 1);
if (!half && i > hours / 2) {
half = true;
}
i += half ? -1 : 1;
}
return statistics;
};
const statisticsFunctions: Record<
string,
(id: string, start: Date, end: Date) => StatisticValue[]
> = {
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const morningFinalVal = morningLow.length
? morningLow[morningLow.length - 1].sum!
: 0;
const empty = generateSumStatistics(
id,
morningEnd,
eveningStart,
morningFinalVal,
0
);
const eveningLow = generateSumStatistics(
id,
eveningStart,
end,
morningFinalVal,
0.7
);
return [...morningLow, ...empty, ...eveningLow];
},
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const highTarif = generateSumStatistics(
id,
morningEnd,
eveningStart,
0,
0.3
);
const highTarifFinalVal = highTarif.length
? highTarif[highTarif.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
const evening = generateSumStatistics(
id,
eveningStart,
end,
highTarifFinalVal,
0
);
return [...morning, ...highTarif, ...evening];
},
"sensor.energy_production_tarif_1": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end) => {
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.15,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.solar_production": (id, start, end) => {
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.3,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
generateMeanStatistics(id, start, end, 35, 1.3),
};
export const mockHistory = (mockHass: MockHomeAssistant) => { export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI( mockHass.mockAPI(
new RegExp("history/period/.+"), new RegExp("history/period/.+"),
( (hass, _method, path, _parameters) => {
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]); const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(","); const entities = params.filter_entity_id.split(",");
@@ -95,7 +299,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state); const numberState = Number(state.state);
if (isNaN(numberState)) { if (isNaN(numberState)) {
// eslint-disable-next-line // eslint-disable-next-line no-console
console.log( console.log(
"Ignoring state with unparsable state but with a unit", "Ignoring state with unparsable state but with a unit",
entityId, entityId,
@@ -140,4 +344,40 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results; return results;
} }
); );
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",
({ statistic_ids, start_time, end_time }, hass) => {
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
const statistics: Record<string, StatisticValue[]> = {};
statistic_ids.forEach((id: string) => {
if (id in statisticsFunctions) {
statistics[id] = statisticsFunctions[id](id, start, end);
} else {
const entityState = hass.states[id];
const state = entityState ? Number(entityState.state) : 1;
statistics[id] =
entityState && "last_reset" in entityState.attributes
? generateSumStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.01 : 0.05)
)
: generateMeanStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}
});
return statistics;
}
);
}; };

View File

@@ -10,10 +10,9 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc> localizePromise: Promise<LocalizeFunc>
) => { ) => {
hass.mockWS("lovelace/config", () => hass.mockWS("lovelace/config", () =>
Promise.all([ Promise.all([selectedDemoConfig, localizePromise]).then(
selectedDemoConfig, ([config, localize]) => config.lovelace(localize)
localizePromise, )
]).then(([config, localize]) => config.lovelace(localize))
); );
hass.mockWS("lovelace/config/save", () => Promise.resolve()); hass.mockWS("lovelace/config/save", () => Promise.resolve());

View File

@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
body: { message: "Template dev tool does not work in the demo." }, body: { message: "Template dev tool does not work in the demo." },
}) })
); );
hass.mockWS("render_template", (msg, onChange) => { hass.mockWS("render_template", (msg, _hass, onChange) => {
onChange!({ onChange!({
result: msg.template, result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false }, listeners: { all: false, domains: [], entities: [], time: false },

View File

@@ -2,12 +2,12 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph"; import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline"; import "../../../src/components/trace/hat-trace-timeline";
import { customElement, property, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types"; import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace"; import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace"; import { motionLightTrace } from "../data/traces/motion-light-trace";
import { customElement, property, state } from "lit/decorators";
const traces: DemoTrace[] = [basicTrace, motionLightTrace]; const traces: DemoTrace[] = [basicTrace, motionLightTrace];

View File

@@ -2,6 +2,8 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-formfield"; import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
import { IntegrationManifest } from "../../../src/data/integration"; import { IntegrationManifest } from "../../../src/data/integration";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -15,8 +17,6 @@ import type {
} from "../../../src/panels/config/integrations/ha-config-integrations"; } from "../../../src/panels/config/integrations/ha-config-integrations";
import { DeviceRegistryEntry } from "../../../src/data/device_registry"; import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import { EntityRegistryEntry } from "../../../src/data/entity_registry"; import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = ( const createConfigEntry = (
title: string, title: string,

View File

@@ -2,7 +2,6 @@ import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list"; import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml"; import { DEFAULT_SCHEMA, Type } from "js-yaml";
import { import {
css, css,
@@ -134,7 +133,7 @@ class HassioAddonConfig extends LitElement {
></ha-form>` ></ha-form>`
: html` <ha-yaml-editor : html` <ha-yaml-editor
@value-changed=${this._configChanged} @value-changed=${this._configChanged}
.schema=${ADDON_YAML_SCHEMA} .yamlSchema=${ADDON_YAML_SCHEMA}
></ha-yaml-editor>`} ></ha-yaml-editor>`}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this._yamlMode || ${!this._yamlMode ||
@@ -269,6 +268,9 @@ class HassioAddonConfig extends LitElement {
private async _saveTapped(ev: CustomEvent): Promise<void> { private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any; const button = ev.currentTarget as any;
const options: Record<string, unknown> = this._yamlMode
? this._editor?.value
: this._options;
const eventdata = { const eventdata = {
success: true, success: true,
response: undefined, response: undefined,
@@ -282,13 +284,13 @@ class HassioAddonConfig extends LitElement {
const validation = await validateHassioAddonOption( const validation = await validateHassioAddonOption(
this.hass, this.hass,
this.addon.slug, this.addon.slug,
this._editor?.value options
); );
if (!validation.valid) { if (!validation.valid) {
throw Error(validation.message); throw Error(validation.message);
} }
await setHassioAddonOption(this.hass, this.addon.slug, { await setHassioAddonOption(this.hass, this.addon.slug, {
options: this._yamlMode ? this._editor?.value : this._options, options,
}); });
this._configHasChanged = false; this._configHasChanged = false;
@@ -326,10 +328,6 @@ class HassioAddonConfig extends LitElement {
color: var(--error-color); color: var(--error-color);
margin-top: 16px; margin-top: 16px;
} }
iron-autogrow-textarea {
width: 100%;
font-family: var(--code-font-family, monospace);
}
.syntaxerror { .syntaxerror {
color: var(--error-color); color: var(--error-color);
} }

View File

@@ -2,6 +2,7 @@ import "../../../../src/components/ha-card";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { customElement, property, state } from "lit/decorators";
import { import {
fetchHassioAddonDocumentation, fetchHassioAddonDocumentation,
HassioAddonDetails, HassioAddonDetails,
@@ -12,7 +13,6 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
@customElement("hassio-addon-documentation-tab") @customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement { class HassioAddonDocumentationDashboard extends LitElement {

View File

@@ -892,10 +892,19 @@ class HassioAddonInfo extends LitElement {
private async _openChangelog(): Promise<void> { private async _openChangelog(): Promise<void> {
try { try {
const content = await fetchHassioAddonChangelog( let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
this.hass, if (
this.addon.slug content.includes(`# ${this.addon.version}`) &&
); content.includes(`# ${this.addon.version_latest}`)
) {
const newcontent = content.split(`# ${this.addon.version}`)[0];
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
// Only change the content if the new version still exist
// if the changelog does not have the newests version on top
// this will not be true, and we don't modify the content
content = newcontent;
}
}
showHassioMarkdownDialog(this, { showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"), title: this.supervisor.localize("addon.dashboard.changelog"),
content, content,
@@ -978,7 +987,7 @@ class HassioAddonInfo extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: this.addon.name, name: this.addon.name,
version: this.addon.version_latest, version: this.addon.version_latest,
snapshotParams: { backupParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`, name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug], addons: [this.addon.slug],
homeassistant: false, homeassistant: false,
@@ -1140,6 +1149,7 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px; margin-bottom: 16px;
} }
img.logo { img.logo {
max-width: 100%;
max-height: 60px; max-height: 60px;
margin: 16px 0; margin: 16px 0;
display: block; display: block;
@@ -1149,10 +1159,10 @@ class HassioAddonInfo extends LitElement {
display: flex; display: flex;
} }
ha-svg-icon.running { ha-svg-icon.running {
color: var(--paper-green-400); color: var(--success-color);
} }
ha-svg-icon.stopped { ha-svg-icon.stopped {
color: var(--google-red-300); color: var(--error-color);
} }
ha-call-api-button { ha-call-api-button {
font-weight: 500; font-weight: 500;

View File

@@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab"; import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
fetchHassioSnapshots, fetchHassioBackups,
friendlyFolderName, friendlyFolderName,
HassioSnapshot, HassioBackup,
reloadHassioSnapshots, reloadHassioBackups,
removeSnapshot, removeBackup,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
@@ -40,14 +40,14 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table"; import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot"; import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload"; import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-snapshots") @customElement("hassio-backups")
export class HassioSnapshots extends LitElement { export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@@ -58,9 +58,9 @@ export class HassioSnapshots extends LitElement {
@property({ type: Boolean }) public isWide!: boolean; @property({ type: Boolean }) public isWide!: boolean;
@state() private _selectedSnapshots: string[] = []; @state() private _selectedBackups: string[] = [];
@state() private _snapshots?: HassioSnapshot[] = []; @state() private _backups?: HassioBackup[] = [];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@@ -75,26 +75,26 @@ export class HassioSnapshots extends LitElement {
} }
public async refreshData() { public async refreshData() {
await reloadHassioSnapshots(this.hass); await reloadHassioBackups(this.hass);
await this.fetchSnapshots(); await this.fetchBackups();
} }
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => { private _computeBackupContent = (backup: HassioBackup): string => {
if (snapshot.type === "full") { if (backup.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot"); return this.supervisor.localize("backup.full_backup");
} }
const content: string[] = []; const content: string[] = [];
if (snapshot.content.homeassistant) { if (backup.content.homeassistant) {
content.push("Home Assistant"); content.push("Home Assistant");
} }
if (snapshot.content.folders.length !== 0) { if (backup.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) { for (const folder of backup.content.folders) {
content.push(friendlyFolderName[folder] || folder); content.push(friendlyFolderName[folder] || folder);
} }
} }
if (snapshot.content.addons.length !== 0) { if (backup.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) { for (const addon of backup.content.addons) {
content.push( content.push(
this.supervisor.supervisor.addons.find( this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon (entry) => entry.slug === addon
@@ -117,16 +117,16 @@ export class HassioSnapshots extends LitElement {
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({ (narrow: boolean): DataTableColumnContainer => ({
name: { name: {
title: this.supervisor?.localize("snapshot.name") || "", title: this.supervisor?.localize("backup.name") || "",
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (entry: string, snapshot: any) => template: (entry: string, backup: any) =>
html`${entry || snapshot.slug} html`${entry || backup.slug}
<div class="secondary">${snapshot.secondary}</div>`, <div class="secondary">${backup.secondary}</div>`,
}, },
date: { date: {
title: this.supervisor?.localize("snapshot.created") || "", title: this.supervisor?.localize("backup.created") || "",
width: "15%", width: "15%",
direction: "desc", direction: "desc",
hidden: narrow, hidden: narrow,
@@ -143,10 +143,10 @@ export class HassioSnapshots extends LitElement {
}) })
); );
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) => private _backupData = memoizeOne((backups: HassioBackup[]) =>
snapshots.map((snapshot) => ({ backups.map((backup) => ({
...snapshot, ...backup,
secondary: this._computeSnapshotContent(snapshot), secondary: this._computeBackupContent(backup),
})) }))
); );
@@ -160,11 +160,11 @@ export class HassioSnapshots extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.localizeFunc=${this.supervisor.localize} .localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")} .searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")} .noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
.columns=${this._columns(this.narrow)} .columns=${this._columns(this.narrow)}
.data=${this._snapshotData(this._snapshots || [])} .data=${this._backupData(this._backups || [])}
id="slug" id="slug"
@row-click=${this._handleRowClicked} @row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged} @selection-changed=${this._handleSelectionChanged}
@@ -187,12 +187,12 @@ export class HassioSnapshots extends LitElement {
</mwc-list-item> </mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116) ${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item> ? html`<mwc-list-item>
${this.supervisor?.localize("snapshot.upload_snapshot")} ${this.supervisor?.localize("backup.upload_backup")}
</mwc-list-item>` </mwc-list-item>`
: ""} : ""}
</ha-button-menu> </ha-button-menu>
${this._selectedSnapshots.length ${this._selectedBackups.length
? html`<div ? html`<div
class=${classMap({ class=${classMap({
"header-toolbar": this.narrow, "header-toolbar": this.narrow,
@@ -201,8 +201,8 @@ export class HassioSnapshots extends LitElement {
slot="header" slot="header"
> >
<p class="selected-txt"> <p class="selected-txt">
${this.supervisor.localize("snapshot.selected", { ${this.supervisor.localize("backup.selected", {
number: this._selectedSnapshots.length, number: this._selectedBackups.length,
})} })}
</p> </p>
<div class="header-btns"> <div class="header-btns">
@@ -212,7 +212,7 @@ export class HassioSnapshots extends LitElement {
@click=${this._deleteSelected} @click=${this._deleteSelected}
class="warning" class="warning"
> >
${this.supervisor.localize("snapshot.delete_selected")} ${this.supervisor.localize("backup.delete_selected")}
</mwc-button> </mwc-button>
` `
: html` : html`
@@ -224,7 +224,7 @@ export class HassioSnapshots extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon> <ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn"> <paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("snapshot.delete_selected")} ${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip> </paper-tooltip>
`} `}
</div> </div>
@@ -233,8 +233,8 @@ export class HassioSnapshots extends LitElement {
<ha-fab <ha-fab
slot="fab" slot="fab"
@click=${this._createSnapshot} @click=${this._createBackup}
.label=${this.supervisor.localize("snapshot.create_snapshot")} .label=${this.supervisor.localize("backup.create_backup")}
extended extended
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -249,7 +249,7 @@ export class HassioSnapshots extends LitElement {
this.refreshData(); this.refreshData();
break; break;
case 1: case 1:
this._showUploadSnapshotDialog(); this._showUploadBackupDialog();
break; break;
} }
} }
@@ -257,33 +257,33 @@ export class HassioSnapshots extends LitElement {
private _handleSelectionChanged( private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent> ev: HASSDomEvent<SelectionChangedEvent>
): void { ): void {
this._selectedSnapshots = ev.detail.value; this._selectedBackups = ev.detail.value;
} }
private _showUploadSnapshotDialog() { private _showUploadBackupDialog() {
showSnapshotUploadDialog(this, { showBackupUploadDialog(this, {
showSnapshot: (slug: string) => showBackup: (slug: string) =>
showHassioSnapshotDialog(this, { showHassioBackupDialog(this, {
slug, slug,
supervisor: this.supervisor, supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(), onDelete: () => this.fetchBackups(),
}), }),
reloadSnapshot: () => this.refreshData(), reloadBackup: () => this.refreshData(),
}); });
} }
private async fetchSnapshots() { private async fetchBackups() {
await reloadHassioSnapshots(this.hass); await reloadHassioBackups(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass); this._backups = await fetchHassioBackups(this.hass);
} }
private async _deleteSelected() { private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, { const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("snapshot.delete_snapshot_title"), title: this.supervisor.localize("backup.delete_backup_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", { text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedSnapshots.length, number: this._selectedBackups.length,
}), }),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"), confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
}); });
if (!confirm) { if (!confirm) {
@@ -292,44 +292,44 @@ export class HassioSnapshots extends LitElement {
try { try {
await Promise.all( await Promise.all(
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug)) this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
); );
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("snapshot.failed_to_delete"), title: this.supervisor.localize("backup.failed_to_delete"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
return; return;
} }
await reloadHassioSnapshots(this.hass); await reloadHassioBackups(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass); this._backups = await fetchHassioBackups(this.hass);
this._dataTable.clearSelection(); this._dataTable.clearSelection();
} }
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) { private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id; const slug = ev.detail.id;
showHassioSnapshotDialog(this, { showHassioBackupDialog(this, {
slug, slug,
supervisor: this.supervisor, supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(), onDelete: () => this.fetchBackups(),
}); });
} }
private _createSnapshot() { private _createBackup() {
if (this.supervisor!.info.state !== "running") { if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"), title: this.supervisor!.localize("backup.could_not_create"),
text: this.supervisor!.localize( text: this.supervisor!.localize(
"snapshot.create_blocked_not_running", "backup.create_blocked_not_running",
"state", "state",
this.supervisor!.info.state this.supervisor!.info.state
), ),
}); });
return; return;
} }
showHassioCreateSnapshotDialog(this, { showHassioCreateBackupDialog(this, {
supervisor: this.supervisor!, supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(), onCreate: () => this.fetchBackups(),
}); });
} }
@@ -378,6 +378,6 @@ export class HassioSnapshots extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hassio-snapshots": HassioSnapshots; "hassio-backups": HassioBackups;
} }
} }

View File

@@ -41,16 +41,16 @@ class HassioAnsiToHtml extends LitElement {
text-decoration: underline line-through; text-decoration: underline line-through;
} }
.fg-red { .fg-red {
color: rgb(222, 56, 43); color: var(--error-color);
} }
.fg-green { .fg-green {
color: rgb(57, 181, 74); color: var(--success-color);
} }
.fg-yellow { .fg-yellow {
color: rgb(255, 199, 6); color: var(--warning-color);
} }
.fg-blue { .fg-blue {
color: rgb(0, 111, 184); color: var(--info-color);
} }
.fg-magenta { .fg-magenta {
color: rgb(118, 38, 113); color: rgb(118, 38, 113);
@@ -65,16 +65,16 @@ class HassioAnsiToHtml extends LitElement {
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
} }
.bg-red { .bg-red {
background-color: rgb(222, 56, 43); background-color: var(--error-color);
} }
.bg-green { .bg-green {
background-color: rgb(57, 181, 74); background-color: var(--success-color);
} }
.bg-yellow { .bg-yellow {
background-color: rgb(255, 199, 6); background-color: var(--warning-color);
} }
.bg-blue { .bg-blue {
background-color: rgb(0, 111, 184); background-color: var(--info-color);
} }
.bg-magenta { .bg-magenta {
background-color: rgb(118, 38, 113); background-color: rgb(118, 38, 113);

View File

@@ -80,14 +80,14 @@ class HassioCardContent extends LitElement {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-svg-icon.update { ha-svg-icon.update {
color: var(--paper-orange-400); color: var(--warning-color);
} }
ha-svg-icon.running, ha-svg-icon.running,
ha-svg-icon.installed { ha-svg-icon.installed {
color: var(--paper-green-400); color: var(--success-color);
} }
ha-svg-icon.hassupdate, ha-svg-icon.hassupdate,
ha-svg-icon.snapshot { ha-svg-icon.backup {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }
ha-svg-icon.not_available { ha-svg-icon.not_available {
@@ -122,7 +122,7 @@ class HassioCardContent extends LitElement {
} }
.dot { .dot {
position: absolute; position: absolute;
background-color: var(--paper-orange-400); background-color: var(--warning-color);
width: 12px; width: 12px;
height: 12px; height: 12px;
top: 8px; top: 8px;

View File

@@ -1,6 +1,5 @@
import "@material/mwc-icon-button/mwc-icon-button"; import "@material/mwc-icon-button/mwc-icon-button";
import { mdiFolderUpload } from "@mdi/js"; import { mdiFolderUpload } from "@mdi/js";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container"; import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit"; import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
@@ -9,23 +8,20 @@ import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload"; import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
"snapshot-uploaded": { snapshot: HassioSnapshot }; "backup-uploaded": { backup: HassioBackup };
} }
} }
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-snapshot") @customElement("hassio-upload-backup")
export class HassioUploadSnapshot extends LitElement { export class HassioUploadBackup extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() public value: string | null = null; @state() public value: string | null = null;
@@ -38,7 +34,7 @@ export class HassioUploadSnapshot extends LitElement {
.uploading=${this._uploading} .uploading=${this._uploading}
.icon=${mdiFolderUpload} .icon=${mdiFolderUpload}
accept="application/x-tar" accept="application/x-tar"
label="Upload snapshot" label="Upload backup"
@file-picked=${this._uploadFile} @file-picked=${this._uploadFile}
auto-open-file-dialog auto-open-file-dialog
></ha-file-upload> ></ha-file-upload>
@@ -50,10 +46,10 @@ export class HassioUploadSnapshot extends LitElement {
if (file.size > MAX_FILE_SIZE) { if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Snapshot file is too big", title: "Backup file is too big",
text: html`The maximum allowed filesize is 1GB.<br /> text: html`The maximum allowed filesize is 1GB.<br />
<a <a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install" href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
target="_blank" target="_blank"
>Have a look here on how to restore it.</a >Have a look here on how to restore it.</a
>`, >`,
@@ -65,15 +61,15 @@ export class HassioUploadSnapshot extends LitElement {
if (!["application/x-tar"].includes(file.type)) { if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Unsupported file format", title: "Unsupported file format",
text: "Please choose a Home Assistant snapshot file (.tar)", text: "Please choose a Home Assistant backup file (.tar)",
confirmText: "ok", confirmText: "ok",
}); });
return; return;
} }
this._uploading = true; this._uploading = true;
try { try {
const snapshot = await uploadSnapshot(this.hass, file); const backup = await uploadBackup(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data }); fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Upload failed", title: "Upload failed",
@@ -88,6 +84,6 @@ export class HassioUploadSnapshot extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hassio-upload-snapshot": HassioUploadSnapshot; "hassio-upload-backup": HassioUploadBackup;
} }
} }

View File

@@ -5,15 +5,16 @@ import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { formatDate } from "../../../src/common/datetime/format_date"; import { formatDate } from "../../../src/common/datetime/format_date";
import { formatDateTime } from "../../../src/common/datetime/format_date_time"; import { formatDateTime } from "../../../src/common/datetime/format_date_time";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox"; import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield"; import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio"; import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio"; import type { HaRadio } from "../../../src/components/ha-radio";
import { import {
HassioFullSnapshotCreateParams, HassioFullBackupCreateParams,
HassioPartialSnapshotCreateParams, HassioPartialBackupCreateParams,
HassioSnapshotDetail, HassioBackupDetail,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -63,15 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
})) }))
.sort((a, b) => (a.name > b.name ? 1 : -1)); .sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-snapshot-content") @customElement("supervisor-backup-content")
export class SupervisorSnapshotContent extends LitElement { export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor; @property({ attribute: false }) public supervisor?: Supervisor;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail; @property({ attribute: false }) public backup?: HassioBackupDetail;
@property() public snapshotType: HassioSnapshotDetail["type"] = "full"; @property() public backupType: HassioBackupDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[]; @property({ attribute: false }) public folders?: CheckboxItem[];
@@ -79,96 +82,100 @@ export class SupervisorSnapshotContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false; @property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public snapshotHasPassword = false; @property({ type: Boolean }) public backupHasPassword = false;
@property() public snapshotName = ""; @property({ type: Boolean }) public onboarding = false;
@property() public snapshotPassword = ""; @property() public backupName = "";
@property() public backupPassword = "";
@property() public confirmBackupPassword = "";
public willUpdate(changedProps) { public willUpdate(changedProps) {
super.willUpdate(changedProps); super.willUpdate(changedProps);
if (!this.hasUpdated) { if (!this.hasUpdated) {
this.folders = _computeFolders( this.folders = _computeFolders(
this.snapshot this.backup
? this.snapshot.folders ? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"] : ["homeassistant", "ssl", "share", "media", "addons/local"]
); );
this.addons = _computeAddons( this.addons = _computeAddons(
this.snapshot this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
? this.snapshot.addons
: this.supervisor?.supervisor.addons
); );
this.snapshotType = this.snapshot?.type || "full"; this.backupType = this.backup?.type || "full";
this.snapshotName = this.snapshot?.name || ""; this.backupName = this.backup?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false; this.backupHasPassword = this.backup?.protected || false;
} }
} }
private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisor) { if (!this.onboarding && !this.supervisor) {
return html``; return html``;
} }
const foldersSection = const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined; this.backupType === "partial" ? this._getSection("folders") : undefined;
const addonsSection = const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined; this.backupType === "partial" ? this._getSection("addons") : undefined;
return html` return html`
${this.snapshot ${this.backup
? html`<div class="details"> ? html`<div class="details">
${this.snapshot.type === "full" ${this.backup.type === "full"
? this.supervisor.localize("snapshot.full_snapshot") ? this._localize("full_backup")
: this.supervisor.localize("snapshot.partial_snapshot")} : this._localize("partial_backup")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br /> (${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${formatDateTime(new Date(this.snapshot.date), this.hass.locale)} ${this.hass
? formatDateTime(new Date(this.backup.date), this.hass.locale)
: this.backup.date}
</div>` </div>`
: html`<paper-input : html`<paper-input
name="snapshotName" name="backupName"
.label=${this.supervisor.localize("snapshot.name")} .label=${this._localize("name")}
.value=${this.snapshotName} .value=${this.backupName}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>`} </paper-input>`}
${!this.snapshot || this.snapshot.type === "full" ${!this.backup || this.backup.type === "full"
? html`<div class="sub-header"> ? html`<div class="sub-header">
${!this.snapshot ${!this.backup
? this.supervisor.localize("snapshot.type") ? this._localize("type")
: this.supervisor.localize("snapshot.select_type")} : this._localize("select_type")}
</div> </div>
<div class="snapshot-types"> <div class="backup-types">
<ha-formfield <ha-formfield .label=${this._localize("full_backup")}>
.label=${this.supervisor.localize("snapshot.full_snapshot")}
>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="full" value="full"
name="snapshotType" name="backupType"
.checked=${this.snapshotType === "full"} .checked=${this.backupType === "full"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
<ha-formfield <ha-formfield .label=${this._localize("partial_backup")}>
.label=${this.supervisor!.localize("snapshot.partial_snapshot")}
>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="partial" value="partial"
name="snapshotType" name="backupType"
.checked=${this.snapshotType === "partial"} .checked=${this.backupType === "partial"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
</div>` </div>`
: ""} : ""}
${this.snapshot && this.snapshotType === "partial" ${this.backupType === "partial"
? html` ? html`<div class="partial-picker">
${this.snapshot.homeassistant ${this.backup && this.backup.homeassistant
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
label="Home Assistant" label="Home Assistant"
.iconPath=${mdiHomeAssistant} .iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant} .version=${this.backup.homeassistant}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
> >
@@ -182,15 +189,11 @@ export class SupervisorSnapshotContent extends LitElement {
</ha-formfield> </ha-formfield>
` `
: ""} : ""}
`
: ""}
${this.snapshotType === "partial"
? html`
${foldersSection?.templates.length ${foldersSection?.templates.length
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
.label=${this.supervisor.localize("snapshot.folders")} .label=${this._localize("folders")}
.iconPath=${mdiFolder} .iconPath=${mdiFolder}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
@@ -210,7 +213,7 @@ export class SupervisorSnapshotContent extends LitElement {
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
.label=${this.supervisor.localize("snapshot.addons")} .label=${this._localize("addons")}
.iconPath=${mdiPuzzle} .iconPath=${mdiPuzzle}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
@@ -226,29 +229,44 @@ export class SupervisorSnapshotContent extends LitElement {
<div class="section-content">${addonsSection.templates}</div> <div class="section-content">${addonsSection.templates}</div>
` `
: ""} : ""}
` </div> `
: ""} : ""}
${!this.snapshot ${this.backupType === "partial" &&
(!this.backup || this.backupHasPassword)
? html`<hr />`
: ""}
${!this.backup
? html`<ha-formfield ? html`<ha-formfield
.label=${this.supervisor.localize("snapshot.password_protection")} class="password"
.label=${this._localize("password_protection")}
> >
<ha-checkbox <ha-checkbox
.checked=${this.snapshotHasPassword} .checked=${this.backupHasPassword}
@change=${this._toggleHasPassword} @change=${this._toggleHasPassword}
> >
</ha-checkbox </ha-checkbox>
></ha-formfield>` </ha-formfield>`
: ""} : ""}
${this.snapshotHasPassword ${this.backupHasPassword
? html` ? html`
<paper-input <paper-input
.label=${this.supervisor.localize("snapshot.password")} .label=${this._localize("password")}
type="password" type="password"
name="snapshotPassword" name="backupPassword"
.value=${this.snapshotPassword} .value=${this.backupPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input> </paper-input>
${!this.backup
? html` <paper-input
.label=${this._localize("confirm_password")}
type="password"
name="confirmBackupPassword"
.value=${this.confirmBackupPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
: ""}
` `
: ""} : ""}
`; `;
@@ -256,21 +274,24 @@ export class SupervisorSnapshotContent extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
ha-checkbox { .partial-picker ha-formfield {
--mdc-checkbox-touch-target-size: 16px;
display: block; display: block;
margin: 4px 12px 8px 0;
} }
ha-formfield { .partial-picker ha-checkbox {
display: contents; --mdc-checkbox-touch-target-size: 32px;
}
.partial-picker {
display: block;
margin: 0px -6px;
} }
supervisor-formfield-label { supervisor-formfield-label {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
} }
paper-input[type="password"] { hr {
display: block; border-color: var(--divider-color);
margin: 4px 0 4px 16px; border-bottom: none;
margin: 16px 0;
} }
.details { .details {
color: var(--secondary-text-color); color: var(--secondary-text-color);
@@ -278,13 +299,15 @@ export class SupervisorSnapshotContent extends LitElement {
.section-content { .section-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 16px; margin-left: 30px;
} }
.security { ha-formfield.password {
margin-top: 16px; display: block;
margin: 0 -14px -16px;
} }
.snapshot-types { .backup-types {
display: flex; display: flex;
margin-left: -13px;
} }
.sub-header { .sub-header {
margin-top: 8px; margin-top: 8px;
@@ -292,20 +315,23 @@ export class SupervisorSnapshotContent extends LitElement {
`; `;
} }
public snapshotDetails(): public backupDetails():
| HassioPartialSnapshotCreateParams | HassioPartialBackupCreateParams
| HassioFullSnapshotCreateParams { | HassioFullBackupCreateParams {
const data: any = {}; const data: any = {};
if (!this.snapshot) { if (!this.backup) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale); data.name = this.backupName || formatDate(new Date(), this.hass.locale);
} }
if (this.snapshotHasPassword) { if (this.backupHasPassword) {
data.password = this.snapshotPassword; data.password = this.backupPassword;
if (!this.backup) {
data.confirm_password = this.confirmBackupPassword;
}
} }
if (this.snapshotType === "full") { if (this.backupType === "full") {
return data; return data;
} }
@@ -334,7 +360,7 @@ export class SupervisorSnapshotContent extends LitElement {
const addons = const addons =
section === "addons" section === "addons"
? new Map( ? new Map(
this.supervisor!.addon.addons.map((item) => [item.slug, item]) this.supervisor?.addon.addons.map((item) => [item.slug, item])
) )
: undefined; : undefined;
let checkedItems = 0; let checkedItems = 0;
@@ -344,6 +370,7 @@ export class SupervisorSnapshotContent extends LitElement {
.label=${item.name} .label=${item.name}
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder} .iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" && .imageUrl=${section === "addons" &&
!this.onboarding &&
atLeastVersion(this.hass.config.version, 0, 105) && atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon` ? `/api/hassio/addons/${item.slug}/icon`
@@ -386,7 +413,7 @@ export class SupervisorSnapshotContent extends LitElement {
} }
private _toggleHasPassword(): void { private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword; this.backupHasPassword = !this.backupHasPassword;
} }
private _toggleSection(ev): void { private _toggleSection(ev): void {
@@ -416,6 +443,6 @@ export class SupervisorSnapshotContent extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent; "supervisor-backup-content": SupervisorBackupContent;
} }
} }

View File

@@ -29,7 +29,6 @@ class SupervisorFormfieldLabel extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
} }

View File

@@ -86,7 +86,7 @@ export class HassioUpdate extends LitElement {
"hassio/supervisor/update", "hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}` `https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
)} )}
${this.supervisor.host.features.includes("hassos") ${this.supervisor.host.features.includes("haos")
? this._renderUpdateCard( ? this._renderUpdateCard(
"Operating System", "Operating System",
"os", "os",
@@ -162,7 +162,7 @@ export class HassioUpdate extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: "Home Assistant Core", name: "Home Assistant Core",
version: this.supervisor.core.version_latest, version: this.supervisor.core.version_latest,
snapshotParams: { backupParams: {
name: `core_${this.supervisor.core.version}`, name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"], folders: ["homeassistant"],
homeassistant: true, homeassistant: true,

View File

@@ -6,19 +6,20 @@ import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-snapshot"; import "../../components/hassio-upload-backup";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"; import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
@customElement("dialog-hassio-snapshot-upload") @customElement("dialog-hassio-backup-upload")
export class DialogHassioSnapshotUpload export class DialogHassioBackupUpload
extends LitElement extends LitElement
implements HassDialog<HassioSnapshotUploadDialogParams> { implements HassDialog<HassioBackupUploadDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioSnapshotUploadDialogParams; @state() private _params?: HassioBackupUploadDialogParams;
public async showDialog( public async showDialog(
params: HassioSnapshotUploadDialogParams params: HassioBackupUploadDialogParams
): Promise<void> { ): Promise<void> {
this._params = params; this._params = params;
await this.updateComplete; await this.updateComplete;
@@ -26,8 +27,8 @@ export class DialogHassioSnapshotUpload
public closeDialog(): void { public closeDialog(): void {
if (this._params && !this._params.onboarding) { if (this._params && !this._params.onboarding) {
if (this._params.reloadSnapshot) { if (this._params.reloadBackup) {
this._params.reloadSnapshot(); this._params.reloadBackup();
} }
} }
this._params = undefined; this._params = undefined;
@@ -50,23 +51,23 @@ export class DialogHassioSnapshotUpload
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> Upload snapshot </span> <span slot="title"> Upload backup </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
<hassio-upload-snapshot <hassio-upload-backup
@snapshot-uploaded=${this._snapshotUploaded} @backup-uploaded=${this._backupUploaded}
.hass=${this.hass} .hass=${this.hass}
></hassio-upload-snapshot> ></hassio-upload-backup>
</ha-dialog> </ha-dialog>
`; `;
} }
private _snapshotUploaded(ev) { private _backupUploaded(ev) {
const snapshot = ev.detail.snapshot; const backup = ev.detail.backup;
this._params?.showSnapshot(snapshot.slug); this._params?.showBackup(backup.slug);
this.closeDialog(); this.closeDialog();
} }
@@ -93,6 +94,6 @@ export class DialogHassioSnapshotUpload
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload; "dialog-hassio-backup-upload": DialogHassioBackupUpload;
} }
} }

View File

@@ -1,20 +1,20 @@
import { ActionDetail } from "@material/mwc-list"; import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiClose, mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify"; import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
fetchHassioSnapshotInfo, fetchHassioBackupInfo,
HassioSnapshotDetail, HassioBackupDetail,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/backup";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -23,43 +23,45 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download"; import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-snapshot-content"; import "../../components/supervisor-backup-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
@customElement("dialog-hassio-snapshot") @customElement("dialog-hassio-backup")
class HassioSnapshotDialog class HassioBackupDialog
extends LitElement extends LitElement
implements HassDialog<HassioSnapshotDialogParams> { implements HassDialog<HassioBackupDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string; @state() private _error?: string;
@state() private _snapshot?: HassioSnapshotDetail; @state() private _backup?: HassioBackupDetail;
@state() private _dialogParams?: HassioSnapshotDialogParams; @state() private _dialogParams?: HassioBackupDialogParams;
@state() private _restoringSnapshot = false; @state() private _restoringBackup = false;
@query("supervisor-snapshot-content") @query("supervisor-backup-content")
private _snapshotContent!: SupervisorSnapshotContent; private _backupContent!: SupervisorBackupContent;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioBackupDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
this._dialogParams = params; this._dialogParams = params;
this._restoringSnapshot = false; this._restoringBackup = false;
} }
public closeDialog() { public closeDialog() {
this._snapshot = undefined; this._backup = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._restoringSnapshot = false; this._restoringBackup = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) { if (!this._dialogParams || !this._backup) {
return html``; return html``;
} }
return html` return html`
@@ -67,27 +69,38 @@ class HassioSnapshotDialog
open open
scrimClickAction scrimClickAction
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, this._computeName)} .heading=${true}
> >
${this._restoringSnapshot <div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
${this._restoringBackup
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-backup-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot} .backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
> >
</supervisor-snapshot-content>`} </supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button <mwc-button
.disabled=${this._restoringSnapshot} .disabled=${this._restoringBackup}
slot="secondaryAction" slot="secondaryAction"
@click=${this._restoreClicked} @click=${this._restoreClicked}
> >
Restore Restore
</mwc-button> </mwc-button>
<ha-button-menu ${!this._dialogParams.onboarding
? html`<ha-button-menu
fixed fixed
slot="primaryAction" slot="primaryAction"
@action=${this._handleMenuAction} @action=${this._handleMenuAction}
@@ -96,9 +109,10 @@ class HassioSnapshotDialog
<mwc-icon-button slot="trigger" alt="menu"> <mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item> <mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item> <mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu> </ha-button-menu>`
: ""}
</ha-dialog> </ha-dialog>
`; `;
} }
@@ -115,6 +129,12 @@ class HassioSnapshotDialog
display: block; display: block;
text-align: center; text-align: center;
} }
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
`, `,
]; ];
} }
@@ -131,30 +151,30 @@ class HassioSnapshotDialog
} }
private async _restoreClicked() { private async _restoreClicked() {
const snapshotDetails = this._snapshotContent.snapshotDetails(); const backupDetails = this._backupContent.backupDetails();
this._restoringSnapshot = true; this._restoringBackup = true;
if (this._snapshotContent.snapshotType === "full") { if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(snapshotDetails); await this._fullRestoreClicked(backupDetails);
} else { } else {
await this._partialRestoreClicked(snapshotDetails); await this._partialRestoreClicked(backupDetails);
} }
this._restoringSnapshot = false; this._restoringBackup = false;
} }
private async _partialRestoreClicked(snapshotDetails) { private async _partialRestoreClicked(backupDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore backup",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?", title: "Are you sure you want partially to restore this backup?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -167,8 +187,12 @@ class HassioSnapshotDialog
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`, `hassio/${
snapshotDetails atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
) )
.then( .then(
() => { () => {
@@ -180,29 +204,29 @@ class HassioSnapshotDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(backupDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
} }
private async _fullRestoreClicked(snapshotDetails) { private async _fullRestoreClicked(backupDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore backup",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: title:
"Are you sure you want to wipe your system and restore this snapshot?", "Are you sure you want to wipe your system and restore this backup?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -214,8 +238,12 @@ class HassioSnapshotDialog
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`, `hassio/${
snapshotDetails atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
) )
.then( .then(
() => { () => {
@@ -227,9 +255,9 @@ class HassioSnapshotDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(backupDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
@@ -238,7 +266,7 @@ class HassioSnapshotDialog
private async _deleteClicked() { private async _deleteClicked() {
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?", title: "Are you sure you want to delete this backup?",
confirmText: "delete", confirmText: "delete",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -248,7 +276,14 @@ class HassioSnapshotDialog
this.hass this.hass
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`) .callApi(
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/remove`
)
.then( .then(
() => { () => {
if (this._dialogParams!.onDelete) { if (this._dialogParams!.onDelete) {
@@ -267,7 +302,11 @@ class HassioSnapshotDialog
try { try {
signedPath = await getSignedPath( signedPath = await getSignedPath(
this.hass, this.hass,
`/api/hassio/snapshots/${this._snapshot!.slug}/download` `/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
); );
} catch (err) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
@@ -279,8 +318,7 @@ class HassioSnapshotDialog
if (window.location.href.includes("ui.nabu.casa")) { if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, { const confirm = await showConfirmationDialog(this, {
title: "Potential slow download", title: "Potential slow download",
text: text: "Downloading backups over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
"Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
confirmText: "continue", confirmText: "continue",
dismissText: "cancel", dismissText: "cancel",
}); });
@@ -292,19 +330,19 @@ class HassioSnapshotDialog
fileDownload( fileDownload(
this, this,
signedPath.path, signedPath.path,
`home_assistant_snapshot_${slugify(this._computeName)}.tar` `home_assistant_backup_${slugify(this._computeName)}.tar`
); );
} }
private get _computeName() { private get _computeName() {
return this._snapshot return this._backup
? this._snapshot.name || this._snapshot.slug ? this._backup.name || this._backup.slug
: "Unnamed snapshot"; : "Unnamed backup";
} }
} }
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-snapshot": HassioSnapshotDialog; "dialog-hassio-backup": HassioBackupDialog;
} }
} }

View File

@@ -6,37 +6,37 @@ import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
createHassioFullSnapshot, createHassioFullBackup,
createHassioPartialSnapshot, createHassioPartialBackup,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content"; import "../../components/supervisor-backup-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot"; import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
@customElement("dialog-hassio-create-snapshot") @customElement("dialog-hassio-create-backup")
class HassioCreateSnapshotDialog extends LitElement { class HassioCreateBackupDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams; @state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _error?: string; @state() private _error?: string;
@state() private _creatingSnapshot = false; @state() private _creatingBackup = false;
@query("supervisor-snapshot-content") @query("supervisor-backup-content")
private _snapshotContent!: SupervisorSnapshotContent; private _backupContent!: SupervisorBackupContent;
public showDialog(params: HassioCreateSnapshotDialogParams) { public showDialog(params: HassioCreateBackupDialogParams) {
this._dialogParams = params; this._dialogParams = params;
this._creatingSnapshot = false; this._creatingBackup = false;
} }
public closeDialog() { public closeDialog() {
this._dialogParams = undefined; this._dialogParams = undefined;
this._creatingSnapshot = false; this._creatingBackup = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@@ -52,65 +52,74 @@ class HassioCreateSnapshotDialog extends LitElement {
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, this.hass,
this._dialogParams.supervisor.localize("snapshot.create_snapshot") this._dialogParams.supervisor.localize("backup.create_backup")
)} )}
> >
${this._creatingSnapshot ${this._creatingBackup
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-backup-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
> >
</supervisor-snapshot-content>`} </supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}> <mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")} ${this._dialogParams.supervisor.localize("common.close")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button
.disabled=${this._creatingSnapshot} .disabled=${this._creatingBackup}
slot="primaryAction" slot="primaryAction"
@click=${this._createSnapshot} @click=${this._createBackup}
> >
${this._dialogParams.supervisor.localize("snapshot.create")} ${this._dialogParams.supervisor.localize("backup.create")}
</mwc-button> </mwc-button>
</ha-dialog> </ha-dialog>
`; `;
} }
private async _createSnapshot(): Promise<void> { private async _createBackup(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") { if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, { showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize( title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create" "backup.could_not_create"
), ),
text: this._dialogParams!.supervisor.localize( text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running", "backup.create_blocked_not_running",
"state", "state",
this._dialogParams!.supervisor.info.state this._dialogParams!.supervisor.info.state
), ),
}); });
return; return;
} }
const snapshotDetails = this._snapshotContent.snapshotDetails(); const backupDetails = this._backupContent.backupDetails();
this._creatingSnapshot = true; this._creatingBackup = true;
this._error = ""; this._error = "";
if (backupDetails.password && !backupDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"backup.enter_password"
);
this._creatingBackup = false;
return;
}
if ( if (
this._snapshotContent.snapshotHasPassword && backupDetails.password &&
!this._snapshotContent.snapshotPassword.length backupDetails.password !== backupDetails.confirm_password
) { ) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password" "backup.passwords_not_matching"
); );
this._creatingSnapshot = false; this._creatingBackup = false;
return; return;
} }
delete backupDetails.confirm_password;
try { try {
if (this._snapshotContent.snapshotType === "full") { if (this._backupContent.backupType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails); await createHassioFullBackup(this.hass, backupDetails);
} else { } else {
await createHassioPartialSnapshot(this.hass, snapshotDetails); await createHassioPartialBackup(this.hass, backupDetails);
} }
this._dialogParams!.onCreate(); this._dialogParams!.onCreate();
@@ -118,7 +127,7 @@ class HassioCreateSnapshotDialog extends LitElement {
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._creatingSnapshot = false; this._creatingBackup = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@@ -137,6 +146,6 @@ class HassioCreateSnapshotDialog extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog; "dialog-hassio-create-backup": HassioCreateBackupDialog;
} }
} }

View File

@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-backup-upload";
export interface HassioBackupUploadDialogParams {
showBackup: (slug: string) => void;
reloadBackup?: () => Promise<void>;
onboarding?: boolean;
}
export const showBackupUploadDialog = (
element: HTMLElement,
dialogParams: HassioBackupUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup-upload",
dialogImport: () => import("./dialog-hassio-backup-upload"),
dialogParams,
});
};

View File

@@ -1,20 +1,22 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams { export interface HassioBackupDialogParams {
slug: string; slug: string;
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
supervisor?: Supervisor; supervisor?: Supervisor;
localize?: LocalizeFunc;
} }
export const showHassioSnapshotDialog = ( export const showHassioBackupDialog = (
element: HTMLElement, element: HTMLElement,
dialogParams: HassioSnapshotDialogParams dialogParams: HassioBackupDialogParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot", dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-snapshot"), dialogImport: () => import("./dialog-hassio-backup"),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -1,18 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioCreateSnapshotDialogParams { export interface HassioCreateBackupDialogParams {
supervisor: Supervisor; supervisor: Supervisor;
onCreate: () => void; onCreate: () => void;
} }
export const showHassioCreateSnapshotDialog = ( export const showHassioCreateBackupDialog = (
element: HTMLElement, element: HTMLElement,
dialogParams: HassioCreateSnapshotDialogParams dialogParams: HassioCreateBackupDialogParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-create-snapshot", dialogTag: "dialog-hassio-create-backup",
dialogImport: () => import("./dialog-hassio-create-snapshot"), dialogImport: () => import("./dialog-hassio-create-backup"),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -61,10 +61,6 @@ class HassioMarkdownDialog extends LitElement {
app-toolbar [main-title] { app-toolbar [main-title] {
margin-left: 16px; margin-left: 16px;
} }
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) { @media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog { ha-paper-dialog {
max-height: 100%; max-height: 100%;

View File

@@ -41,7 +41,8 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network") @customElement("dialog-hassio-network")
export class DialogHassioNetwork export class DialogHassioNetwork
extends LitElement extends LitElement
implements HassDialog<HassioNetworkDialogParams> { implements HassDialog<HassioNetworkDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@@ -492,7 +493,7 @@ export class DialogHassioNetwork
} }
private _handleRadioValueChangedAp(ev: CustomEvent): void { private _handleRadioValueChangedAp(ev: CustomEvent): void {
const value = ((ev.target as any).value as string) as const value = (ev.target as any).value as string as
| "open" | "open"
| "wep" | "wep"
| "wpa-psk"; | "wpa-psk";

View File

@@ -161,9 +161,9 @@ class HassioRegistriesDialog extends LitElement {
public focus(): void { public focus(): void {
this.updateComplete.then(() => this.updateComplete.then(() =>
(this.shadowRoot?.querySelector( (
"[dialogInitialFocus]" this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
) as HTMLElement)?.focus() )?.focus()
); );
} }

View File

@@ -161,9 +161,9 @@ class HassioRepositoriesDialog extends LitElement {
public focus() { public focus() {
this.updateComplete.then(() => this.updateComplete.then(() =>
(this.shadowRoot?.querySelector( (
"[dialogInitialFocus]" this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
) as HTMLElement)?.focus() )?.focus()
); );
} }

View File

@@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-snapshot-upload";
export interface HassioSnapshotUploadDialogParams {
showSnapshot: (slug: string) => void;
reloadSnapshot?: () => Promise<void>;
onboarding?: boolean;
}
export const showSnapshotUploadDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot-upload",
dialogImport: () => import("./dialog-hassio-snapshot-upload"),
dialogParams,
});
};

View File

@@ -11,7 +11,7 @@ import {
extractApiErrorMessage, extractApiErrorMessage,
ignoreSupervisorError, ignoreSupervisorError,
} from "../../../../src/data/hassio/common"; } from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot"; import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update"; import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@@ -22,9 +22,9 @@ class DialogSupervisorUpdate extends LitElement {
@state() private _opened = false; @state() private _opened = false;
@state() private _createSnapshot = true; @state() private _createBackup = true;
@state() private _action: "snapshot" | "update" | null = null; @state() private _action: "backup" | "update" | null = null;
@state() private _error?: string; @state() private _error?: string;
@@ -41,7 +41,7 @@ class DialogSupervisorUpdate extends LitElement {
public closeDialog(): void { public closeDialog(): void {
this._action = null; this._action = null;
this._createSnapshot = true; this._createBackup = true;
this._error = undefined; this._error = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -49,9 +49,9 @@ class DialogSupervisorUpdate extends LitElement {
public focus(): void { public focus(): void {
this.updateComplete.then(() => this.updateComplete.then(() =>
(this.shadowRoot?.querySelector( (
"[dialogInitialFocus]" this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
) as HTMLElement)?.focus() )?.focus()
); );
} }
@@ -84,20 +84,20 @@ class DialogSupervisorUpdate extends LitElement {
<ha-settings-row> <ha-settings-row>
<span slot="heading"> <span slot="heading">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.snapshot" "dialog.update.backup"
)} )}
</span> </span>
<span slot="description"> <span slot="description">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.create_snapshot", "dialog.update.create_backup",
"name", "name",
this._dialogParams.name this._dialogParams.name
)} )}
</span> </span>
<ha-switch <ha-switch
.checked=${this._createSnapshot} .checked=${this._createBackup}
haptic haptic
@click=${this._toggleSnapshot} @click=${this._toggleBackup}
> >
</ha-switch> </ha-switch>
</ha-settings-row> </ha-settings-row>
@@ -123,7 +123,7 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version this._dialogParams.version
) )
: this._dialogParams.supervisor.localize( : this._dialogParams.supervisor.localize(
"dialog.update.snapshotting", "dialog.update.creating_backup",
"name", "name",
this._dialogParams.name this._dialogParams.name
)} )}
@@ -133,17 +133,17 @@ class DialogSupervisorUpdate extends LitElement {
`; `;
} }
private _toggleSnapshot() { private _toggleBackup() {
this._createSnapshot = !this._createSnapshot; this._createBackup = !this._createBackup;
} }
private async _update() { private async _update() {
if (this._createSnapshot) { if (this._createBackup) {
this._action = "snapshot"; this._action = "backup";
try { try {
await createHassioPartialSnapshot( await createHassioPartialBackup(
this.hass, this.hass,
this._dialogParams!.snapshotParams this._dialogParams!.backupParams
); );
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
@@ -158,8 +158,8 @@ class DialogSupervisorUpdate extends LitElement {
} catch (err) { } catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
}
this._action = null; this._action = null;
}
return; return;
} }

View File

@@ -5,7 +5,7 @@ export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor; supervisor: Supervisor;
name: string; name: string;
version: string; version: string;
snapshotParams: any; backupParams: any;
updateHandler: () => Promise<void>; updateHandler: () => Promise<void>;
} }

View File

@@ -121,7 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
} }
} else { } else {
themeName = themeName =
((this.hass.selectedTheme as unknown) as string) || (this.hass.selectedTheme as unknown as string) ||
this.hass.themes.default_theme; this.hass.themes.default_theme;
} }

View File

@@ -1,19 +1,19 @@
import { html, LitElement, TemplateResult } from "lit";
import { sanitizeUrl } from "@braintree/sanitize-url"; import { sanitizeUrl } from "@braintree/sanitize-url";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../src/common/navigate";
import { import {
createSearchParam, createSearchParam,
extractSearchParamsObject, extractSearchParamsObject,
} from "../../src/common/url/search-params"; } from "../../src/common/url/search-params";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import "../../src/layouts/hass-error-screen"; import "../../src/layouts/hass-error-screen";
import { import {
ParamType, ParamType,
Redirect, Redirect,
Redirects, Redirects,
} from "../../src/panels/my/ha-panel-my"; } from "../../src/panels/my/ha-panel-my";
import { navigate } from "../../src/common/navigate";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
const REDIRECTS: Redirects = { const REDIRECTS: Redirects = {
supervisor: { supervisor: {
@@ -26,7 +26,10 @@ const REDIRECTS: Redirects = {
redirect: "/hassio/system", redirect: "/hassio/system",
}, },
supervisor_snapshots: { supervisor_snapshots: {
redirect: "/hassio/snapshots", redirect: "/hassio/backups",
},
supervisor_backups: {
redirect: "/hassio/backups",
}, },
supervisor_store: { supervisor_store: {
redirect: "/hassio/store", redirect: "/hassio/store",

View File

@@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast. // Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard"; import "./dashboard/hassio-dashboard";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi // Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./snapshots/hassio-snapshots"; import "./backups/hassio-backups";
import "./system/hassio-system"; import "./system/hassio-system";
@customElement("hassio-panel-router") @customElement("hassio-panel-router")
@@ -23,6 +23,8 @@ class HassioPanelRouter extends HassRouterPage {
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
routes: { routes: {
dashboard: { dashboard: {
tag: "hassio-dashboard", tag: "hassio-dashboard",
@@ -30,8 +32,8 @@ class HassioPanelRouter extends HassRouterPage {
store: { store: {
tag: "hassio-addon-store", tag: "hassio-addon-store",
}, },
snapshots: { backups: {
tag: "hassio-snapshots", tag: "hassio-backups",
}, },
system: { system: {
tag: "hassio-system", tag: "hassio-system",

View File

@@ -23,6 +23,8 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it. // Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard", defaultPage: "dashboard",
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
initialLoad: () => this._redirectIngress(), initialLoad: () => this._redirectIngress(),
showLoading: true, showLoading: true,
routes: { routes: {
@@ -30,7 +32,7 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-panel", tag: "hassio-panel",
cache: true, cache: true,
}, },
snapshots: "dashboard", backups: "dashboard",
store: "dashboard", store: "dashboard",
system: "dashboard", system: "dashboard",
addon: { addon: {

View File

@@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
iconPath: mdiStore, iconPath: mdiStore,
}, },
{ {
translationKey: "panel.snapshots", translationKey: "panel.backups",
path: `/hassio/snapshots`, path: `/hassio/backups`,
iconPath: mdiBackupRestore, iconPath: mdiBackupRestore,
}, },
{ {

View File

@@ -86,10 +86,8 @@ export class SupervisorBaseElement extends urlSyncMixin(
const unsubs = Object.keys(this._unsubs); const unsubs = Object.keys(this._unsubs);
for (const collection of Object.keys(this._collections)) { for (const collection of Object.keys(this._collections)) {
if (!unsubs.includes(collection)) { if (!unsubs.includes(collection)) {
this._unsubs[collection] = this._collections[ this._unsubs[collection] = this._collections[collection].subscribe(
collection (data) => this._updateSupervisor({ [collection]: data })
].subscribe((data) =>
this._updateSupervisor({ [collection]: data })
); );
} }
} }

View File

@@ -165,7 +165,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: "Home Assistant Core", name: "Home Assistant Core",
version: this.supervisor.core.version_latest, version: this.supervisor.core.version_latest,
snapshotParams: { backupParams: {
name: `core_${this.supervisor.core.version}`, name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"], folders: ["homeassistant"],
homeassistant: true, homeassistant: true,

View File

@@ -113,7 +113,7 @@ class HassioHostInfo extends LitElement {
` `
: ""} : ""}
</ha-settings-row> </ha-settings-row>
${!this.supervisor.host.features.includes("hassos") ${!this.supervisor.host.features.includes("haos")
? html`<ha-settings-row> ? html`<ha-settings-row>
<span slot="heading"> <span slot="heading">
${this.supervisor.localize("system.host.docker_version")} ${this.supervisor.localize("system.host.docker_version")}
@@ -190,7 +190,7 @@ class HassioHostInfo extends LitElement {
<mwc-list-item> <mwc-list-item>
${this.supervisor.localize("system.host.hardware")} ${this.supervisor.localize("system.host.hardware")}
</mwc-list-item> </mwc-list-item>
${this.supervisor.host.features.includes("hassos") ${this.supervisor.host.features.includes("haos")
? html`<mwc-list-item> ? html`<mwc-list-item>
${this.supervisor.localize("system.host.import_from_usb")} ${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>` </mwc-list-item>`

View File

@@ -1,4 +1,4 @@
module.exports = { module.exports = {
"*.{js,ts}": "eslint --fix", "*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write", "!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
}; };

3
netlify.toml Normal file
View File

@@ -0,0 +1,3 @@
[build.environment]
YARN_VERSION = "1.22.11"
NODE_OPTIONS = "--max_old_space_size=4096"

View File

@@ -17,7 +17,7 @@
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types", "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier", "format": "yarn run format:eslint && yarn run format:prettier",
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"", "mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
"test": "yarn run lint && yarn run mocha" "test": "yarn run mocha"
}, },
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -42,74 +42,72 @@
"@fullcalendar/daygrid": "5.1.0", "@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0", "@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0", "@fullcalendar/list": "5.1.0",
"@lit-labs/virtualizer": "^0.6.0", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
"@material/chips": "=12.0.0-canary.1a8d06483.0", "@material/chips": "12.0.0-canary.22d29cbb4.0",
"@material/mwc-button": "canary", "@material/data-table": "12.0.0-canary.22d29cbb4.0",
"@material/mwc-checkbox": "canary", "@material/mwc-button": "0.22.1",
"@material/mwc-circular-progress": "canary", "@material/mwc-checkbox": "0.22.1",
"@material/mwc-dialog": "canary", "@material/mwc-circular-progress": "0.22.1",
"@material/mwc-fab": "canary", "@material/mwc-dialog": "0.22.1",
"@material/mwc-formfield": "canary", "@material/mwc-fab": "0.22.1",
"@material/mwc-icon-button": "canary", "@material/mwc-formfield": "0.22.1",
"@material/mwc-list": "canary", "@material/mwc-icon-button": "0.22.1",
"@material/mwc-menu": "canary", "@material/mwc-linear-progress": "0.22.1",
"@material/mwc-radio": "canary", "@material/mwc-list": "0.22.1",
"@material/mwc-ripple": "canary", "@material/mwc-menu": "0.22.1",
"@material/mwc-switch": "canary", "@material/mwc-radio": "0.22.1",
"@material/mwc-tab": "canary", "@material/mwc-ripple": "0.22.1",
"@material/mwc-tab-bar": "canary", "@material/mwc-switch": "0.22.1",
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0", "@material/mwc-tab": "0.22.1",
"@material/mwc-tab-bar": "0.22.1",
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
"@mdi/js": "5.9.55", "@mdi/js": "5.9.55",
"@mdi/svg": "5.9.55", "@mdi/svg": "5.9.55",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.1.0",
"@polymer/app-storage": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1", "@polymer/iron-icon": "^3.0.1",
"@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1", "@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^3.0.1", "@polymer/iron-overlay-behavior": "^3.0.3",
"@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-resizable-behavior": "^3.0.1", "@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-checkbox": "^3.1.0", "@polymer/paper-checkbox": "^3.1.0",
"@polymer/paper-dialog": "^3.0.1", "@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1", "@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1", "@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.0.1", "@polymer/paper-dropdown-menu": "^3.2.0",
"@polymer/paper-input": "^3.0.1", "@polymer/paper-input": "^3.2.1",
"@polymer/paper-item": "^3.0.1", "@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1", "@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-menu-button": "^3.0.1", "@polymer/paper-menu-button": "^3.1.0",
"@polymer/paper-progress": "^3.0.1", "@polymer/paper-progress": "^3.0.1",
"@polymer/paper-radio-button": "^3.0.1", "@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1", "@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.1", "@polymer/paper-ripple": "^3.0.2",
"@polymer/paper-slider": "^3.0.1", "@polymer/paper-slider": "^3.0.1",
"@polymer/paper-styles": "^3.0.1", "@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.0.1", "@polymer/paper-tabs": "^3.1.0",
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.2", "@thomasloven/round-slider": "0.5.2",
"@vaadin/vaadin-combo-box": "^5.0.10", "@vaadin/vaadin-combo-box": "^20.0.1",
"@vaadin/vaadin-date-picker": "^4.0.7", "@vaadin/vaadin-date-picker": "^20.0.1",
"@vibrant/color": "^3.2.1-alpha.1", "@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.2.0", "@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7", "@webcomponents/webcomponentsjs": "^2.2.10",
"chart.js": "^2.9.4", "chart.js": "^3.3.2",
"chartjs-chart-timeline": "^0.4.0",
"comlink": "^4.3.1", "comlink": "^4.3.1",
"core-js": "^3.6.5", "core-js": "^3.15.2",
"cropperjs": "^1.5.11", "cropperjs": "^1.5.11",
"date-fns": "^2.22.1",
"deep-clone-simple": "^1.1.1", "deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1", "deep-freeze": "^0.0.1",
"fecha": "^4.2.0", "fecha": "^4.2.0",
"fuse.js": "^6.0.0", "fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^1.0.5", "hls.js": "^1.0.7",
"home-assistant-js-websocket": "^5.10.0", "home-assistant-js-websocket": "^5.11.1",
"idb-keyval": "^5.0.5", "idb-keyval": "^5.0.5",
"intl-messageformat": "^9.6.16", "intl-messageformat": "^9.6.16",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
@@ -124,7 +122,7 @@
"proxy-polyfill": "^0.3.1", "proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.2", "regenerator-runtime": "^0.13.8",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2", "sortablejs": "^1.10.2",
@@ -146,17 +144,17 @@
"xss": "^1.0.9" "xss": "^1.0.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.3", "@babel/core": "^7.14.6",
"@babel/plugin-external-helpers": "^7.12.13", "@babel/plugin-external-helpers": "^7.14.5",
"@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.13.15", "@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.13.8", "@babel/plugin-proposal-object-rest-spread": "^7.14.7",
"@babel/plugin-proposal-optional-chaining": "^7.13.12", "@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.14.2", "@babel/preset-env": "^7.14.7",
"@babel/preset-typescript": "^7.13.0", "@babel/preset-typescript": "^7.14.5",
"@koa/cors": "^3.1.0", "@koa/cors": "^3.1.0",
"@open-wc/dev-server-hmr": "^0.0.2", "@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1", "@rollup/plugin-babel": "^5.2.1",
@@ -173,22 +171,22 @@
"@types/mocha": "^8.2.2", "@types/mocha": "^8.2.2",
"@types/sortablejs": "^1.10.6", "@types/sortablejs": "^1.10.6",
"@types/webspeechapi": "^0.0.29", "@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.22.0", "@typescript-eslint/parser": "^4.28.3",
"@web/dev-server": "^0.0.24", "@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11", "@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^8.1.0", "babel-loader": "^8.2.2",
"chai": "^4.3.4", "chai": "^4.3.4",
"cpx": "^1.5.0",
"del": "^4.0.0", "del": "^4.0.0",
"eslint": "^7.25.0", "eslint": "^7.30.0",
"eslint-config-airbnb-typescript": "^12.3.1", "eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.0", "eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-disable": "^2.0.1", "eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-lit": "^1.3.0", "eslint-plugin-lit": "^1.5.1",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-unused-imports": "^1.1.2",
"eslint-plugin-wc": "^1.3.0", "eslint-plugin-wc": "^1.3.0",
"fancy-log": "^1.3.3", "fancy-log": "^1.3.3",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
@@ -200,7 +198,7 @@
"gulp-zopfli-green": "^3.0.1", "gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"husky": "^1.3.1", "husky": "^1.3.1",
"lint-staged": "^10.5.4", "lint-staged": "^11.0.1",
"lit-analyzer": "^1.2.1", "lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
@@ -209,8 +207,7 @@
"mocha": "^8.4.0", "mocha": "^8.4.0",
"object-hash": "^2.0.3", "object-hash": "^2.0.3",
"open": "^7.0.4", "open": "^7.0.4",
"prettier": "^2.0.4", "prettier": "^2.3.2",
"raw-loader": "^2.0.0",
"require-dir": "^1.2.0", "require-dir": "^1.2.0",
"rollup": "^2.8.2", "rollup": "^2.8.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
@@ -220,23 +217,22 @@
"sinon": "^11.0.0", "sinon": "^11.0.0",
"source-map-url": "^0.4.0", "source-map-url": "^0.4.0",
"systemjs": "^6.3.2", "systemjs": "^6.3.2",
"terser-webpack-plugin": "^5.1.2", "terser-webpack-plugin": "^5.1.4",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"ts-mocha": "^8.0.0", "ts-mocha": "^8.0.0",
"typescript": "^4.2.4", "typescript": "^4.3.5",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0", "vinyl-source-stream": "^2.0.0",
"webpack": "^5.24.1", "webpack": "^5.43.0",
"webpack-cli": "^4.5.0", "webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpack-manifest-plugin": "^3.0.0", "webpack-manifest-plugin": "^3.1.1",
"workbox-build": "^6.1.5" "workbox-build": "^6.1.5"
}, },
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page", "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"resolutions": { "resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10", "@webcomponents/webcomponentsjs": "^2.2.10",
"@polymer/polymer": "3.1.0",
"lit-html": "2.0.0-rc.3", "lit-html": "2.0.0-rc.3",
"lit-element": "3.0.0-rc.2" "lit-element": "3.0.0-rc.2"
}, },

View File

@@ -9,12 +9,6 @@ if [ -z "${DEVCONTAINER}" ]; then
exit 1 exit 1
fi fi
if [ ! -z "${CODESPACES}" ]; then
WORKSPACE="/root/workspace/frontend"
else
WORKSPACE="/workspaces/frontend"
fi
if [ -z $(which hass) ]; then if [ -z $(which hass) ]; then
echo "Installing Home Asstant core from dev." echo "Installing Home Asstant core from dev."
python3 -m pip install --upgrade \ python3 -m pip install --upgrade \
@@ -22,9 +16,9 @@ if [ -z $(which hass) ]; then
git+git://github.com/home-assistant/home-assistant.git@dev git+git://github.com/home-assistant/home-assistant.git@dev
fi fi
if [ ! -d "${WORKSPACE}/config" ]; then if [ ! -d "/workspaces/frontend/config" ]; then
echo "Creating default configuration." echo "Creating default configuration."
mkdir -p "${WORKSPACE}/config"; mkdir -p "/workspaces/frontend/config";
hass --script ensure_config -c config hass --script ensure_config -c config
echo "demo: echo "demo:
@@ -32,24 +26,24 @@ logger:
default: info default: info
logs: logs:
homeassistant.components.frontend: debug homeassistant.components.frontend: debug
" >> "${WORKSPACE}/config/configuration.yaml" " >> /workspaces/frontend/config/configuration.yaml
if [ ! -z "${HASSIO}" ]; then if [ ! -z "${HASSIO}" ]; then
echo " echo "
# frontend: # frontend:
# development_repo: ${WORKSPACE} # development_repo: /workspaces/frontend
hassio: hassio:
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml" development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
else else
echo " echo "
frontend: frontend:
development_repo: ${WORKSPACE} development_repo: /workspaces/frontend
# hassio: # hassio:
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml" # development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
fi fi
fi fi
hass -c "${WORKSPACE}/config" hass -c /workspaces/frontend/config

View File

@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20210603.0", version="20210813.0",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer", url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors", author="The Home Assistant Authors",
author_email="hello@home-assistant.io", author_email="hello@home-assistant.io",
license="Apache-2.0", license="Apache-2.0",

View File

@@ -7,6 +7,7 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import "./ha-password-manager-polyfill";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import "../components/ha-form/ha-form"; import "../components/ha-form/ha-form";
import "../components/ha-markdown"; import "../components/ha-markdown";
@@ -20,7 +21,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
type State = "loading" | "error" | "step"; type State = "loading" | "error" | "step";
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@property() public authProvider?: AuthProvider; @property({ attribute: false }) public authProvider?: AuthProvider;
@property() public clientId?: string; @property() public clientId?: string;
@@ -37,7 +38,15 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@state() private _errorMessage?: string; @state() private _errorMessage?: string;
protected render() { protected render() {
return html` <form>${this._renderForm()}</form> `; return html`
<form>${this._renderForm()}</form>
<ha-password-manager-polyfill
.step=${this._step}
.stepData=${this._stepData}
@form-submitted=${this._handleSubmit}
@value-changed=${this._stepDataChanged}
></ha-password-manager-polyfill>
`;
} }
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
@@ -231,11 +240,17 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
await this.updateComplete; await this.updateComplete;
// 100ms to give all the form elements time to initialize. // 100ms to give all the form elements time to initialize.
setTimeout(() => { setTimeout(() => {
const form = this.shadowRoot!.querySelector("ha-form"); const form = this.renderRoot.querySelector("ha-form");
if (form) { if (form) {
(form as any).focus(); (form as any).focus();
} }
}, 100); }, 100);
setTimeout(() => {
this.renderRoot.querySelector(
"ha-password-manager-polyfill"
)!.boundingRect = this.getBoundingClientRect();
}, 500);
} }
private _stepDataChanged(ev: CustomEvent) { private _stepDataChanged(ev: CustomEvent) {
@@ -329,3 +344,9 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
} }
} }
customElements.define("ha-auth-flow", HaAuthFlow); customElements.define("ha-auth-flow", HaAuthFlow);
declare global {
interface HTMLElementTagNameMap {
"ha-auth-flow": HaAuthFlow;
}
}

View File

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

View File

@@ -0,0 +1,63 @@
export const COLORS = [
"#377eb8",
"#984ea3",
"#00d2d5",
"#ff7f00",
"#af8d00",
"#7f80cd",
"#b3e900",
"#c42e60",
"#a65628",
"#f781bf",
"#8dd3c7",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#fccde5",
"#bc80bd",
"#ffed6f",
"#c4eaff",
"#cf8c00",
"#1b9e77",
"#d95f02",
"#e7298a",
"#e6ab02",
"#a6761d",
"#0097ff",
"#00d067",
"#f43600",
"#4ba93b",
"#5779bb",
"#927acc",
"#97ee3f",
"#bf3947",
"#9f5b00",
"#f48758",
"#8caed6",
"#f2b94f",
"#eff26e",
"#e43872",
"#d9b100",
"#9d7a00",
"#698cff",
"#d9d9d9",
"#00d27e",
"#d06800",
"#009f82",
"#c49200",
"#cbe8ff",
"#fecddf",
"#c27eb6",
"#8cd2ce",
"#c4b8d9",
"#f883b0",
"#a49100",
"#f48800",
"#27d0df",
"#a04a9b",
];
export function getColorByIndex(index: number) {
return COLORS[index % COLORS.length];
}

View File

@@ -1,4 +1,4 @@
const luminosity = (rgb: [number, number, number]): number => { export const luminosity = (rgb: [number, number, number]): number => {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef // http://www.w3.org/TR/WCAG20/#relativeluminancedef
const lum: [number, number, number] = [0, 0, 0]; const lum: [number, number, number] = [0, 0, 0];
for (let i = 0; i < rgb.length; i++) { for (let i = 0; i < rgb.length; i++) {

View File

@@ -42,6 +42,7 @@ export const FIXED_DOMAIN_ICONS = {
remote: "hass:remote", remote: "hass:remote",
scene: "hass:palette", scene: "hass:palette",
script: "hass:script-text", script: "hass:script-text",
select: "hass:format-list-bulleted",
sensor: "hass:eye", sensor: "hass:eye",
simple_alarm: "hass:bell", simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny", sun: "hass:white-balance-sunny",
@@ -58,10 +59,11 @@ export const FIXED_DEVICE_CLASS_ICONS = {
current: "hass:current-ac", current: "hass:current-ac",
carbon_dioxide: "mdi:molecule-co2", carbon_dioxide: "mdi:molecule-co2",
carbon_monoxide: "mdi:molecule-co", carbon_monoxide: "mdi:molecule-co",
energy: "hass:flash", energy: "hass:lightning-bolt",
humidity: "hass:water-percent", humidity: "hass:water-percent",
illuminance: "hass:brightness-5", illuminance: "hass:brightness-5",
temperature: "hass:thermometer", temperature: "hass:thermometer",
monetary: "mdi:cash",
pressure: "hass:gauge", pressure: "hass:gauge",
power: "hass:flash", power: "hass:flash",
power_factor: "hass:angle-acute", power_factor: "hass:angle-acute",
@@ -83,6 +85,7 @@ export const DOMAINS_WITH_CARD = [
"number", "number",
"scene", "scene",
"script", "script",
"select",
"timer", "timer",
"vacuum", "vacuum",
"water_heater", "water_heater",
@@ -121,6 +124,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_text", "input_text",
"number", "number",
"scene", "scene",
"select",
]; ];
/** Domains that should have the history hidden in the more info dialog. */ /** Domains that should have the history hidden in the more info dialog. */

View File

@@ -26,6 +26,9 @@ function checkToLocaleStringSupportsOptions() {
return false; return false;
} }
export const toLocaleDateStringSupportsOptions = checkToLocaleDateStringSupportsOptions(); export const toLocaleDateStringSupportsOptions =
export const toLocaleTimeStringSupportsOptions = checkToLocaleTimeStringSupportsOptions(); checkToLocaleDateStringSupportsOptions();
export const toLocaleStringSupportsOptions = checkToLocaleStringSupportsOptions(); export const toLocaleTimeStringSupportsOptions =
checkToLocaleTimeStringSupportsOptions();
export const toLocaleStringSupportsOptions =
checkToLocaleStringSupportsOptions();

View File

@@ -0,0 +1,31 @@
import { HaDurationData } from "../../components/ha-duration-input";
import { ForDict } from "../../data/automation";
export const createDurationData = (
duration: string | number | ForDict | undefined
): HaDurationData => {
if (duration === undefined) {
return {};
}
if (typeof duration !== "object") {
if (typeof duration === "string" || isNaN(duration)) {
const parts = duration?.toString().split(":") || [];
return {
hours: Number(parts[0]) || 0,
minutes: Number(parts[1]) || 0,
seconds: Number(parts[2]) || 0,
milliseconds: Number(parts[3]) || 0,
};
}
return { seconds: duration };
}
const { days, minutes, seconds, milliseconds } = duration;
let hours = duration.hours || 0;
hours = (hours || 0) + (days || 0) * 24;
return {
hours,
minutes,
seconds,
milliseconds,
};
};

View File

@@ -3,20 +3,11 @@ import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { toLocaleDateStringSupportsOptions } from "./check_options_support"; import { toLocaleDateStringSupportsOptions } from "./check_options_support";
const formatDateMem = memoizeOne( // Tuesday, August 10
(locale: FrontendLocaleData) => export const formatDateWeekday = toLocaleDateStringSupportsOptions
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
})
);
export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) => ? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMem(locale).format(dateObj) formatDateWeekdayMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "longDate"); : (dateObj: Date) => format(dateObj, "dddd, MMMM D");
const formatDateWeekdayMem = memoizeOne( const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
@@ -26,7 +17,80 @@ const formatDateWeekdayMem = memoizeOne(
}) })
); );
export const formatDateWeekday = toLocaleDateStringSupportsOptions // August 10, 2021
export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) => ? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj) formatDateMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "dddd, MMM D"); : (dateObj: Date) => format(dateObj, "MMMM D, YYYY");
const formatDateMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
})
);
// 10/08/2021
export const formatDateNumeric = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateNumericMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "M/D/YYYY");
const formatDateNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
})
);
// Aug 10
export const formatDateShort = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateShortMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMM D");
const formatDateShortMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
})
);
// August 2021
export const formatDateMonthYear = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthYearMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM YYYY");
const formatDateMonthYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
})
);
// August
export const formatDateMonth = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM");
const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
})
);
// 2021
export const formatDateYear = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateYearMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "YYYY");
const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
})
);

View File

@@ -4,6 +4,12 @@ import { FrontendLocaleData } from "../../data/translation";
import { toLocaleStringSupportsOptions } from "./check_options_support"; import { toLocaleStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm"; import { useAmPm } from "./use_am_pm";
// August 9, 2021, 8:23 AM
export const formatDateTime = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeMem = memoizeOne( const formatDateTimeMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
@@ -16,12 +22,12 @@ const formatDateTimeMem = memoizeOne(
}) })
); );
export const formatDateTime = toLocaleStringSupportsOptions // August 9, 2021, 8:23:15 AM
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) => ? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj) formatDateTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) => : (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : ""); format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
const formatDateTimeWithSecondsMem = memoizeOne( const formatDateTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
@@ -35,8 +41,20 @@ const formatDateTimeWithSecondsMem = memoizeOne(
}) })
); );
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions // 9/8/2021, 8:23 AM
export const formatDateTimeNumeric = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) => ? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeWithSecondsMem(locale).format(dateObj) formatDateTimeNumericMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) => : (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : ""); format(dateObj, "M/D/YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);

View File

@@ -4,6 +4,12 @@ import { FrontendLocaleData } from "../../data/translation";
import { toLocaleTimeStringSupportsOptions } from "./check_options_support"; import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm"; import { useAmPm } from "./use_am_pm";
// 9:15 PM || 21:15
export const formatTime = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
const formatTimeMem = memoizeOne( const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
@@ -13,12 +19,12 @@ const formatTimeMem = memoizeOne(
}) })
); );
export const formatTime = toLocaleTimeStringSupportsOptions // 9:15:24 PM || 21:15:24
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) => ? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeMem(locale).format(dateObj) formatTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) => : (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : ""); format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
const formatTimeWithSecondsMem = memoizeOne( const formatTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
@@ -29,12 +35,12 @@ const formatTimeWithSecondsMem = memoizeOne(
}) })
); );
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions // Tuesday 7:00 PM || Tuesday 19:00
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) => ? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWithSecondsMem(locale).format(dateObj) formatTimeWeekdayMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) => : (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : ""); format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
const formatTimeWeekdayMem = memoizeOne( const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
@@ -44,9 +50,3 @@ const formatTimeWeekdayMem = memoizeOne(
hour12: useAmPm(locale), hour12: useAmPm(locale),
}) })
); );
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWeekdayMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");

View File

@@ -1,6 +1,7 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData, TimeFormat } from "../../data/translation"; import { FrontendLocaleData, TimeFormat } from "../../data/translation";
export const useAmPm = (locale: FrontendLocaleData): boolean => { export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
if ( if (
locale.time_format === TimeFormat.language || locale.time_format === TimeFormat.language ||
locale.time_format === TimeFormat.system locale.time_format === TimeFormat.system
@@ -12,4 +13,4 @@ export const useAmPm = (locale: FrontendLocaleData): boolean => {
} }
return locale.time_format === TimeFormat.am_pm; return locale.time_format === TimeFormat.am_pm;
}; });

View File

@@ -82,14 +82,18 @@ class Storage {
const storage = new Storage(); const storage = new Storage();
export const LocalStorage = ( export const LocalStorage =
(
storageKey?: string, storageKey?: string,
property?: boolean, property?: boolean,
propertyOptions?: PropertyDeclaration propertyOptions?: PropertyDeclaration
): any => (clsElement: ClassElement) => { ): any =>
(clsElement: ClassElement) => {
const key = String(clsElement.key); const key = String(clsElement.key);
storageKey = storageKey || String(clsElement.key); storageKey = storageKey || String(clsElement.key);
const initVal = clsElement.initializer ? clsElement.initializer() : undefined; const initVal = clsElement.initializer
? clsElement.initializer()
: undefined;
storage.addFromStorage(storageKey); storage.addFromStorage(storageKey);

View File

@@ -1,9 +1,9 @@
import type { LitElement } from "lit"; import type { LitElement } from "lit";
import type { ClassElement } from "../../types"; import type { ClassElement } from "../../types";
export const restoreScroll = (selector: string): any => ( export const restoreScroll =
element: ClassElement (selector: string): any =>
) => ({ (element: ClassElement) => ({
kind: "method", kind: "method",
placement: "prototype", placement: "prototype",
key: element.key, key: element.key,

View File

@@ -6,8 +6,7 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async ( export const setupLeafletMap = async (
mapElement: HTMLElement, mapElement: HTMLElement,
darkMode?: boolean, darkMode?: boolean
draw = false
): Promise<[Map, LeafletModuleType, TileLayer]> => { ): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) { if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element"); throw new Error("Cannot setup Leaflet map on disconnected element");
@@ -17,10 +16,6 @@ export const setupLeafletMap = async (
.default as LeafletModuleType; .default as LeafletModuleType;
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/"; Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
if (draw) {
await import("leaflet-draw");
}
const map = Leaflet.map(mapElement); const map = Leaflet.map(mapElement);
const style = document.createElement("link"); const style = document.createElement("link");
style.setAttribute("href", "/static/images/leaflet/leaflet.css"); style.setAttribute("href", "/static/images/leaflet/leaflet.css");

View File

@@ -21,6 +21,16 @@ export const computeStateDisplay = (
} }
if (stateObj.attributes.unit_of_measurement) { if (stateObj.attributes.unit_of_measurement) {
if (stateObj.attributes.device_class === "monetary") {
try {
return formatNumber(compareState, locale, {
style: "currency",
currency: stateObj.attributes.unit_of_measurement,
});
} catch (_err) {
// fallback to default
}
}
return `${formatNumber(compareState, locale)} ${ return `${formatNumber(compareState, locale)} ${
stateObj.attributes.unit_of_measurement stateObj.attributes.unit_of_measurement
}`; }`;
@@ -29,6 +39,37 @@ export const computeStateDisplay = (
const domain = computeStateDomain(stateObj); const domain = computeStateDomain(stateObj);
if (domain === "input_datetime") { if (domain === "input_datetime") {
if (state) {
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
// Attributes aren't available, we have to use `state`.
try {
const components = state.split(" ");
if (components.length === 2) {
// Date and time.
return formatDateTime(new Date(components.join("T")), locale);
}
if (components.length === 1) {
if (state.includes("-")) {
// Date only.
return formatDate(new Date(`${state}T00:00`), locale);
}
if (state.includes(":")) {
// Time only.
const now = new Date();
return formatTime(
new Date(`${now.toISOString().split("T")[0]}T${state}`),
locale
);
}
}
return state;
} catch {
// Formatting methods may throw error if date parsing doesn't go well,
// just return the state string in that case.
return state;
}
} else {
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
let date: Date; let date: Date;
if (!stateObj.attributes.has_time) { if (!stateObj.attributes.has_time) {
date = new Date( date = new Date(
@@ -39,16 +80,8 @@ export const computeStateDisplay = (
return formatDate(date, locale); return formatDate(date, locale);
} }
if (!stateObj.attributes.has_date) { if (!stateObj.attributes.has_date) {
const now = new Date(); date = new Date();
date = new Date( date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
// don't use artificial 1970 year.
now.getFullYear(),
now.getMonth(),
now.getDay(),
stateObj.attributes.hour,
stateObj.attributes.minute
);
return formatTime(date, locale); return formatTime(date, locale);
} }
@@ -61,6 +94,7 @@ export const computeStateDisplay = (
); );
return formatDateTime(date, locale); return formatDateTime(date, locale);
} }
}
if (domain === "humidifier") { if (domain === "humidifier") {
if (compareState === "on" && stateObj.attributes.humidity) { if (compareState === "on" && stateObj.attributes.humidity) {

View File

@@ -43,7 +43,17 @@ export const domainIcon = (
: "hass:air-humidifier"; : "hass:air-humidifier";
case "lock": case "lock":
return compareState === "unlocked" ? "hass:lock-open" : "hass:lock"; switch (compareState) {
case "unlocked":
return "hass:lock-open";
case "jammed":
return "hass:lock-alert";
case "locking":
case "unlocking":
return "hass:lock-clock";
default:
return "hass:lock";
}
case "media_player": case "media_player":
return compareState === "playing" ? "hass:cast-connected" : "hass:cast"; return compareState === "playing" ? "hass:cast-connected" : "hass:cast";

View File

@@ -4,7 +4,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
import { computeDomain } from "./compute_domain"; import { computeDomain } from "./compute_domain";
import { domainIcon } from "./domain_icon"; import { domainIcon } from "./domain_icon";
export const stateIcon = (state: HassEntity) => { export const stateIcon = (state?: HassEntity) => {
if (!state) { if (!state) {
return DEFAULT_DOMAIN_ICON; return DEFAULT_DOMAIN_ICON;
} }

View File

@@ -0,0 +1,2 @@
export const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);

View File

@@ -0,0 +1,2 @@
export const round = (value: number, precision = 2): number =>
Math.round(value * 10 ** precision) / 10 ** precision;

View File

@@ -67,9 +67,11 @@ class SearchInput extends LitElement {
changedProps.has("noUnderline") && changedProps.has("noUnderline") &&
(this.noUnderline || changedProps.get("noUnderline") !== undefined) (this.noUnderline || changedProps.get("noUnderline") !== undefined)
) { ) {
(this._input.inputElement!.parentElement!.shadowRoot!.querySelector( (
this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
"div.unfocused-line" "div.unfocused-line"
) as HTMLElement).style.display = this.noUnderline ? "none" : "block"; ) as HTMLElement
).style.display = this.noUnderline ? "none" : "block";
} }
} }

View File

@@ -1,4 +1,22 @@
import { FrontendLocaleData, NumberFormat } from "../../data/translation"; import { FrontendLocaleData, NumberFormat } from "../../data/translation";
import { round } from "../number/round";
export const numberFormatToLocale = (
localeOptions: FrontendLocaleData
): string | string[] | undefined => {
switch (localeOptions.number_format) {
case NumberFormat.comma_decimal:
return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
case NumberFormat.decimal_comma:
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
case NumberFormat.space_comma:
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
case NumberFormat.system:
return undefined;
default:
return localeOptions.language;
}
};
/** /**
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility. * Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
@@ -9,27 +27,12 @@ import { FrontendLocaleData, NumberFormat } from "../../data/translation";
*/ */
export const formatNumber = ( export const formatNumber = (
num: string | number, num: string | number,
locale?: FrontendLocaleData, localeOptions?: FrontendLocaleData,
options?: Intl.NumberFormatOptions options?: Intl.NumberFormatOptions
): string => { ): string => {
let format: string | string[] | undefined; const locale = localeOptions
? numberFormatToLocale(localeOptions)
switch (locale?.number_format) { : undefined;
case NumberFormat.comma_decimal:
format = ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
break;
case NumberFormat.decimal_comma:
format = ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
break;
case NumberFormat.space_comma:
format = ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
break;
case NumberFormat.system:
format = undefined;
break;
default:
format = locale?.language;
}
// Polyfill for Number.isNaN, which is more reliable than the global isNaN() // Polyfill for Number.isNaN, which is more reliable than the global isNaN()
Number.isNaN = Number.isNaN =
@@ -39,13 +42,13 @@ export const formatNumber = (
}; };
if ( if (
localeOptions?.number_format !== NumberFormat.none &&
!Number.isNaN(Number(num)) && !Number.isNaN(Number(num)) &&
Intl && Intl
locale?.number_format !== NumberFormat.none
) { ) {
try { try {
return new Intl.NumberFormat( return new Intl.NumberFormat(
format, locale,
getDefaultFormatOptions(num, options) getDefaultFormatOptions(num, options)
).format(Number(num)); ).format(Number(num));
} catch (error) { } catch (error) {
@@ -58,7 +61,12 @@ export const formatNumber = (
).format(Number(num)); ).format(Number(num));
} }
} }
return num.toString(); if (typeof num === "string") {
return num;
}
return `${round(num, options?.maximumFractionDigits).toString()}${
options?.style === "currency" ? ` ${options.currency}` : ""
}`;
}; };
/** /**
@@ -70,7 +78,10 @@ const getDefaultFormatOptions = (
num: string | number, num: string | number,
options?: Intl.NumberFormatOptions options?: Intl.NumberFormatOptions
): Intl.NumberFormatOptions => { ): Intl.NumberFormatOptions => {
const defaultOptions: Intl.NumberFormatOptions = options || {}; const defaultOptions: Intl.NumberFormatOptions = {
maximumFractionDigits: 2,
...options,
};
if (typeof num !== "string") { if (typeof num !== "string") {
return defaultOptions; return defaultOptions;

View File

@@ -6,6 +6,7 @@
// 3. Disallow dates based on week number. // 3. Disallow dates based on week number.
// 4. Disallow dates only consisting of a year. // 4. Disallow dates only consisting of a year.
// https://regex101.com/r/kc5C14/3 // https://regex101.com/r/kc5C14/3
const regexp = /^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/; const regexp =
/^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/;
export const isTimestamp = (input: string): boolean => regexp.test(input); export const isTimestamp = (input: string): boolean => regexp.test(input);

View File

@@ -29,31 +29,28 @@ export const iconColorCSS = css`
} }
ha-icon[data-domain="climate"][data-state="cooling"] { ha-icon[data-domain="climate"][data-state="cooling"] {
color: var(--cool-color, #2b9af9); color: var(--cool-color, var(--state-climate-cool-color));
} }
ha-icon[data-domain="climate"][data-state="heating"] { ha-icon[data-domain="climate"][data-state="heating"] {
color: var(--heat-color, #ff8100); color: var(--heat-color, var(--state-climate-heat-color));
} }
ha-icon[data-domain="climate"][data-state="drying"] { ha-icon[data-domain="climate"][data-state="drying"] {
color: var(--dry-color, #efbd07); color: var(--dry-color, var(--state-climate-dry-color));
} }
ha-icon[data-domain="alarm_control_panel"] { ha-icon[data-domain="alarm_control_panel"] {
color: var(--alarm-color-armed, var(--label-badge-red)); color: var(--alarm-color-armed, var(--label-badge-red));
} }
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] { ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
color: var(--alarm-color-disarmed, var(--label-badge-green)); color: var(--alarm-color-disarmed, var(--label-badge-green));
} }
ha-icon[data-domain="alarm_control_panel"][data-state="pending"], ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] { ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
color: var(--alarm-color-pending, var(--label-badge-yellow)); color: var(--alarm-color-pending, var(--label-badge-yellow));
animation: pulse 1s infinite; animation: pulse 1s infinite;
} }
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] { ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
color: var(--alarm-color-triggered, var(--label-badge-red)); color: var(--alarm-color-triggered, var(--label-badge-red));
animation: pulse 1s infinite; animation: pulse 1s infinite;
@@ -73,11 +70,11 @@ export const iconColorCSS = css`
ha-icon[data-domain="plant"][data-state="problem"], ha-icon[data-domain="plant"][data-state="problem"],
ha-icon[data-domain="zwave"][data-state="dead"] { ha-icon[data-domain="zwave"][data-state="dead"] {
color: var(--error-state-color, #db4437); color: var(--state-icon-error-color);
} }
/* Color the icon if unavailable */ /* Color the icon if unavailable */
ha-icon[data-state="unavailable"] { ha-icon[data-state="unavailable"] {
color: var(--state-icon-unavailable-color); color: var(--state-unavailable-color);
} }
`; `;

View File

@@ -5,32 +5,20 @@
// as much as it can, without ever going more than once per `wait` duration; // as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass // but if you'd like to disable the execution on the leading edge, pass
// `false for leading`. To disable execution on the trailing edge, ditto. // `false for leading`. To disable execution on the trailing edge, ditto.
export const throttle = <T extends (...args) => unknown>( export const throttle = <T extends any[]>(
func: T, func: (...args: T) => void,
wait: number, wait: number,
leading = true, leading = true,
trailing = true trailing = true
): T => { ) => {
let timeout: number | undefined; let timeout: number | undefined;
let previous = 0; let previous = 0;
let context: any; return (...args: T): void => {
let args: any;
const later = () => { const later = () => {
previous = leading === false ? 0 : Date.now(); previous = leading === false ? 0 : Date.now();
timeout = undefined; timeout = undefined;
func.apply(context, args); func(...args);
if (!timeout) {
context = null;
args = null;
}
}; };
// @ts-ignore
return function (...argmnts) {
// @ts-ignore
// @typescript-eslint/no-this-alias
context = this;
args = argmnts;
const now = Date.now(); const now = Date.now();
if (!previous && leading === false) { if (!previous && leading === false) {
previous = now; previous = now;
@@ -42,7 +30,7 @@ export const throttle = <T extends (...args) => unknown>(
timeout = undefined; timeout = undefined;
} }
previous = now; previous = now;
func.apply(context, args); func(...args);
} else if (!timeout && trailing !== false) { } else if (!timeout && trailing !== false) {
timeout = window.setTimeout(later, remaining); timeout = window.setTimeout(later, remaining);
} }

View File

@@ -7,14 +7,10 @@ interface ResultCache<T> {
export const timeCachePromiseFunc = async <T>( export const timeCachePromiseFunc = async <T>(
cacheKey: string, cacheKey: string,
cacheTime: number, cacheTime: number,
func: ( func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
hass: HomeAssistant, hass: HomeAssistant,
entityId: string, entityId: string,
...args: unknown[] ...args: any[]
) => Promise<T>,
hass: HomeAssistant,
entityId: string,
...args: unknown[]
): Promise<T> => { ): Promise<T> => {
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey]; let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];

View File

@@ -64,18 +64,18 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
this.hass this.hass
.callService(this.domain, this.service, this.serviceData) .callService(this.domain, this.service, this.serviceData)
.then( .then(
function () { () => {
el.progress = false; el.progress = false;
el.$.progress.actionSuccess(); el.$.progress.actionSuccess();
eventData.success = true; eventData.success = true;
}, },
function () { () => {
el.progress = false; el.progress = false;
el.$.progress.actionError(); el.$.progress.actionError();
eventData.success = false; eventData.success = false;
} }
) )
.then(function () { .then(() => {
el.fire("hass-service-called", eventData); el.fire("hass-service-called", eventData);
}); });
} }

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