Compare commits

...

231 Commits

Author SHA1 Message Date
Joakim Sørensen
0bfeb22209 Move snapshot toggle to persistent checkbox 2021-06-15 16:40:24 +00: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
Joakim Sørensen
c68b76e2da Add hardware dialog (#9348)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-07 10:16:33 +02:00
Joakim Sørensen
342020b420 Fix downloads on mobile (#9375) 2021-06-07 10:15:43 +02:00
GitHub Action
1e6e99e3c7 Translation update 2021-06-07 00:49:13 +00:00
GitHub Action
2e9aafc377 Translation update 2021-06-06 00:49:09 +00:00
dependabot[bot]
299c863f49 Bump ws from 6.2.1 to 6.2.2 (#9372) 2021-06-05 23:52:13 +02:00
Bram Kragten
c2792a28ba Move attributes down in more info person and timer (#9368) 2021-06-05 12:52:27 +02:00
GitHub Action
635a027a8e Translation update 2021-06-05 02:40:15 +00:00
Will Adler
a45b8ca8e7 Add period to end of sentence (#9361) 2021-06-04 08:57:03 +02:00
GitHub Action
1e6e945a07 Translation update 2021-06-04 02:52:56 +00:00
Bram Kragten
f71157c24d Remove tsc from pre commit (#9359) 2021-06-03 22:57:03 +02:00
Bram Kragten
e87a2b36cf Bumped version to 20210603.0 2021-06-03 22:51:53 +02:00
Bram Kragten
5418474f64 Polyfill globalThis in latest build (#9352) 2021-06-03 22:50:33 +02:00
Philip Allgaier
8836ba6ceb Pick the correct backend-selected active theme (#9357) 2021-06-03 22:48:52 +02:00
Bram Kragten
509c5b497a Disable babel compact option (#9335) 2021-06-03 12:34:30 -07:00
Joakim Sørensen
e00bcc9f48 Better exit navigation for my-ingress (#9342) 2021-06-03 10:01:12 -07:00
Joakim Sørensen
bdef9fd040 Add missing media folder to snapshot (#9341) 2021-06-03 10:21:04 +02:00
GitHub Action
c956491ec5 Translation update 2021-06-03 03:48:04 +00:00
Bram Kragten
68bc549d6a Use HLS light build (#9338)
* Use HLS light build

* Bump hls, backBufferLength
2021-06-02 18:34:18 +02:00
Bram Kragten
9c64eafc21 Fix ZHA visualization (#9337) 2021-06-02 18:33:55 +02:00
Bram Kragten
b05e86d442 Fix noUnderline in search input (#9339) 2021-06-02 18:33:44 +02:00
Bram Kragten
fe5f9576c6 Fix dev 2021-06-02 10:11:29 +02:00
Brynley McDonald
1b282b65b7 Add QR code to long lived access tokens dialog (#8948)
* Add QR code to long lived access tokens dialog

* Apply suggestions from code review

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

* Further changes from code review

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-02 09:59:22 +02:00
GitHub Action
e49664bad3 Translation update 2021-06-02 04:10:37 +00:00
Bram Kragten
2a30b55a43 Bumped version to 20210601.1 2021-06-01 21:30:51 +02:00
Paulus Schoutsen
9d0b20adce Add support for system options v2 (#9332)
* Add support for system options v2

* Update src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts

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

* Update dialog-config-entry-system-options.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-01 12:22:25 -07:00
Bram Kragten
acd5e1c081 Fix back button too wide on mobile (#9333) 2021-06-01 12:12:00 -07:00
Bram Kragten
cc1c5e45b2 Display error when enabling/disabling config entries (#9325) 2021-06-01 21:03:00 +02:00
Bram Kragten
038199c447 Change the type of debounce, use arrow functions (#9328) 2021-06-01 11:53:45 -07:00
Bram Kragten
8a1eab7ceb Cleanup virtualizer styles (#9327) 2021-06-01 11:51:30 -07:00
Joakim Sørensen
bc5bd35448 Filter adapters with information from the Supervisor (#9322)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-01 20:12:54 +02:00
Bram Kragten
1795fd56b7 Don't rotate chart axis labels (#9329) 2021-06-01 10:28:03 -07:00
Bram Kragten
4a7c33edad Bumped version to 20210601.0 2021-06-01 11:40:37 +02:00
rianadon
797f60d725 Show pressure units in weather details card (#9295) 2021-06-01 11:40:04 +02:00
Bram Kragten
2427d68aa1 Use local version 0.7 of lit-virtualizer (#9321) 2021-06-01 11:39:15 +02:00
GitHub Action
00c6b0f8ed Translation update 2021-06-01 04:14:44 +00:00
Paulus Schoutsen
7b8d4ab3d6 Update translations 2021-05-31 15:50:47 -07:00
Paulus Schoutsen
07a1a805f6 Bumped version to 20210531.1 2021-05-31 15:44:59 -07:00
Paulus Schoutsen
d8bab6aba9 Add support for disable polling system option (#9316) 2021-05-31 15:40:50 -07:00
Bram Kragten
a930e2dc75 Fix store auth disappearing (#9312) 2021-05-31 15:35:51 -07:00
J. Nick Koston
2eb35668fa Seperate addresses in network configuration (#9319) 2021-05-31 15:31:33 -07:00
GitHub Action
07f4e5ac5c Translation update 2021-05-31 03:47:12 +00:00
Paulus Schoutsen
db82a90414 Bumped version to 20210531.0 2021-05-30 20:17:09 -07:00
Bram Kragten
51a693badf Convert ha-store-auth-card to Lit/TS/ha-card (#9300) 2021-05-30 20:16:45 -07:00
Bram Kragten
2aa8f5b352 Dev states: replace pattern in word by wildcard search (#9288) 2021-05-30 20:11:53 -07:00
Bram Kragten
93b3b8f985 Fix editor structs (#9286) 2021-05-30 20:08:46 -07:00
Bram Kragten
92c8bd80b5 Catch translation errors (#9299) 2021-05-30 17:02:03 +02:00
Philip Allgaier
528af0157d Move entity attribution out of attribute expansion panel (#9296) 2021-05-30 16:06:22 +02:00
Bram Kragten
10a77b6278 Update translations 2021-05-30 16:02:03 +02:00
GitHub Action
03bbf6a582 Translation update 2021-05-30 03:38:49 +00:00
GitHub Action
63fcb649d2 Translation update 2021-05-29 03:21:09 +00:00
Bram Kragten
4f60a92b92 Fix default themes (#9290)
* Fix default themes

* Simplify pick theme row
2021-05-28 20:02:28 -07:00
Bram Kragten
0419c1a41f Fix icon loading (#9289) 2021-05-28 19:54:28 -07:00
Joakim Sørensen
2d5ae78521 Add selection to snapshot table for mass deletion (#9284)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-28 15:49:40 +02:00
Joakim Sørensen
959134df02 Better secrets support in add-on configuration (#9275) 2021-05-28 14:37:16 +02:00
Bram Kragten
a9f9fc4ce2 Bumped version to 20210528.0 2021-05-28 12:26:38 +02:00
dependabot[bot]
cfb370a3c8 Bump dns-packet from 1.3.1 to 1.3.4 (#9281)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-28 12:25:46 +02:00
Bram Kragten
353435c8d5 Fix icon db loading (#9280) 2021-05-28 11:54:20 +02:00
Bram Kragten
c8c85d096b Show less ticks in charts (#9279) 2021-05-28 10:16:37 +02:00
GitHub Action
19c9c8f227 Translation update 2021-05-28 03:03:41 +00:00
Bram Kragten
6ea2a29eea Hide attribute measurement and editable attributes (#9272) 2021-05-27 11:48:27 +02:00
Joakim Sørensen
59f3f819a6 Revert name change from selectedTheme to selectedThemeSettings (#9273) 2021-05-27 10:21:58 +02:00
GitHub Action
93e8f52880 Translation update 2021-05-27 02:45:51 +00:00
Joakim Sørensen
02810efcc4 Replace Hass_io_ prefix for snapshot downloads (#9270) 2021-05-26 21:56:27 +02:00
Bram Kragten
4b9be7ce16 Fix entity filtering in dev states (#9268) 2021-05-26 17:27:45 +02:00
Bram Kragten
f3ec09e480 Bumped version to 20210526.0 2021-05-26 16:58:31 +02:00
Bram Kragten
8291a84e3e Hide network config when not loaded (#9265) 2021-05-26 07:53:54 -07:00
J. Nick Koston
b0e1f0f73a Add network configuration (#9210)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-26 16:44:15 +02:00
Bram Kragten
a66b966e7d Fix a bunch of updates triggering updated (#9260) 2021-05-26 16:29:50 +02:00
Philip Allgaier
5f56040c64 Add friendly_name to dev tools "Entity" column + fuzzy search (#7582) 2021-05-26 15:29:51 +02:00
Bram Kragten
eaccd22267 Fix chartjs deprecation warnings (#9261) 2021-05-26 15:05:01 +02:00
Bram Kragten
27845a7345 Fix logbook height (#9258) 2021-05-26 12:44:10 +02:00
Bram Kragten
f7ef8180e4 Guard for undefined item in quick bar (#9259) 2021-05-26 12:43:59 +02:00
Bram Kragten
5958eb9a55 Minor dependency bumps (#9249) 2021-05-26 12:04:39 +02:00
Bram Kragten
3ef2912b60 Fix typo in translation key 2021-05-26 11:19:09 +02:00
Joakim Sørensen
fa9c6a765a Replace closing with closed in dialogs (#9257) 2021-05-26 11:10:27 +02:00
Bram Kragten
c4a8899780 Bump idb-keyval (#9248)
https://github.com/jakearchibald/idb-keyval#updating-from-3x
2021-05-26 10:22:38 +02:00
Bram Kragten
3cc4628d03 Bump test dependencies (#9244) 2021-05-26 10:02:02 +02:00
GitHub Action
b6c5223221 Translation update 2021-05-26 02:25:19 +00:00
Philip Allgaier
cbd6d4251c Prevent shrinking of percent value in supervisor metrics (#9033) 2021-05-26 00:24:30 +02:00
Bram Kragten
fdcbb5b432 Bump js-yaml (#9245) 2021-05-26 00:13:58 +02:00
Bram Kragten
de09e31815 Fix resetting theme, only fallback to light when theme doesnt support… (#9253) 2021-05-26 00:11:17 +02:00
Philip Allgaier
f55e911313 Prevent formatting for unknown attribute (#9252) 2021-05-26 00:08:41 +02:00
Bram Kragten
465a91dbf3 Fix circulair progress producing scrollbars (#9247) 2021-05-25 23:59:24 +02:00
Bram Kragten
835a7833ae Bump memoize one (#9243) 2021-05-25 23:53:58 +02:00
Bram Kragten
179717d40c Fix rollup build (#9246) 2021-05-25 23:51:31 +02:00
Philip Allgaier
3d4d789f7f Detect and format date & timestamp attributes (#9074) 2021-05-25 22:39:21 +02:00
Bram Kragten
d97fb19f05 Ingress: Wait for dialog to close before navigating (#9250) 2021-05-25 22:11:52 +02:00
Joakim Sørensen
0dd3757df2 Refresh snapshot create/restore dialogs (#9223)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-25 21:29:32 +02:00
Philip Allgaier
c32a4546f3 Translate NC account connection state (#9167) 2021-05-25 18:12:56 +02:00
Raman Gupta
1bb025ccd0 Add log level changed message when user changes Z-Wave JS log level (#9238)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-25 17:59:00 +02:00
Philip Allgaier
2b8033a97f Prevent cutting off of attributes in more-info light (#9219)
* Prevent cutting off of attributes in more-info light

* Update src/dialogs/more-info/controls/more-info-light.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-25 17:58:20 +02:00
Joakim Sørensen
21a3a8c594 Navigate cleanup (#9202) 2021-05-25 17:46:36 +02:00
Philip Allgaier
1026e90296 Put attributes in more-info into a foldable section (#9220)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-25 17:44:02 +02:00
Bram Kragten
0eca602e61 Use comboBoxRenderer from lit-vaadin-helpers (#9201) 2021-05-25 13:27:49 +02:00
Philip Allgaier
7f75ca81f1 Add support for custom themes to use dark mode (#8347) 2021-05-25 13:26:35 +02:00
Bram Kragten
8af05e2726 Optimise data table and device dashboard (#9217) 2021-05-25 13:12:44 +02:00
Bram Kragten
0a478ee1da Fix tabs styling (#9241) 2021-05-25 12:05:20 +02:00
GitHub Action
a4bdc5a05f Translation update 2021-05-25 02:00:53 +00:00
Philip Allgaier
d425767dae Ensure timer row uses correct state translation keys (#9143)
* Ensure timer row uses correct state translation keys

* Changes from review

* Update src/panels/lovelace/entity-rows/hui-timer-entity-row.ts

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

* Move logic to data/timer

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-24 23:27:00 +02:00
Philip Allgaier
c78382c119 Make it clear that those are the installed add-ons (#9228) 2021-05-24 23:20:22 +02:00
Philip Allgaier
ee15ddfbc3 Show correct number of disabled integrations (#9232) 2021-05-24 22:47:28 +02:00
Philip Allgaier
0af14eb77e Add refresh button to state dev tools (#9231) 2021-05-24 22:37:29 +02:00
GitHub Action
583cc4bc8a Translation update 2021-05-24 02:01:00 +00:00
GitHub Action
2ee92f48e6 Translation update 2021-05-23 02:04:38 +00:00
Franck Nijhof
d05e02ab3e Add the Supervisor as an ignorable discovery source (#9229) 2021-05-22 19:26:27 +02:00
Joakim Sørensen
abb9f8e233 Remove padding when narrow (#9209) 2021-05-22 06:12:51 -07:00
GitHub Action
f873ef9b59 Translation update 2021-05-22 01:52:23 +00:00
Philip Allgaier
1255b56522 Add missing translations to voice command dialog (#9221) 2021-05-21 10:11:31 +02:00
Bram Kragten
fd9bb4d8cc Make chrome work-around work in iframes (#9200) 2021-05-21 09:09:31 +02:00
GitHub Action
9328576b55 Translation update 2021-05-21 01:53:57 +00:00
Philip Allgaier
70a1edd1dd Allow users to select time format for UI rendering (#9042)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-20 16:23:53 +02:00
Brynley McDonald
87e4c209f4 Add icon to Entities card schema (#9208) 2021-05-20 15:29:32 +02:00
Bram Kragten
3d0a5642cc Only apply on Safari 14.0, and not 14.0.1 2021-05-20 14:23:50 +02:00
GitHub Action
e211d812ad Translation update 2021-05-20 01:50:44 +00:00
GitHub Action
0dcf673b87 Translation update 2021-05-19 01:50:13 +00:00
Paulus Schoutsen
cb14e1f20c Bumped version to 20210518.0 2021-05-18 15:12:08 -07:00
Bram Kragten
52087c0e30 Fix _initialize (#9206)
* Fix _initialize

* Update ha-demo.ts
2021-05-18 15:11:24 -07:00
Paulus Schoutsen
1b9286db76 Fix lit warnings (#9204)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-18 15:01:43 -07:00
Bram Kragten
bc92c0b052 Upgrade to Lit 2 (#9199) 2021-05-18 07:37:53 -07:00
Joakim Sørensen
245bb639f2 Fix URL to jinja template docs (#9198) 2021-05-18 10:22:03 +02:00
GitHub Action
8d81ed58c8 Translation update 2021-05-18 01:57:40 +00:00
Paulus Schoutsen
7890ca85a8 Bumped version to 20210517.0 2021-05-17 16:06:19 -07:00
Paulus Schoutsen
07bab7b264 Drop app-route (#9196) 2021-05-17 16:05:58 -07:00
Paulus Schoutsen
5730c14dc1 Make hassio backwards compat (#9195) 2021-05-17 23:59:46 +02:00
Paulus Schoutsen
f8e8b5ad18 Trace fixes (#9192)
* Show sub-conditions

* Better show errors in timeline

* Add rendered nodes

* Fix some rendering issues with sub-conditions in timeline

* Improve condition rendering
2021-05-17 20:19:47 +02:00
Charles Garwood
fd2728c02c Fix Z-Wave JS add node wizard and add interview status (#9145) 2021-05-17 17:15:01 +02:00
Bram Kragten
7e2bf920e1 Correct types for script automation editors (#9184) 2021-05-17 07:57:43 -07:00
Bram Kragten
1f65328f2d Make slider default for number selector (#9190) 2021-05-17 07:55:12 -07:00
Bram Kragten
4f731baa00 Add guard for non color lights (#9186) 2021-05-17 16:24:39 +02:00
Bram Kragten
5abb3dd8c1 Use default behaviour for service target (#8650) 2021-05-17 14:12:16 +02:00
GitHub Action
0a672c55c5 Translation update 2021-05-17 01:54:21 +00:00
GitHub Action
a6b2299c74 Translation update 2021-05-16 01:59:23 +00:00
Paulus Schoutsen
37cc6709d4 If we have a link, make it a link (#9181) 2021-05-15 12:25:28 +02:00
Paulus Schoutsen
f4ffbe67e2 Remove shadowroot from ha-markdown-element (#9187) 2021-05-15 12:23:33 +02:00
GitHub Action
9f32d72a41 Translation update 2021-05-15 01:51:54 +00:00
Bram Kragten
64a117d8ac Fix yarn.lock 2021-05-14 22:32:41 +02:00
Matt Emerick-Law
ebf0bdc840 Add duplicate scene functionality (#9175) 2021-05-14 21:44:03 +02:00
Julien Roy
cc0a120bf6 Switch update and openChangelog button (#9174)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-14 16:55:38 +02:00
Bram Kragten
fe2fe7468f Bump lodash (#9135) 2021-05-14 11:50:50 +02:00
Joakim Sørensen
b12a10ccb5 Add snapshot contents as secondary info for partial snapshots (#9166) 2021-05-14 11:49:52 +02:00
Bram Kragten
2ad2a4b198 Bump lokalize deps + support object format for args (#9155)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-05-13 22:47:47 -07:00
GitHub Action
6a62f05657 Translation update 2021-05-14 01:54:56 +00:00
GitHub Action
4910f60ec4 Translation update 2021-05-13 01:54:52 +00:00
Bram Kragten
d35168e88f Bump chart.js (#9160)
Replaces #9159
2021-05-12 17:09:21 -07:00
GitHub Action
01b3d2aca9 Translation update 2021-05-12 01:47:33 +00:00
dependabot[bot]
29e8d1cff0 Bump hosted-git-info from 2.7.1 to 2.8.9 (#9162)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.7.1 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.7.1...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-11 11:24:05 +02:00
Michael
4e1d10cc08 Improve UX for counter and input_number helpers (#9061) 2021-05-11 10:52:44 +02:00
GitHub Action
3575d94ca1 Translation update 2021-05-11 01:46:41 +00:00
Bram Kragten
d91546b532 Bump home-assistant-js-websocket (#9156) 2021-05-11 00:46:59 +02:00
Bram Kragten
9f554f4917 Silence babel warnings (#9158) 2021-05-10 22:57:42 +02:00
Bram Kragten
d4720a9244 Align state info in center (#9153) 2021-05-10 22:22:54 +02:00
Philip Allgaier
5c466712db Fix missing customElement import after Lit 2.0 bump (#9157) 2021-05-10 22:11:08 +02:00
Joakim Sørensen
6dc7e852ae Use hass-tabs-subpage-data-table for supervisor snapshots (#9103)
* Use hass-tabs-subpage-data-table for supervisor snapshots

* comments

* type

* cleanup

* change translations

* Update hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts

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

* reset

* fix after rebase

* internalProperty -> state

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-10 18:17:16 +02:00
dependabot[bot]
785f614bd9 Bump lodash from 4.17.15 to 4.17.21 (#9154)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 16:12:38 +02:00
uvjustin
0a8e27249d Bump hls.js from v1.0.1 to v1.0.3 (#9147) 2021-05-10 11:06:16 +02:00
GitHub Action
15ee87ee67 Translation update 2021-05-10 01:45:53 +00:00
GitHub Action
12612a16df Translation update 2021-05-09 01:44:02 +00:00
Philip Allgaier
4f449e2600 Adjust token relative_date wording (#9138) 2021-05-08 17:30:31 +02:00
Paulus Schoutsen
7f49f039fd Close new automation dialog before moving to next step (#9071)
* Close new automatin dialog before next step

* Update dialog-thingtalk.ts

* Fix

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-08 13:53:07 +02:00
Bram Kragten
88dc65bc4e Dont require manifest for the overflow menu (#9129) 2021-05-08 13:08:28 +02:00
Paulus Schoutsen
6edebe18ad Use grid for sensor cards on 2nd view of teaching birds demo (#9132) 2021-05-08 13:07:58 +02:00
GitHub Action
38b3a9205d Translation update 2021-05-08 01:42:37 +00:00
Joakim Sørensen
4b796b4929 Add supervisor_ingress support to my (#9087) 2021-05-07 15:08:57 -07:00
Bram Kragten
83cabcac28 Add tsc and eslint to pre-commit (#9131) 2021-05-07 15:07:05 -07:00
Thomas Lovén
d308c5d9b9 Add manual limit selection to graph header/footer (#9126)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-05-07 15:06:11 -07:00
Bram Kragten
9f032a61a9 Add compatibility for Lit 2.0 (#8878) 2021-05-07 13:16:14 -07:00
Bram Kragten
0f58214ba1 Bump leaflet + fix location editor (#9118) 2021-05-07 13:11:30 -07:00
Bram Kragten
c48a60cce6 Bump superstruct (#9119)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2021-05-07 13:10:35 -07:00
Bram Kragten
cd3ffceeff Fix for when the value doesnt get changed by the backend when we send it (#9105) 2021-05-07 14:33:33 +02:00
Bram Kragten
9be4a00169 Update UI when service schema change (#9120) 2021-05-07 11:28:37 +02:00
Bram Kragten
a9c7a39a47 Fix positioning of preload checkbox (#9115) 2021-05-07 11:16:25 +02:00
Philip Allgaier
abcdd60a21 Convert GPS to uppercase in attribute name (#9124) 2021-05-07 11:14:03 +02:00
Philip Allgaier
a94f85a100 Fix alignment of Entities card header toggle (#9123) 2021-05-07 11:13:48 +02:00
GitHub Action
9755bf723f Translation update 2021-05-07 01:42:34 +00:00
Philip Allgaier
a71ebcf47e Bump js-xss to 1.0.9 (#9121) 2021-05-06 19:51:08 +02:00
GitHub Action
72695631cd Translation update 2021-05-06 01:39:12 +00:00
Bram Kragten
2af211b543 Guard for undefined values in attribute filtering (#9089) 2021-05-05 10:32:24 +02:00
Joakim Sørensen
6e5e2625d6 Show supervisor addon configuration error (#8950)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-05 10:26:11 +02:00
Joakim Sørensen
23c1c2f5eb Bump wheels to 3.8-alpine3.12 (#9098) 2021-05-05 08:19:58 +02:00
GitHub Action
da85ee5d01 Translation update 2021-05-05 01:39:25 +00:00
Joakim Sørensen
d408e8653c Return instead of trhow (#9094) 2021-05-04 23:03:57 +02:00
Bram Kragten
cc76ccc3c9 Bumped version to 20210504.0 2021-05-04 23:01:56 +02:00
Bram Kragten
105a00d3e4 Use hs color when outside of wheel (#9088) 2021-05-04 22:51:28 +02:00
Philip Allgaier
2c08cba8cc Draw a <hr> above the attributes in more-info (#9090) 2021-05-04 22:04:27 +02:00
Philip Allgaier
344b11a204 Prevent not needed <hr> in more-info-light (#9080) 2021-05-04 14:18:02 +02:00
GitHub Action
1ff5bf0fd5 Translation update 2021-05-04 01:43:58 +00:00
Bram Kragten
c29cf7f77c Bumped version to 20210503.0 2021-05-03 15:47:44 +02:00
Bram Kragten
193cb46d60 Config flow: Show next when not last step (#9078) 2021-05-03 15:47:03 +02:00
Bram Kragten
9dc864d486 Break word if possible instead if break anywhere (#9076) 2021-05-02 20:42:04 -07:00
Bram Kragten
cee166839a Bump Lit element (#9068) 2021-05-02 20:41:00 -07:00
GitHub Action
1a60a3c728 Translation update 2021-05-03 01:49:21 +00:00
Paulus Schoutsen
5d946778cb Show setting for auto connect seperately if currently connected (#9072)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-05-02 14:45:53 +02:00
Paulus Schoutsen
ac5f85820f Integration Card: Fix Safari header break and deal with long errors (#9070) 2021-05-02 10:54:40 +02:00
GitHub Action
716e100a28 Translation update 2021-05-02 01:53:59 +00:00
Charles Garwood
7b8cb16c12 Add "subscribed to log" message on Z-Wave JS log subscription (#9062) 2021-05-01 14:31:59 +02:00
Philip Allgaier
00d46424a3 Translate new setup time info header + add blank before unit (#9067) 2021-05-01 14:31:43 +02:00
Philip Allgaier
2a5f940744 Ensure weather icon is centered in forecast (#9065) 2021-05-01 14:31:01 +02:00
Paulus Schoutsen
13cc016b36 Fix some hassio things (#9059) 2021-05-01 14:30:33 +02:00
GitHub Action
a8d49c27c8 Translation update 2021-05-01 01:48:36 +00:00
Bram Kragten
a8522e91b5 Bumped version to 20210430.0 2021-04-30 21:17:22 +02:00
Bram Kragten
5754f4463d Bump babel and eslint (#9049) 2021-04-30 12:15:31 -07:00
Bram Kragten
d4118ade0f Bump lit-html (#9053)
Adds support for Lit 2.0 directives
2021-04-30 12:12:20 -07:00
Bram Kragten
6d80f15a98 Bump codemirror (#9052) 2021-04-30 11:34:58 -07:00
Bram Kragten
f8aa472409 Only show warning when google is enabled (#9054) 2021-04-30 11:34:45 -07:00
Bram Kragten
df22fd00ca Check if temperature available in forecast (#9055) 2021-04-30 11:34:31 -07:00
David F. Mulcahey
ce2743a982 Move ZHA config panel section translations to backend (#9018) 2021-04-30 18:59:06 +02:00
Paulus Schoutsen
92b32458ad Fix grid card size when square (#9056) 2021-04-30 18:57:20 +02:00
Joakim Sørensen
d57e8a45d3 Break primary anywhere (#9050) 2021-04-30 15:02:01 +02:00
Joakim Sørensen
551d3ffdf3 Use haStyleScrollbar for integration card lists (#9051) 2021-04-30 15:01:31 +02:00
GitHub Action
7add6eb736 Translation update 2021-04-30 01:45:22 +00:00
rmogstad
a28616d535 Fix browser language detection for region specific languages (#8982) (#9026) 2021-04-30 01:11:03 +02:00
Paulus Schoutsen
a288fd370f Fix gallery element definitions (#9046) 2021-04-30 01:10:29 +02:00
Paulus Schoutsen
acd335e249 Set columns to 4 in demo 2021-04-29 14:06:52 -07:00
969 changed files with 22026 additions and 16749 deletions

View File

@@ -4,8 +4,7 @@
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:wc/recommended", "plugin:wc/recommended",
"plugin:lit/recommended", "plugin:lit/recommended",
"prettier", "prettier"
"prettier/@typescript-eslint"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
@@ -29,9 +28,7 @@
"__BUILD__": false, "__BUILD__": false,
"__VERSION__": false, "__VERSION__": false,
"__STATIC_PATH__": false, "__STATIC_PATH__": false,
"Polymer": true, "Polymer": true
"webkitSpeechRecognition": false,
"ResizeObserver": false
}, },
"env": { "env": {
"browser": true, "browser": true,
@@ -85,8 +82,28 @@
"@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0, "@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [
0,
{
"selector": "default",
"format": ["camelCase", "snake_case"],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
},
{
"selector": ["variable"],
"format": ["camelCase", "snake_case", "UPPER_CASE"],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
}
],
"lit/attribute-value-entities": 0 "lit/attribute-value-entities": 0
}, },
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"], "plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable" "processor": "disable/disable",
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
} }

View File

@@ -37,9 +37,11 @@ jobs:
- name: Build resources - name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
- name: Run eslint - name: Run eslint
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore run: yarn run lint:eslint
- name: Run tsc - name: Run tsc
run: ./node_modules/.bin/tsc run: yarn run lint:types
- name: Run prettier
run: yarn run lint:prettier
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@@ -6,8 +6,7 @@ on:
- published - published
env: env:
WHEELS_TAG: 3.7-alpine3.11 PYTHON_VERSION: 3.8
PYTHON_VERSION: 3.7
NODE_VERSION: 12.1 NODE_VERSION: 12.1
jobs: jobs:
@@ -64,6 +63,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 +75,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 }}

21
.gitignore vendored
View File

@@ -1,10 +1,17 @@
.DS_Store
.reify-cache
# build
build build
build-translations/* build-translations/*
hass_frontend/*
dist
# yarn
.yarn
yarn-error.log
node_modules/* node_modules/*
npm-debug.log npm-debug.log
.DS_Store
hass_frontend/*
.reify-cache
# Python stuff # Python stuff
*.py[cod] *.py[cod]
@@ -14,11 +21,8 @@ hass_frontend/*
# venv stuff # venv stuff
pyvenv.cfg pyvenv.cfg
pip-selfcheck.json pip-selfcheck.json
venv venv/*
.venv .venv
lib
bin
dist
# vscode # vscode
.vscode/* .vscode/*
@@ -31,9 +35,8 @@ src/cast/dev_const.ts
# Secrets # Secrets
.lokalise_token .lokalise_token
yarn-error.log
#asdf # asdf
.tool-versions .tool-versions
# Home Assistant config # Home Assistant config

4
.mocharc.cjs Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
require: "test-mocha/testconf.js",
timeout: 10000,
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const env = require("./env.js"); const env = require("./env.js");
const paths = require("./paths.js"); const paths = require("./paths.js");
@@ -51,15 +52,16 @@ module.exports.terserOptions = (latestBuild) => ({
module.exports.babelOptions = ({ latestBuild }) => ({ module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false, babelrc: false,
compact: false,
presets: [ presets: [
!latestBuild && [ !latestBuild && [
require("@babel/preset-env").default, "@babel/preset-env",
{ {
useBuiltIns: "entry", useBuiltIns: "entry",
corejs: "3.6", corejs: "3.6",
}, },
], ],
require("@babel/preset-typescript").default, "@babel/preset-typescript",
].filter(Boolean), ].filter(Boolean),
plugins: [ plugins: [
// 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})
@@ -72,23 +74,12 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
[ ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
require("@babel/plugin-proposal-decorators").default, ["@babel/plugin-proposal-private-methods", { loose: true }],
{ decoratorsBeforeExport: true }, ["@babel/plugin-proposal-class-properties", { loose: true }],
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
].filter(Boolean), ].filter(Boolean),
}); });
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
];
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5"); path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const paths = require("./paths.js"); const paths = require("./paths.js");

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,11 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }), createConfigFunc({ ...params, latestBuild: false }),
]; ];
const isWsl = fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/** /**
* @param {{ * @param {{
* compiler: import("webpack").Compiler, * compiler: import("webpack").Compiler,
@@ -79,7 +85,7 @@ 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(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
{ ignored: /build-translations/ }, { ignored: /build-translations/, poll: isWsl },
doneHandler() doneHandler()
); );
gulp.watch( gulp.watch(
@@ -137,7 +143,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

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
module.exports = { module.exports = {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const commonjs = require("@rollup/plugin-commonjs"); const commonjs = require("@rollup/plugin-commonjs");
@@ -32,88 +33,77 @@ const createRollupConfig = ({
publicPath, publicPath,
dontHash, dontHash,
isWDS, isWDS,
}) => { }) => ({
return { /**
/** * @type { import("rollup").InputOptions }
* @type { import("rollup").InputOptions } */
*/ inputOptions: {
inputOptions: { input: entry,
input: entry, // Some entry points contain no JavaScript. This setting silences a warning about that.
// Some entry points contain no JavaScript. This setting silences a warning about that. // https://rollupjs.org/guide/en/#preserveentrysignatures
// https://rollupjs.org/guide/en/#preserveentrysignatures preserveEntrySignatures: false,
preserveEntrySignatures: false, plugins: [
plugins: [ ignore({
ignore({ files: bundle.emptyPackages({ latestBuild }),
files: bundle.emptyPackages({ latestBuild }), }),
resolve({
extensions,
preferBuiltins: false,
browser: true,
rootDir: paths.polymer_dir,
}),
commonjs(),
json(),
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
babelHelpers: isWDS ? "inline" : "bundled",
}),
string({
// Import certain extensions as strings
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
}),
replace(bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })),
!isWDS &&
manifest({
publicPath,
}), }),
resolve({ !isWDS && worker(),
extensions, !isWDS && dontHashPlugin({ dontHash }),
preferBuiltins: false, !isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)),
browser: true, !isWDS &&
rootDir: paths.polymer_dir, isStatsBuild &&
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}), }),
commonjs({ ].filter(Boolean),
namedExports: { },
"js-yaml": ["safeDump", "safeLoad"], /**
}, * @type { import("rollup").OutputOptions }
}), */
json(), outputOptions: {
babel({ // https://rollupjs.org/guide/en/#outputdir
...bundle.babelOptions({ latestBuild }), dir: outputPath,
extensions, // https://rollupjs.org/guide/en/#outputformat
exclude: bundle.babelExclude(), format: latestBuild ? "es" : "systemjs",
babelHelpers: isWDS ? "inline" : "bundled", // https://rollupjs.org/guide/en/#outputexternallivebindings
}), externalLiveBindings: false,
string({ // https://rollupjs.org/guide/en/#outputentryfilenames
// Import certain extensions as strings // https://rollupjs.org/guide/en/#outputchunkfilenames
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")], // https://rollupjs.org/guide/en/#outputassetfilenames
}), entryFileNames:
replace( isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
), assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
!isWDS && // https://rollupjs.org/guide/en/#outputsourcemap
manifest({ sourcemap: isProdBuild ? true : "inline",
publicPath, },
}), });
!isWDS && worker(),
!isWDS && dontHashPlugin({ dontHash }),
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)),
!isWDS &&
isStatsBuild &&
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}),
].filter(Boolean),
},
/**
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/guide/en/#outputdir
dir: outputPath,
// https://rollupjs.org/guide/en/#outputformat
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/guide/en/#outputexternallivebindings
externalLiveBindings: false,
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames:
isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames:
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline",
},
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => { const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) =>
return createRollupConfig( createRollupConfig(
bundle.config.app({ bundle.config.app({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
@@ -121,31 +111,24 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => {
isWDS, isWDS,
}) })
); );
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
return createRollupConfig( createRollupConfig(
bundle.config.demo({ bundle.config.demo({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
}) })
); );
};
const createCastConfig = ({ isProdBuild, latestBuild }) => { const createCastConfig = ({ isProdBuild, latestBuild }) =>
return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild })); createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => { const createHassioConfig = ({ isProdBuild, latestBuild }) =>
return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild })); createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => { const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
return createRollupConfig( createRollupConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = { module.exports = {
createAppConfig, createAppConfig,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");

View File

@@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const webpack = require("webpack"); const webpack = require("webpack");
const path = require("path"); const path = require("path");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js"); const paths = require("./paths.js");
const bundle = require("./bundle"); const bundle = require("./bundle.js");
const log = require("fancy-log"); const log = require("fancy-log");
class LogStartCompilePlugin { class LogStartCompilePlugin {
@@ -46,7 +47,6 @@ const createWebpackConfig = ({
rules: [ rules: [
{ {
test: /\.m?js$|\.ts$/, test: /\.m?js$|\.ts$/,
exclude: bundle.babelExclude(),
use: { use: {
loader: "babel-loader", loader: "babel-loader",
options: bundle.babelOptions({ latestBuild }), options: bundle.babelOptions({ latestBuild }),
@@ -94,6 +94,7 @@ const createWebpackConfig = ({
? path.resolve(context, resource) ? path.resolve(context, resource)
: require.resolve(resource); : require.resolve(resource);
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.error( console.error(
"Error in Home Assistant ignore plugin", "Error in Home Assistant ignore plugin",
resource, resource,
@@ -114,8 +115,9 @@ const createWebpackConfig = ({
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one // 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 webpack.NormalModuleReplacementPlugin(
new RegExp( new RegExp(
require.resolve( path.resolve(
"lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js" paths.polymer_dir,
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
) )
), ),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js") path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
@@ -124,6 +126,11 @@ const createWebpackConfig = ({
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
extensions: [".ts", ".js", ".json"], extensions: [".ts", ".js", ".json"],
alias: {
"lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
},
}, },
output: { output: {
filename: ({ chunk }) => { filename: ({ chunk }) => {
@@ -144,33 +151,24 @@ const createWebpackConfig = ({
}; };
}; };
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
return createWebpackConfig( createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild }) bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
); );
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
return createWebpackConfig( createWebpackConfig(
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
); );
};
const createCastConfig = ({ isProdBuild, latestBuild }) => { const createCastConfig = ({ isProdBuild, latestBuild }) =>
return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild })); createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => { const createHassioConfig = ({ isProdBuild, latestBuild }) =>
return createWebpackConfig( createWebpackConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
bundle.config.hassio({ isProdBuild, latestBuild })
);
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => { const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
return createWebpackConfig( createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = { module.exports = {
createAppConfig, createAppConfig,

View File

@@ -1,16 +1,9 @@
import "@material/mwc-button/mwc-button";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket"; import { Auth, Connection } from "home-assistant-js-websocket";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager"; import { CastManager } from "../../../../src/cast/cast_manager";
import { import {
castSendShowLovelaceView, castSendShowLovelaceView,
@@ -32,7 +25,6 @@ import {
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout"; import "./hc-layout";
import "@material/mwc-button/mwc-button";
@customElement("hc-cast") @customElement("hc-cast")
class HcCast extends LitElement { class HcCast extends LitElement {
@@ -42,9 +34,9 @@ class HcCast extends LitElement {
@property() public castManager!: CastManager; @property() public castManager!: CastManager;
@internalProperty() private askWrite = false; @state() private askWrite = false;
@internalProperty() private lovelaceConfig?: LovelaceConfig | null; @state() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult { protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) { if (this.lovelaceConfig === undefined) {
@@ -54,9 +46,7 @@ class HcCast extends LitElement {
const error = const error =
this.castManager.castState === "NO_DEVICES_AVAILABLE" this.castManager.castState === "NO_DEVICES_AVAILABLE"
? html` ? html`
<p> <p>There were no suitable Chromecast devices to cast to found.</p>
There were no suitable Chromecast devices to cast to found.
</p>
` `
: undefined; : undefined;
@@ -206,7 +196,7 @@ class HcCast extends LitElement {
} }
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
.center-item { .center-item {
display: flex; display: flex;

View File

@@ -11,15 +11,8 @@ import {
getAuth, getAuth,
getAuthOptions, getAuthOptions,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, state } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
TemplateResult,
internalProperty,
} from "lit-element";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager"; import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
import { import {
@@ -60,19 +53,19 @@ const INTRO = html`
@customElement("hc-connect") @customElement("hc-connect")
export class HcConnect extends LitElement { export class HcConnect extends LitElement {
@internalProperty() private loading = false; @state() private loading = false;
// If we had stored credentials but we cannot connect, // If we had stored credentials but we cannot connect,
// show a screen asking retry or logout. // show a screen asking retry or logout.
@internalProperty() private cannotConnect = false; @state() private cannotConnect = false;
@internalProperty() private error?: string | TemplateResult; @state() private error?: string | TemplateResult;
@internalProperty() private auth?: Auth; @state() private auth?: Auth;
@internalProperty() private connection?: Connection; @state() private connection?: Connection;
@internalProperty() private castManager?: CastManager | null; @state() private castManager?: CastManager | null;
private openDemo = false; private openDemo = false;
@@ -86,9 +79,7 @@ export class HcConnect extends LitElement {
</div> </div>
<div class="card-actions"> <div class="card-actions">
<a href="/"> <a href="/">
<mwc-button> <mwc-button> Retry </mwc-button>
Retry
</mwc-button>
</a> </a>
<div class="spacer"></div> <div class="spacer"></div>
<mwc-button @click=${this._handleLogout}>Log out</mwc-button> <mwc-button @click=${this._handleLogout}>Log out</mwc-button>
@@ -299,7 +290,7 @@ export class HcConnect extends LitElement {
} }
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
.card-content a { .card-content a {
color: var(--primary-color); color: var(--primary-color);

View File

@@ -4,15 +4,8 @@ import {
getUser, getUser,
HassUser, HassUser,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@customElement("hc-layout") @customElement("hc-layout")
@@ -69,7 +62,7 @@ class HcLayout extends LitElement {
} }
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
display: flex; display: flex;

View File

@@ -1,10 +1,5 @@
import { import { html, TemplateResult } from "lit";
customElement, import { customElement, property, state } from "lit/decorators";
html,
internalProperty,
property,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history"; import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { import {
@@ -21,7 +16,7 @@ import "./hc-lovelace";
class HcDemo extends HassElement { class HcDemo extends HassElement {
@property({ attribute: false }) public lovelacePath!: string; @property({ attribute: false }) public lovelacePath!: string;
@internalProperty() private _lovelaceConfig?: LovelaceConfig; @state() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._lovelaceConfig) { if (!this._lovelaceConfig) {
@@ -38,10 +33,10 @@ class HcDemo extends HassElement {
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._initialize(); this._initializeHass();
} }
private async _initialize() { private async _initializeHass() {
const initial: Partial<MockHomeAssistant> = { const initial: Partial<MockHomeAssistant> = {
// Override updateHass so that the correct hass lifecycle methods are called // Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate: Partial<HomeAssistant>) => updateHass: (hassUpdate: Partial<HomeAssistant>) =>

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@customElement("hc-launch-screen") @customElement("hc-launch-screen")
@@ -29,7 +22,7 @@ class HcLaunchScreen extends LitElement {
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
display: block; display: block;

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types"; import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view"; import "../../../../src/panels/lovelace/views/hui-view";
@@ -91,7 +84,7 @@ class HcLovelace extends LitElement {
return undefined; return undefined;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
min-height: 100vh; min-height: 100vh;

View File

@@ -3,12 +3,8 @@ import {
getAuth, getAuth,
UnsubscribeFunc, UnsubscribeFunc,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { import { html, TemplateResult } from "lit";
customElement, import { customElement, state } from "lit/decorators";
html,
internalProperty,
TemplateResult,
} from "lit-element";
import { CAST_NS } from "../../../../src/cast/const"; import { CAST_NS } from "../../../../src/cast/const";
import { import {
ConnectMessage, ConnectMessage,
@@ -36,13 +32,13 @@ let resourcesLoaded = false;
@customElement("hc-main") @customElement("hc-main")
export class HcMain extends HassElement { export class HcMain extends HassElement {
@internalProperty() private _showDemo = false; @state() private _showDemo = false;
@internalProperty() private _lovelaceConfig?: LovelaceConfig; @state() private _lovelaceConfig?: LovelaceConfig;
@internalProperty() private _lovelacePath: string | number | null = null; @state() private _lovelacePath: string | number | null = null;
@internalProperty() private _error?: string; @state() private _error?: string;
private _unsubLovelace?: UnsubscribeFunc; private _unsubLovelace?: UnsubscribeFunc;

View File

@@ -440,57 +440,43 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
type: "horizontal-stack", type: "horizontal-stack",
}, },
{ {
type: "grid",
columns: 2,
cards: [ cards: [
{ {
cards: [ graph: "line",
{ type: "sensor",
graph: "line", entity: "sensor.temperature_bedroom",
type: "sensor",
entity: "sensor.temperature_bedroom",
},
{
graph: "line",
type: "sensor",
name: "S's room",
entity: "sensor.temperature_stefan",
},
],
type: "horizontal-stack",
}, },
{ {
cards: [ graph: "line",
{ type: "sensor",
graph: "line", name: "S's room",
type: "sensor", entity: "sensor.temperature_stefan",
entity: "sensor.temperature_passage",
},
{
graph: "line",
type: "sensor",
name: "Bathroom",
entity: "sensor.temperature_downstairs_bathroom",
},
],
type: "horizontal-stack",
}, },
{ {
cards: [ graph: "line",
{ type: "sensor",
graph: "line", entity: "sensor.temperature_passage",
type: "sensor", },
entity: "sensor.temperature_storage", {
}, graph: "line",
{ type: "sensor",
graph: "line", name: "Bathroom",
type: "sensor", entity: "sensor.temperature_downstairs_bathroom",
name: "Refrigerator", },
entity: "sensor.refrigerator", {
}, graph: "line",
], type: "sensor",
type: "horizontal-stack", entity: "sensor.temperature_storage",
},
{
graph: "line",
type: "sensor",
name: "Refrigerator",
entity: "sensor.refrigerator",
}, },
], ],
type: "vertical-stack",
}, },
{ {
entities: [ entities: [

View File

@@ -1,5 +1,5 @@
/* eslint-disable */ /* eslint-disable */
import { LitElement } from "lit-element"; import { LitElement } from "lit";
import "./card-tools"; import "./card-tools";
class CardModder extends LitElement { class CardModder extends LitElement {

View File

@@ -1,5 +1,5 @@
/* eslint-disable */ /* eslint-disable */
import { html, LitElement } from "lit-element"; import { html, LitElement } from "lit";
if (!window.cardTools) { if (!window.cardTools) {
const version = 0.2; const version = 0.2;

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager"; import { CastManager } from "../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../src/cast/receiver_messages";
import "../../../src/components/ha-icon"; import "../../../src/components/ha-icon";
@@ -20,7 +13,7 @@ import { HomeAssistant } from "../../../src/types";
class CastDemoRow extends LitElement implements LovelaceRow { class CastDemoRow extends LitElement implements LovelaceRow {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@internalProperty() private _castManager?: CastManager | null; @state() private _castManager?: CastManager | null;
public setConfig(_config: CastConfig): void { public setConfig(_config: CastConfig): void {
// No config possible. // No config possible.
@@ -73,7 +66,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
this.style.display = this._castManager ? "" : "none"; this.style.display = this._castManager ? "" : "none";
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
display: flex; display: flex;

View File

@@ -1,14 +1,7 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { property, state } from "lit/decorators";
CSSResult, import { until } from "lit/directives/until";
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace"; import { LovelaceCardConfig } from "../../../src/data/lovelace";
@@ -26,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant; @property({ attribute: false }) public hass!: MockHomeAssistant;
@internalProperty() private _switching?: boolean; @state() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card; private _hidden = localStorage.hide_demo_card;
@@ -113,7 +106,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
} }
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
css` css`
a { a {

View File

@@ -22,9 +22,9 @@ import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initialize() { protected async _initializeHass() {
const initial: Partial<MockHomeAssistant> = { const initial: Partial<MockHomeAssistant> = {
panelUrl: (this as any).panelUrl, panelUrl: (this as any)._panelUrl,
// Override updateHass so that the correct hass lifecycle methods are called // Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate: Partial<HomeAssistant>) => updateHass: (hassUpdate: Partial<HomeAssistant>) =>
this._updateHass(hassUpdate), this._updateHass(hassUpdate),
@@ -70,7 +70,7 @@ class HaDemo extends HomeAssistantAppEl {
} }
e.preventDefault(); e.preventDefault();
navigate(this, href); navigate(href);
}, },
{ capture: true } { capture: true }
); );

View File

@@ -3,8 +3,6 @@ import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockTranslations = (hass: MockHomeAssistant) => { export const mockTranslations = (hass: MockHomeAssistant) => {
hass.mockWS( hass.mockWS(
"frontend/get_translations", "frontend/get_translations",
(/* msg: {language: string, category: string} */) => { (/* msg: {language: string, category: string} */) => ({ resources: {} })
return { resources: {} };
}
); );
}; };

View File

@@ -1,7 +1,7 @@
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { safeLoad } from "js-yaml"; import { load } from "js-yaml";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element"; import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
class DemoCard extends PolymerElement { class DemoCard extends PolymerElement {
@@ -80,7 +80,7 @@ class DemoCard extends PolymerElement {
card.removeChild(card.lastChild); card.removeChild(card.lastChild);
} }
const el = this._createCardElement(safeLoad(config.config)[0]); const el = this._createCardElement(load(config.config)[0]);
card.appendChild(el); card.appendChild(el);
this._getSize(el); this._getSize(el);
} }

View File

@@ -1,12 +1,6 @@
import { safeDump } from "js-yaml"; import { dump } from "js-yaml";
import { import { html, css, LitElement, TemplateResult } from "lit";
customElement, import { customElement, property } from "lit/decorators";
html,
css,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { describeAction } from "../../../src/data/script_i18n"; import { describeAction } from "../../../src/data/script_i18n";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -62,7 +56,7 @@ export class DemoAutomationDescribeAction extends LitElement {
(conf) => html` (conf) => html`
<div class="action"> <div class="action">
<span>${describeAction(this.hass, conf as any)}</span> <span>${describeAction(this.hass, conf as any)}</span>
<pre>${safeDump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,11 +1,6 @@
import { safeDump } from "js-yaml"; import { dump } from "js-yaml";
import { import { html, css, LitElement, TemplateResult } from "lit";
customElement, import { customElement } from "lit/decorators";
html,
css,
LitElement,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { describeCondition } from "../../../src/data/automation_i18n"; import { describeCondition } from "../../../src/data/automation_i18n";
@@ -31,7 +26,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
(conf) => html` (conf) => html`
<div class="condition"> <div class="condition">
<span>${describeCondition(conf as any)}</span> <span>${describeCondition(conf as any)}</span>
<pre>${safeDump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,11 +1,6 @@
import { safeDump } from "js-yaml"; import { dump } from "js-yaml";
import { import { html, css, LitElement, TemplateResult } from "lit";
customElement, import { customElement } from "lit/decorators";
html,
css,
LitElement,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { describeTrigger } from "../../../src/data/automation_i18n"; import { describeTrigger } from "../../../src/data/automation_i18n";
@@ -34,7 +29,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
(conf) => html` (conf) => html`
<div class="trigger"> <div class="trigger">
<span>${describeTrigger(conf as any)}</span> <span>${describeTrigger(conf as any)}</span>
<pre>${safeDump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,11 +1,5 @@
import { import { html, css, LitElement, TemplateResult } from "lit";
customElement, import { customElement, property } from "lit/decorators";
html,
css,
LitElement,
TemplateResult,
property,
} from "lit-element";
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";

View File

@@ -1,12 +1,4 @@
import { import { html, css, LitElement, TemplateResult } from "lit";
customElement,
html,
css,
LitElement,
TemplateResult,
internalProperty,
property,
} from "lit-element";
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";
@@ -15,6 +7,7 @@ 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];
@@ -22,7 +15,7 @@ const traces: DemoTrace[] = [basicTrace, motionLightTrace];
export class DemoAutomationTrace extends LitElement { export class DemoAutomationTrace extends LitElement {
@property({ attribute: false }) hass?: HomeAssistant; @property({ attribute: false }) hass?: HomeAssistant;
@internalProperty() private _selected = {}; @state() private _selected = {};
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -93,4 +87,8 @@ class DemoAlarmPanelEntity extends LitElement {
} }
} }
customElements.define("demo-hui-alarm-panel-card", DemoAlarmPanelEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-alarm-panel-card": DemoAlarmPanelEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -75,4 +69,8 @@ class DemoConditional extends LitElement {
} }
} }
customElements.define("demo-hui-conditional-card", DemoConditional); declare global {
interface HTMLElementTagNameMap {
"demo-hui-conditional-card": DemoConditional;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -239,4 +233,8 @@ class DemoEntities extends LitElement {
} }
} }
customElements.define("demo-hui-entities-card", DemoEntities); declare global {
interface HTMLElementTagNameMap {
"demo-hui-entities-card": DemoEntities;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -91,4 +85,8 @@ class DemoButtonEntity extends LitElement {
} }
} }
customElements.define("demo-hui-entity-button-card", DemoButtonEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-entity-button-card": DemoButtonEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -132,4 +126,8 @@ class DemoEntityFilter extends LitElement {
} }
} }
customElements.define("demo-hui-entity-filter-card", DemoEntityFilter); declare global {
interface HTMLElementTagNameMap {
"demo-hui-entity-filter-card": DemoEntityFilter;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -129,4 +123,8 @@ class DemoGaugeEntity extends LitElement {
} }
} }
customElements.define("demo-hui-gauge-card", DemoGaugeEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-gauge-card": DemoGaugeEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -186,7 +180,7 @@ const CONFIGS = [
name: name:
- light.kitchen_lights - light.kitchen_lights
- entity: lock.kitchen_door - entity: lock.kitchen_door
name: name:
- light.ceiling_lights - light.ceiling_lights
`, `,
}, },
@@ -194,7 +188,7 @@ const CONFIGS = [
heading: "Custom tap action", heading: "Custom tap action",
config: ` config: `
- type: glance - type: glance
columns: 4 columns: 4
entities: entities:
- entity: lock.kitchen_door - entity: lock.kitchen_door
name: Custom name: Custom
@@ -232,4 +226,8 @@ class DemoGlanceEntity extends LitElement {
} }
} }
customElements.define("demo-hui-glance-card", DemoGlanceEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-glance-card": DemoGlanceEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../demo/src/stubs/history"; import { mockHistory } from "../../../demo/src/stubs/history";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -108,6 +102,7 @@ const CONFIGS = [
heading: "Columns 4", heading: "Columns 4",
config: ` config: `
- type: grid - type: grid
columns: 4
cards: cards:
- type: entity - type: entity
entity: light.kitchen_lights entity: light.kitchen_lights
@@ -142,6 +137,15 @@ const CONFIGS = [
entity: light.kitchen_lights entity: light.kitchen_lights
`, `,
}, },
{
heading: "Size for single card",
config: `
- type: grid
cards:
- type: entity
entity: light.kitchen_lights
`,
},
{ {
heading: "Vertical Stack", heading: "Vertical Stack",

View File

@@ -1,4 +1,5 @@
import { customElement, html, LitElement, TemplateResult } from "lit-element"; import { html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../components/demo-cards"; import "../components/demo-cards";
const CONFIGS = [ const CONFIGS = [
@@ -42,4 +43,8 @@ class DemoIframe extends LitElement {
} }
} }
customElements.define("demo-hui-iframe-card", DemoIframe); declare global {
interface HTMLElementTagNameMap {
"demo-hui-iframe-card": DemoIframe;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -85,4 +79,8 @@ class DemoLightEntity extends LitElement {
} }
} }
customElements.define("demo-hui-light-card", DemoLightEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-light-card": DemoLightEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -183,4 +177,8 @@ class DemoMap extends LitElement {
} }
} }
customElements.define("demo-hui-map-card", DemoMap); declare global {
interface HTMLElementTagNameMap {
"demo-hui-map-card": DemoMap;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { mockTemplate } from "../../../demo/src/stubs/template"; import { mockTemplate } from "../../../demo/src/stubs/template";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -276,4 +270,8 @@ class DemoMarkdown extends LitElement {
} }
} }
customElements.define("demo-hui-markdown-card", DemoMarkdown); declare global {
interface HTMLElementTagNameMap {
"demo-hui-markdown-card": DemoMarkdown;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players"; import { createMediaPlayerEntities } from "../data/media_players";
@@ -180,4 +174,8 @@ class DemoHuiMediaControlCard extends LitElement {
} }
} }
customElements.define("demo-hui-media-control-card", DemoHuiMediaControlCard); declare global {
interface HTMLElementTagNameMap {
"demo-hui-media-control-card": DemoHuiMediaControlCard;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players"; import { createMediaPlayerEntities } from "../data/media_players";
@@ -77,4 +71,8 @@ class DemoHuiMediaPlayerRow extends LitElement {
} }
} }
customElements.define("demo-hui-media-player-row", DemoHuiMediaPlayerRow); declare global {
interface HTMLElementTagNameMap {
"demo-hui-media-player-row": DemoHuiMediaPlayerRow;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -147,4 +141,8 @@ class DemoPictureElements extends LitElement {
} }
} }
customElements.define("demo-hui-picture-elements-card", DemoPictureElements); declare global {
interface HTMLElementTagNameMap {
"demo-hui-picture-elements-card": DemoPictureElements;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -102,4 +96,8 @@ class DemoPictureEntity extends LitElement {
} }
} }
customElements.define("demo-hui-picture-entity-card", DemoPictureEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-picture-entity-card": DemoPictureEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -143,4 +137,8 @@ class DemoPictureGlance extends LitElement {
} }
} }
customElements.define("demo-hui-picture-glance-card", DemoPictureGlance); declare global {
interface HTMLElementTagNameMap {
"demo-hui-picture-glance-card": DemoPictureGlance;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createPlantEntities } from "../data/plants"; import { createPlantEntities } from "../data/plants";
@@ -52,4 +46,8 @@ export class DemoPlantEntity extends LitElement {
} }
} }
customElements.define("demo-hui-plant-card", DemoPlantEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-plant-card": DemoPlantEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -48,4 +42,8 @@ class DemoShoppingListEntity extends LitElement {
} }
} }
customElements.define("demo-hui-shopping-list-card", DemoShoppingListEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-shopping-list-card": DemoShoppingListEntity;
}
}

View File

@@ -1,11 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, query } from "lit/decorators";
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
@@ -96,4 +90,8 @@ class DemoThermostatEntity extends LitElement {
} }
} }
customElements.define("demo-hui-thermostat-card", DemoThermostatEntity); declare global {
interface HTMLElementTagNameMap {
"demo-hui-thermostat-card": DemoThermostatEntity;
}
}

View File

@@ -1,12 +1,4 @@
import { import { html, css, LitElement, TemplateResult } from "lit";
customElement,
html,
css,
internalProperty,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-formfield"; import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
@@ -23,7 +15,8 @@ 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-html/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = ( const createConfigEntry = (
title: string, title: string,
@@ -35,10 +28,11 @@ const createConfigEntry = (
title, title,
source: "zeroconf", source: "zeroconf",
state: "loaded", state: "loaded",
connection_class: "local_push",
supports_options: false, supports_options: false,
supports_unload: true, supports_unload: true,
disabled_by: null, disabled_by: null,
pref_disable_new_entities: false,
pref_disable_polling: false,
reason: null, reason: null,
...override, ...override,
}); });
@@ -61,6 +55,9 @@ const nameAsDomainEntry = createConfigEntry("ESPHome");
const longNameEntry = createConfigEntry( const longNameEntry = createConfigEntry(
"Entry with a super long name that is going to the next line" "Entry with a super long name that is going to the next line"
); );
const longNonBreakingNameEntry = createConfigEntry(
"EntryWithASuperLongNameThatDoesNotBreak"
);
const configPanelEntry = createConfigEntry("Config Panel", { const configPanelEntry = createConfigEntry("Config Panel", {
domain: "mqtt", domain: "mqtt",
localized_domain_name: "MQTT", localized_domain_name: "MQTT",
@@ -68,6 +65,9 @@ const configPanelEntry = createConfigEntry("Config Panel", {
const optionsFlowEntry = createConfigEntry("Options Flow", { const optionsFlowEntry = createConfigEntry("Options Flow", {
supports_options: true, supports_options: true,
}); });
const disabledPollingEntry = createConfigEntry("Disabled Polling", {
pref_disable_polling: true,
});
const setupErrorEntry = createConfigEntry("Setup Error", { const setupErrorEntry = createConfigEntry("Setup Error", {
state: "setup_error", state: "setup_error",
}); });
@@ -83,7 +83,8 @@ const setupRetryReasonEntry = createConfigEntry("Setup Retry", {
}); });
const setupRetryReasonMissingKeyEntry = createConfigEntry("Setup Retry", { const setupRetryReasonMissingKeyEntry = createConfigEntry("Setup Retry", {
state: "setup_retry", state: "setup_retry",
reason: "resolve_error", reason:
"HTTPSConnectionpool: Max retries exceeded with NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x9eedfc10>: Failed to establish a new connection: [Errno 113] Host is unreachable')",
}); });
const failedUnloadEntry = createConfigEntry("Failed Unload", { const failedUnloadEntry = createConfigEntry("Failed Unload", {
state: "failed_unload", state: "failed_unload",
@@ -139,8 +140,10 @@ const configEntries: Array<{
{ items: [loadedEntry] }, { items: [loadedEntry] },
{ items: [configPanelEntry] }, { items: [configPanelEntry] },
{ items: [optionsFlowEntry] }, { items: [optionsFlowEntry] },
{ items: [disabledPollingEntry] },
{ items: [nameAsDomainEntry] }, { items: [nameAsDomainEntry] },
{ items: [longNameEntry] }, { items: [longNameEntry] },
{ items: [longNonBreakingNameEntry] },
{ items: [setupErrorEntry] }, { items: [setupErrorEntry] },
{ items: [migrationErrorEntry] }, { items: [migrationErrorEntry] },
{ items: [setupRetryEntry] }, { items: [setupRetryEntry] },
@@ -154,6 +157,7 @@ const configEntries: Array<{
setupErrorEntry, setupErrorEntry,
migrationErrorEntry, migrationErrorEntry,
longNameEntry, longNameEntry,
longNonBreakingNameEntry,
setupRetryEntry, setupRetryEntry,
failedUnloadEntry, failedUnloadEntry,
notLoadedEntry, notLoadedEntry,
@@ -214,9 +218,9 @@ const createDeviceRegistryEntries = (
export class DemoIntegrationCard extends LitElement { export class DemoIntegrationCard extends LitElement {
@property({ attribute: false }) hass?: HomeAssistant; @property({ attribute: false }) hass?: HomeAssistant;
@internalProperty() isCustomIntegration = false; @state() isCustomIntegration = false;
@internalProperty() isCloud = false; @state() isCloud = false;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {

View File

@@ -1,12 +1,5 @@
import { import { html, LitElement, PropertyValues, TemplateResult } from "lit";
customElement, import { customElement, property, query } from "lit/decorators";
html,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { import {
LightColorModes, LightColorModes,

View File

@@ -1,5 +1,6 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { customElement, html, LitElement, TemplateResult } from "lit-element"; import { html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../src/data/lovelace"; import { ActionHandlerEvent } from "../../../src/data/lovelace";
import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive"; import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive";

View File

@@ -1,12 +1,6 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { property } from "lit/decorators";
CSSResultArray,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
@@ -47,9 +41,7 @@ class HassioAddonRepositoryEl extends LitElement {
const repo = this.repo; const repo = this.repo;
let _addons = this.addons; let _addons = this.addons;
if (!this.hass.userData?.showAdvanced) { if (!this.hass.userData?.showAdvanced) {
_addons = _addons.filter((addon) => { _addons = _addons.filter((addon) => !addon.advanced);
return !addon.advanced;
});
} }
const addons = this._getAddons(_addons, this.filter); const addons = this._getAddons(_addons, this.filter);
@@ -68,9 +60,7 @@ class HassioAddonRepositoryEl extends LitElement {
} }
return html` return html`
<div class="content"> <div class="content">
<h1> <h1>${repo.name}</h1>
${repo.name}
</h1>
<div class="card-group"> <div class="card-group">
${addons.map( ${addons.map(
(addon) => html` (addon) => html`
@@ -130,10 +120,10 @@ class HassioAddonRepositoryEl extends LitElement {
} }
private _addonTapped(ev) { private _addonTapped(ev) {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`); navigate(`/hassio/addon/${ev.currentTarget.addon.slug}`);
} }
static get styles(): CSSResultArray { static get styles(): CSSResultGroup {
return [ return [
hassioStyle, hassioStyle,
css` css`

View File

@@ -4,13 +4,13 @@ import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResultGroup,
internalProperty, html,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
} from "lit-element"; TemplateResult,
import { html, TemplateResult } from "lit-html"; } from "lit";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
@@ -58,7 +58,7 @@ class HassioAddonStore extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@internalProperty() private _filter?: string; @state() private _filter?: string;
public async refreshData() { public async refreshData() {
await reloadHassioAddons(this.hass); await reloadHassioAddons(this.hass);
@@ -86,9 +86,7 @@ class HassioAddonStore extends LitElement {
main-page main-page
supervisor supervisor
> >
<span slot="header"> <span slot="header"> ${this.supervisor.localize("panel.store")} </span>
${this.supervisor.localize("panel.store")}
</span>
<ha-button-menu <ha-button-menu
corner="BOTTOM_START" corner="BOTTOM_START"
slot="toolbar-icon" slot="toolbar-icon"
@@ -140,7 +138,7 @@ class HassioAddonStore extends LitElement {
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
const repositoryUrl = extractSearchParam("repository_url"); const repositoryUrl = extractSearchParam("repository_url");
navigate(this, "/hassio/store", true); navigate("/hassio/store", { replace: true });
if (repositoryUrl) { if (repositoryUrl) {
this._manageRepositories(repositoryUrl); this._manageRepositories(repositoryUrl);
} }
@@ -154,8 +152,8 @@ class HassioAddonStore extends LitElement {
repositories: HassioAddonRepository[], repositories: HassioAddonRepository[],
addons: HassioAddonInfo[], addons: HassioAddonInfo[],
filter?: string filter?: string
) => { ) =>
return repositories.sort(sortRepos).map((repo) => { repositories.sort(sortRepos).map((repo) => {
const filteredAddons = addons.filter( const filteredAddons = addons.filter(
(addon) => addon.repository === repo.slug (addon) => addon.repository === repo.slug
); );
@@ -171,8 +169,7 @@ class HassioAddonStore extends LitElement {
></hassio-addon-repository> ></hassio-addon-repository>
` `
: html``; : html``;
}); })
}
); );
private _handleAction(ev: CustomEvent<ActionDetail>) { private _handleAction(ev: CustomEvent<ActionDetail>) {
@@ -221,7 +218,7 @@ class HassioAddonStore extends LitElement {
this._filter = e.detail.value; this._filter = e.detail.value;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
hassio-addon-repository { hassio-addon-repository {
margin-top: 24px; margin-top: 24px;

View File

@@ -4,15 +4,13 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
css, css,
CSSResult, CSSResultGroup,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit";
import { customElement, property, state } from "lit/decorators";
import "web-animations-js/web-animations-next-lite.min"; import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@@ -39,15 +37,15 @@ class HassioAddonAudio extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @state() private _error?: string;
@internalProperty() private _inputDevices?: HassioHardwareAudioDevice[]; @state() private _inputDevices?: HassioHardwareAudioDevice[];
@internalProperty() private _outputDevices?: HassioHardwareAudioDevice[]; @state() private _outputDevices?: HassioHardwareAudioDevice[];
@internalProperty() private _selectedInput!: null | string; @state() private _selectedInput!: null | string;
@internalProperty() private _selectedOutput!: null | string; @state() private _selectedOutput!: null | string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
@@ -69,13 +67,13 @@ class HassioAddonAudio extends LitElement {
.selected=${this._selectedInput!} .selected=${this._selectedInput!}
> >
${this._inputDevices && ${this._inputDevices &&
this._inputDevices.map((item) => { this._inputDevices.map(
return html` (item) => html`
<paper-item device=${item.device || ""}> <paper-item device=${item.device || ""}>
${item.name} ${item.name}
</paper-item> </paper-item>
`; `
})} )}
</paper-listbox> </paper-listbox>
</paper-dropdown-menu> </paper-dropdown-menu>
<paper-dropdown-menu <paper-dropdown-menu
@@ -90,13 +88,13 @@ class HassioAddonAudio extends LitElement {
.selected=${this._selectedOutput!} .selected=${this._selectedOutput!}
> >
${this._outputDevices && ${this._outputDevices &&
this._outputDevices.map((item) => { this._outputDevices.map(
return html` (item) => html`
<paper-item device=${item.device || ""} <paper-item device=${item.device || ""}
>${item.name}</paper-item >${item.name}</paper-item
> >
`; `
})} )}
</paper-listbox> </paper-listbox>
</paper-dropdown-menu> </paper-dropdown-menu>
</div> </div>
@@ -109,7 +107,7 @@ class HassioAddonAudio extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -70,7 +63,7 @@ class HassioAddonConfigDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -3,18 +3,16 @@ 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 "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml";
import { import {
css, css,
CSSResult, CSSResultGroup,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
@@ -30,6 +28,7 @@ import {
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
setHassioAddonOption, setHassioAddonOption,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -41,6 +40,13 @@ import { hassioStyle } from "../../resources/hassio-style";
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"]; const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
new Type("!secret", {
kind: "scalar",
construct: (data) => `!secret ${data}`,
}),
]);
@customElement("hassio-addon-config") @customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement { class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@@ -53,31 +59,27 @@ class HassioAddonConfig extends LitElement {
@property({ type: Boolean }) private _valid = true; @property({ type: Boolean }) private _valid = true;
@internalProperty() private _canShowSchema = false; @state() private _canShowSchema = false;
@internalProperty() private _showOptional = false; @state() private _showOptional = false;
@internalProperty() private _error?: string; @state() private _error?: string;
@internalProperty() private _options?: Record<string, unknown>; @state() private _options?: Record<string, unknown>;
@internalProperty() private _yamlMode = false; @state() private _yamlMode = false;
@query("ha-yaml-editor") private _editor?: HaYamlEditor; @query("ha-yaml-editor") private _editor?: HaYamlEditor;
public computeLabel = (entry: HaFormSchema): string => { public computeLabel = (entry: HaFormSchema): string =>
return ( this.addon.translations[this.hass.language]?.configuration?.[entry.name]
this.addon.translations[this.hass.language]?.configuration?.[entry.name] ?.name ||
?.name || this.addon.translations.en?.configuration?.[entry.name].name ||
this.addon.translations.en?.configuration?.[entry.name].name || entry.name;
entry.name
);
};
private _filteredShchema = memoizeOne( private _filteredShchema = memoizeOne(
(options: Record<string, unknown>, schema: HaFormSchema[]) => { (options: Record<string, unknown>, schema: HaFormSchema[]) =>
return schema.filter((entry) => entry.name in options || entry.required); schema.filter((entry) => entry.name in options || entry.required)
}
); );
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -132,6 +134,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}
></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 ||
@@ -266,36 +269,45 @@ 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 eventdata = {
success: true,
response: undefined,
path: "options",
};
button.progress = true; button.progress = true;
this._error = undefined; this._error = undefined;
try { try {
const validation = await validateHassioAddonOption(
this.hass,
this.addon.slug,
this._editor?.value
);
if (!validation.valid) {
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._yamlMode ? this._editor?.value : this._options,
}); });
this._configHasChanged = false; this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
} }
} catch (err) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.configuration.options.failed_to_save", "addon.failed_to_save",
"error", "error",
extractApiErrorMessage(err) extractApiErrorMessage(err)
); );
eventdata.success = false;
} }
button.progress = false; button.progress = false;
fireEvent(this, "hass-api-called", eventdata);
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,15 +1,13 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultGroup,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@@ -43,9 +41,9 @@ class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @state() private _error?: string;
@internalProperty() private _config?: NetworkItem[]; @state() private _config?: NetworkItem[];
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
@@ -79,12 +77,10 @@ class HassioAddonNetwork extends LitElement {
"addon.configuration.network.host" "addon.configuration.network.host"
)} )}
</th> </th>
<th> <th>${this.supervisor.localize("common.description")}</th>
${this.supervisor.localize("common.description")}
</th>
</tr> </tr>
${this._config!.map((item) => { ${this._config!.map(
return html` (item) => html`
<tr> <tr>
<td>${item.container}</td> <td>${item.container}</td>
<td> <td>
@@ -100,8 +96,8 @@ class HassioAddonNetwork extends LitElement {
</td> </td>
<td>${this._computeDescription(item)}</td> <td>${this._computeDescription(item)}</td>
</tr> </tr>
`; `
})} )}
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -124,25 +120,20 @@ class HassioAddonNetwork extends LitElement {
} }
} }
private _computeDescription = (item: NetworkItem): string => { private _computeDescription = (item: NetworkItem): string =>
return ( this.addon.translations[this.hass.language]?.network?.[item.container]
this.addon.translations[this.hass.language]?.network?.[item.container] ?.description ||
?.description || this.addon.translations.en?.network?.[item.container]?.description ||
this.addon.translations.en?.network?.[item.container]?.description || item.description;
item.description
);
};
private _setNetworkConfig(): void { private _setNetworkConfig(): void {
const network = this.addon.network || {}; const network = this.addon.network || {};
const description = this.addon.network_description || {}; const description = this.addon.network_description || {};
const items: NetworkItem[] = Object.keys(network).map((key) => { const items: NetworkItem[] = Object.keys(network).map((key) => ({
return { container: key,
container: key, host: network[key],
host: network[key], description: description[key],
description: description[key], }));
};
});
this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1)); this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1));
} }
@@ -223,7 +214,7 @@ class HassioAddonNetwork extends LitElement {
button.progress = false; button.progress = false;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,14 +1,5 @@
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { import {
@@ -21,6 +12,7 @@ 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 {
@@ -30,9 +22,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
@property({ attribute: false }) public addon?: HassioAddonDetails; @property({ attribute: false }) public addon?: HassioAddonDetails;
@internalProperty() private _error?: string; @state() private _error?: string;
@internalProperty() private _content?: string; @state() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
@@ -57,7 +49,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -4,16 +4,8 @@ import {
mdiInformationVariant, mdiInformationVariant,
mdiMathLog, mdiMathLog,
} from "@mdi/js"; } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
@@ -52,7 +44,7 @@ class HassioAddonDashboard extends LitElement {
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@internalProperty() _error?: string; @state() _error?: string;
private _computeTail = memoizeOne((route: Route) => { private _computeTail = memoizeOne((route: Route) => {
const dividerPos = route.path.indexOf("/", 1); const dividerPos = route.path.indexOf("/", 1);
@@ -133,7 +125,7 @@ class HassioAddonDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
@@ -183,7 +175,7 @@ class HassioAddonDashboard extends LitElement {
if (!validAddon) { if (!validAddon) {
this._error = this.supervisor.localize("my.error_addon_not_found"); this._error = this.supervisor.localize("my.error_addon_not_found");
} else { } else {
navigate(this, `/hassio/addon/${requestedAddon}`, true); navigate(`/hassio/addon/${requestedAddon}`, { replace: true });
} }
} }
} }
@@ -191,6 +183,10 @@ class HassioAddonDashboard extends LitElement {
} }
private async _apiCalled(ev): Promise<void> { private async _apiCalled(ev): Promise<void> {
if (!ev.detail.success) {
return;
}
const pathSplit: string[] = ev.detail.path?.split("/"); const pathSplit: string[] = ev.detail.path?.split("/");
if (!pathSplit || pathSplit.length === 0) { if (!pathSplit || pathSplit.length === 0) {

View File

@@ -1,4 +1,4 @@
import { customElement, property } from "lit-element"; import { customElement, property } from "lit/decorators";
import { HassioAddonDetails } from "../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -42,7 +35,7 @@ class HassioAddonInfoDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -14,17 +14,9 @@ import {
mdiPound, mdiPound,
mdiShield, mdiShield,
} from "@mdi/js"; } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult, import { classMap } from "lit/directives/class-map";
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../../src/common/config/version"; import { atLeastVersion } from "../../../../src/common/config/version";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
@@ -90,9 +82,9 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _metrics?: HassioStats; @state() private _metrics?: HassioStats;
@internalProperty() private _error?: string; @state() private _error?: string;
private _addonStoreInfo = memoizeOne( private _addonStoreInfo = memoizeOne(
(slug: string, storeAddons: StoreAddon[]) => (slug: string, storeAddons: StoreAddon[]) =>
@@ -171,16 +163,16 @@ class HassioAddonInfo extends LitElement {
: ""} : ""}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._updateClicked}>
${this.supervisor.localize("common.update")}
</mwc-button>
${this.addon.changelog ${this.addon.changelog
? html` ? html`
<mwc-button @click=${this._openChangelog}> <mwc-button @click=${this._openChangelog}>
${this.supervisor.localize("addon.dashboard.changelog")} ${this.supervisor.localize("addon.dashboard.changelog")}
</mwc-button> </mwc-button>
` `
: ""} : html`<span></span>`}
<mwc-button @click=${this._updateClicked}>
${this.supervisor.localize("common.update")}
</mwc-button>
</div> </div>
</ha-card> </ha-card>
` `
@@ -261,13 +253,9 @@ class HassioAddonInfo extends LitElement {
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.visit_addon_page", "addon.dashboard.visit_addon_page",
"name", "name",
html`<a html`<a href="${this.addon.url!}" target="_blank" rel="noreferrer"
href="${this.addon.url!}" >${this.addon.name}</a
target="_blank" >`
rel="noreferrer"
>
${this.addon.name}
</a>`
)} )}
</div> </div>
<div class="addon-container"> <div class="addon-container">
@@ -566,9 +554,7 @@ class HassioAddonInfo extends LitElement {
<span slot="heading"> <span slot="heading">
${this.supervisor.localize("addon.dashboard.hostname")} ${this.supervisor.localize("addon.dashboard.hostname")}
</span> </span>
<code slot="description"> <code slot="description"> ${this.addon.hostname} </code>
${this.addon.hostname}
</code>
</ha-settings-row> </ha-settings-row>
${metrics.map( ${metrics.map(
(metric) => (metric) =>
@@ -775,7 +761,7 @@ class HassioAddonInfo extends LitElement {
} }
private _openIngress(): void { private _openIngress(): void {
navigate(this, `/hassio/ingress/${this.addon.slug}`); navigate(`/hassio/ingress/${this.addon.slug}`);
} }
private get _computeShowIngressUI(): boolean { private get _computeShowIngressUI(): boolean {
@@ -991,13 +977,14 @@ class HassioAddonInfo extends LitElement {
showDialogSupervisorUpdate(this, { showDialogSupervisorUpdate(this, {
supervisor: this.supervisor, supervisor: this.supervisor,
name: this.addon.name, name: this.addon.name,
slug: this.addon.slug,
version: this.addon.version_latest, version: this.addon.version_latest,
snapshotParams: { snapshotParams: {
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,
}, },
updateHandler: async () => await this._updateAddon(), updateHandler: async () => this._updateAddon(),
}); });
} }
@@ -1065,7 +1052,7 @@ class HassioAddonInfo extends LitElement {
} }
private _openConfiguration(): void { private _openConfiguration(): void {
navigate(this, `/hassio/addon/${this.addon.slug}/config`); navigate(`/hassio/addon/${this.addon.slug}/config`);
} }
private async _uninstallClicked(ev: CustomEvent): Promise<void> { private async _uninstallClicked(ev: CustomEvent): Promise<void> {
@@ -1104,7 +1091,7 @@ class HassioAddonInfo extends LitElement {
button.progress = false; button.progress = false;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -38,7 +31,7 @@ class HassioAddonLogDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,14 +1,6 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
fetchHassioAddonLogs, fetchHassioAddonLogs,
@@ -29,9 +21,9 @@ class HassioAddonLogs extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @state() private _error?: string;
@internalProperty() private _content?: string; @state() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
@@ -59,7 +51,7 @@ class HassioAddonLogs extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
interface State { interface State {
bold: boolean; bold: boolean;
@@ -25,7 +18,7 @@ class HassioAnsiToHtml extends LitElement {
return html`${this._parseTextToColoredPre(this.content)}`; return html`${this._parseTextToColoredPre(this.content)}`;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
pre { pre {
overflow-x: auto; overflow-x: auto;

View File

@@ -1,13 +1,6 @@
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-relative-time"; import "../../../src/components/ha-relative-time";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -56,13 +49,13 @@ class HassioCardContent extends LitElement {
></ha-svg-icon> ></ha-svg-icon>
`} `}
<div> <div>
<div class="title"> <div class="title">${this.title}</div>
${this.title}
</div>
<div class="addition"> <div class="addition">
${this.description} ${this.description}
${/* treat as available when undefined */ ${
this.available === false ? " (Not available)" : ""} /* treat as available when undefined */
this.available === false ? " (Not available)" : ""
}
${this.datetime ${this.datetime
? html` ? html`
<ha-relative-time <ha-relative-time
@@ -77,7 +70,7 @@ class HassioCardContent extends LitElement {
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
ha-svg-icon { ha-svg-icon {
margin-right: 24px; margin-right: 24px;

View File

@@ -2,13 +2,8 @@ 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/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container"; import "@polymer/paper-input/paper-input-container";
import { import { html, LitElement, TemplateResult } from "lit";
customElement, import { customElement, state } from "lit/decorators";
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload"; import "../../../src/components/ha-file-upload";
@@ -33,9 +28,9 @@ const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
export class HassioUploadSnapshot extends LitElement { export class HassioUploadSnapshot extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@internalProperty() public value: string | null = null; @state() public value: string | null = null;
@internalProperty() private _uploading = false; @state() private _uploading = false;
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`

View File

@@ -0,0 +1,54 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-svg-icon";
@customElement("supervisor-formfield-label")
class SupervisorFormfieldLabel extends LitElement {
@property({ type: String }) public label!: string;
@property({ type: String }) public imageUrl?: string;
@property({ type: String }) public iconPath?: string;
@property({ type: String }) public version?: string;
protected render(): TemplateResult {
return html`
${this.imageUrl
? html`<img loading="lazy" .src=${this.imageUrl} class="icon" />`
: this.iconPath
? html`<ha-svg-icon .path=${this.iconPath} class="icon"></ha-svg-icon>`
: ""}
<span class="label">${this.label}</span>
${this.version
? html`<span class="version">(${this.version})</span>`
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
display: flex;
align-items: center;
}
.label {
margin-right: 4px;
}
.version {
color: var(--secondary-text-color);
}
.icon {
max-height: 22px;
max-width: 22px;
margin-right: 8px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-formfield-label": SupervisorFormfieldLabel;
}
}

View File

@@ -1,13 +1,6 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult, import { classMap } from "lit/directives/class-map";
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../src/components/ha-bar"; import "../../../src/components/ha-bar";
import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-settings-row";
import { roundWithOneDecimal } from "../../../src/util/calculate"; import { roundWithOneDecimal } from "../../../src/util/calculate";
@@ -23,13 +16,9 @@ class SupervisorMetric extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
const roundedValue = roundWithOneDecimal(this.value); const roundedValue = roundWithOneDecimal(this.value);
return html`<ha-settings-row> return html`<ha-settings-row>
<span slot="heading"> <span slot="heading"> ${this.description} </span>
${this.description}
</span>
<div slot="description" .title=${this.tooltip ?? ""}> <div slot="description" .title=${this.tooltip ?? ""}>
<span class="value"> <span class="value"> ${roundedValue} % </span>
${roundedValue} %
</span>
<ha-bar <ha-bar
class="${classMap({ class="${classMap({
"target-warning": roundedValue > 50, "target-warning": roundedValue > 50,
@@ -41,7 +30,7 @@ class SupervisorMetric extends LitElement {
</ha-settings-row>`; </ha-settings-row>`;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
ha-settings-row { ha-settings-row {
padding: 0; padding: 0;
@@ -75,6 +64,7 @@ class SupervisorMetric extends LitElement {
.value { .value {
width: 48px; width: 48px;
padding-right: 4px; padding-right: 4px;
flex-shrink: 0;
} }
`; `;
} }

View File

@@ -0,0 +1,450 @@
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { formatDate } from "../../../src/common/datetime/format_date";
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-formfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
HassioFullSnapshotCreateParams,
HassioPartialSnapshotCreateParams,
HassioSnapshotDetail,
} from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types";
import "./supervisor-formfield-label";
interface CheckboxItem {
slug: string;
checked: boolean;
name: string;
}
interface AddonCheckboxItem extends CheckboxItem {
version: string;
}
const _computeFolders = (folders): CheckboxItem[] => {
const list: CheckboxItem[] = [];
if (folders.includes("homeassistant")) {
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: false,
});
}
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: false });
}
if (folders.includes("share")) {
list.push({ slug: "share", name: "Share", checked: false });
}
if (folders.includes("media")) {
list.push({ slug: "media", name: "Media", checked: false });
}
if (folders.includes("addons/local")) {
list.push({ slug: "addons/local", name: "Local add-ons", checked: false });
}
return list.sort((a, b) => (a.name > b.name ? 1 : -1));
};
const _computeAddons = (addons): AddonCheckboxItem[] =>
addons
.map((addon) => ({
slug: addon.slug,
name: addon.name,
version: addon.version,
checked: false,
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-snapshot-content")
export class SupervisorSnapshotContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
@property() public snapshotType: HassioSnapshotDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[];
@property({ attribute: false }) public addons?: AddonCheckboxItem[];
@property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public snapshotHasPassword = false;
@property({ type: Boolean }) public onboarding = false;
@property() public snapshotName = "";
@property() public snapshotPassword = "";
@property() public confirmSnapshotPassword = "";
public willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this.folders = _computeFolders(
this.snapshot
? this.snapshot.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.snapshot
? this.snapshot.addons
: this.supervisor?.supervisor.addons
);
this.snapshotType = this.snapshot?.type || "full";
this.snapshotName = this.snapshot?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false;
}
}
private _localize = (string: string) =>
this.supervisor?.localize(`snapshot.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
if (!this.onboarding && !this.supervisor) {
return html``;
}
const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.snapshot
? html`<div class="details">
${this.snapshot.type === "full"
? this._localize("full_snapshot")
: this._localize("partial_snapshot")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${this.hass
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
: this.snapshot.date}
</div>`
: html`<paper-input
name="snapshotName"
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
.value=${this.snapshotName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.snapshot || this.snapshot.type === "full"
? html`<div class="sub-header">
${!this.snapshot
? this._localize("type")
: this._localize("select_type")}
</div>
<div class="snapshot-types">
<ha-formfield .label=${this._localize("full_snapshot")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="snapshotType"
.checked=${this.snapshotType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield .label=${this._localize("partial_snapshot")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="snapshotType"
.checked=${this.snapshotType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${this.snapshotType === "partial"
? html`<div class="partial-picker">
${this.snapshot && this.snapshot.homeassistant
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@click=${() => {
this.homeAssistant = !this.homeAssistant;
}}
>
</ha-checkbox>
</ha-formfield>
`
: ""}
${foldersSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("folders")}
.iconPath=${mdiFolder}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
@change=${this._toggleSection}
.checked=${foldersSection.checked}
.indeterminate=${foldersSection.indeterminate}
.section=${"folders"}
>
</ha-checkbox>
</ha-formfield>
<div class="section-content">${foldersSection.templates}</div>
`
: ""}
${addonsSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("addons")}
.iconPath=${mdiPuzzle}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
@change=${this._toggleSection}
.checked=${addonsSection.checked}
.indeterminate=${addonsSection.indeterminate}
.section=${"addons"}
>
</ha-checkbox>
</ha-formfield>
<div class="section-content">${addonsSection.templates}</div>
`
: ""}
</div> `
: ""}
${this.snapshotType === "partial" &&
(!this.snapshot || this.snapshotHasPassword)
? html`<hr />`
: ""}
${!this.snapshot
? html`<ha-formfield
class="password"
.label=${this._localize("password_protection")}
>
<ha-checkbox
.checked=${this.snapshotHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${this.snapshotHasPassword
? html`
<paper-input
.label=${this._localize("password")}
type="password"
name="snapshotPassword"
.value=${this.snapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
${!this.snapshot
? html` <paper-input
.label=${this.supervisor?.localize("confirm_password")}
type="password"
name="confirmSnapshotPassword"
.value=${this.confirmSnapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
: ""}
`
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
.partial-picker ha-formfield {
display: block;
}
.partial-picker ha-checkbox {
--mdc-checkbox-touch-target-size: 32px;
}
.partial-picker {
display: block;
margin: 0px -6px;
}
supervisor-formfield-label {
display: inline-flex;
align-items: center;
}
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
}
.details {
color: var(--secondary-text-color);
}
.section-content {
display: flex;
flex-direction: column;
margin-left: 30px;
}
ha-formfield.password {
display: block;
margin: 0 -14px -16px;
}
.snapshot-types {
display: flex;
margin-left: -13px;
}
.sub-header {
margin-top: 8px;
}
`;
}
public snapshotDetails():
| HassioPartialSnapshotCreateParams
| HassioFullSnapshotCreateParams {
const data: any = {};
if (!this.snapshot) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
}
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
if (!this.snapshot) {
data.confirm_password = this.confirmSnapshotPassword;
}
}
if (this.snapshotType === "full") {
return data;
}
const addons = this.addons
?.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this.folders
?.filter((folder) => folder.checked)
.map((folder) => folder.slug);
if (addons?.length) {
data.addons = addons;
}
if (folders?.length) {
data.folders = folders;
}
if (this.homeAssistant) {
data.homeassistant = this.homeAssistant;
}
return data;
}
private _getSection(section: string) {
const templates: TemplateResult[] = [];
const addons =
section === "addons"
? new Map(
this.supervisor?.addon.addons.map((item) => [item.slug, item])
)
: undefined;
let checkedItems = 0;
this[section].forEach((item) => {
templates.push(html`<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${item.name}
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" &&
!this.onboarding &&
atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon`
: undefined}
.version=${item.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.item=${item}
.checked=${item.checked}
.section=${section}
@change=${this._updateSectionEntry}
>
</ha-checkbox>
</ha-formfield>`);
if (item.checked) {
checkedItems++;
}
});
const checked = checkedItems === this[section].length;
return {
templates,
checked,
indeterminate: !checked && checkedItems !== 0,
};
}
private _handleRadioValueChanged(ev: CustomEvent) {
const input = ev.currentTarget as HaRadio;
this[input.name] = input.value;
}
private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperInputElement;
this[input.name!] = ev.detail.value;
}
private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword;
}
private _toggleSection(ev): void {
const section = ev.currentTarget.section;
this[section] = (section === "addons" ? this.addons : this.folders)!.map(
(item) => ({
...item,
checked: ev.currentTarget.checked,
})
);
}
private _updateSectionEntry(ev): void {
const item = ev.currentTarget.item;
const section = ev.currentTarget.section;
this[section] = this[section].map((entry) =>
entry.slug === item.slug
? {
...entry,
checked: ev.currentTarget.checked,
}
: entry
);
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent;
}
}

View File

@@ -1,13 +1,6 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { compare } from "../../../src/common/string/compare"; import { compare } from "../../../src/common/string/compare";
@@ -90,7 +83,7 @@ class HassioAddons extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
@@ -103,11 +96,11 @@ class HassioAddons extends LitElement {
} }
private _addonTapped(ev: any): void { private _addonTapped(ev: any): void {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}/info`); navigate(`/hassio/addon/${ev.currentTarget.addon.slug}/info`);
} }
private _openStore(): void { private _openStore(): void {
navigate(this, "/hassio/store"); navigate("/hassio/store");
} }
} }

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
@@ -53,7 +46,7 @@ class HassioDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
css` css`

View File

@@ -1,14 +1,7 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { mdiHomeAssistant } from "@mdi/js"; import { mdiHomeAssistant } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
@@ -40,9 +33,8 @@ import { HomeAssistant } from "../../../src/types";
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update"; import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
const computeVersion = (key: string, version: string): string => { const computeVersion = (key: string, version: string): string =>
return key === "os" ? version : `${key}-${version}`; key === "os" ? version : `${key}-${version}`;
};
@customElement("hassio-update") @customElement("hassio-update")
export class HassioUpdate extends LitElement { export class HassioUpdate extends LitElement {
@@ -50,11 +42,12 @@ export class HassioUpdate extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
private _pendingUpdates = memoizeOne((supervisor: Supervisor): number => { private _pendingUpdates = memoizeOne(
return Object.keys(supervisor).filter( (supervisor: Supervisor): number =>
(value) => supervisor[value].update_available Object.keys(supervisor).filter(
).length; (value) => supervisor[value].update_available
}); ).length
);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisor) { if (!this.supervisor) {
@@ -168,6 +161,7 @@ export class HassioUpdate extends LitElement {
showDialogSupervisorUpdate(this, { showDialogSupervisorUpdate(this, {
supervisor: this.supervisor, supervisor: this.supervisor,
name: "Home Assistant Core", name: "Home Assistant Core",
slug: "core",
version: this.supervisor.core.version_latest, version: this.supervisor.core.version_latest,
snapshotParams: { snapshotParams: {
name: `core_${this.supervisor.core.version}`, name: `core_${this.supervisor.core.version}`,
@@ -233,7 +227,7 @@ export class HassioUpdate extends LitElement {
}); });
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -0,0 +1,194 @@
import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input";
import { compare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { dump } from "../../../../src/resources/js-yaml-dump";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
const _filterDevices = memoizeOne(
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
hardware.devices
.filter(
(device) =>
(showAdvanced ||
["tty", "gpio", "input"].includes(device.subsystem)) &&
(device.by_id?.toLowerCase().includes(filter) ||
device.name.toLowerCase().includes(filter) ||
device.dev_path.toLocaleLowerCase().includes(filter) ||
JSON.stringify(device.attributes)
.toLocaleLowerCase()
.includes(filter))
)
.sort((a, b) => compare(a.name, b.name))
);
@customElement("dialog-hassio-hardware")
class HassioHardwareDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioHardwareDialogParams;
@state() private _filter?: string;
public showDialog(params: HassioHardwareDialogParams) {
this._dialogParams = params;
}
public closeDialog() {
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
const devices = _filterDevices(
this.hass.userData?.showAdvanced || false,
this._dialogParams.hardware,
(this._filter || "").toLowerCase()
);
return html`
<ha-dialog
open
scrimClickAction
hideActions
@closed=${this.closeDialog}
.heading=${true}
>
<div class="header" slot="heading">
<h2>
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
</h2>
<mwc-icon-button dialogAction="close">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<search-input
autofocus
no-label-float
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this._dialogParams.supervisor.localize(
"dialog.hardware.search"
)}
>
</search-input>
</div>
${devices.map(
(device) =>
html`<ha-expansion-panel
.header=${device.name}
.secondary=${device.by_id || undefined}
outlined
>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.subsystem"
)}:
</span>
<span>${device.subsystem}</span>
</div>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.device_path"
)}:
</span>
<code>${device.dev_path}</code>
</div>
${device.by_id
? html` <div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.id"
)}:
</span>
<code>${device.by_id}</code>
</div>`
: ""}
<div class="attributes">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.attributes"
)}:
</span>
<pre>${dump(device.attributes, { indent: 2 })}</pre>
</div>
</ha-expansion-panel>`
)}
</ha-dialog>
`;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
mwc-icon-button {
position: absolute;
right: 16px;
top: 10px;
text-decoration: none;
color: var(--primary-text-color);
}
h2 {
margin: 18px 42px 0 18px;
color: var(--primary-text-color);
}
ha-expansion-panel {
margin: 4px 0;
}
pre,
code {
background-color: var(--markdown-code-background-color, none);
border-radius: 3px;
}
pre {
padding: 16px;
overflow: auto;
line-height: 1.45;
font-family: var(--code-font-family, monospace);
}
code {
font-size: 85%;
padding: 0.2em 0.4em;
}
search-input {
margin: 0 16px;
display: block;
}
.device-property {
display: flex;
justify-content: space-between;
}
.attributes {
margin-top: 12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-hardware": HassioHardwareDialog;
}
}

View File

@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioHardwareDialogParams {
supervisor: Supervisor;
hardware: HassioHardwareInfo;
}
export const showHassioHardwareDialog = (
element: HTMLElement,
dialogParams: HassioHardwareDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-hardware",
dialogImport: () => import("./dialog-hassio-hardware"),
dialogParams,
});
};

View File

@@ -1,13 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
@@ -23,7 +15,7 @@ class HassioMarkdownDialog extends LitElement {
@property() public content!: string; @property() public content!: string;
@internalProperty() private _opened = false; @state() private _opened = false;
public showDialog(params: HassioMarkdownDialogParams) { public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title; this.title = params.title;
@@ -50,7 +42,7 @@ class HassioMarkdownDialog extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyleDialog, haStyleDialog,
hassioStyle, hassioStyle,

View File

@@ -6,17 +6,9 @@ import "@material/mwc-tab";
import "@material/mwc-tab-bar"; import "@material/mwc-tab-bar";
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult, import { cache } from "lit/directives/cache";
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { cache } from "lit-html/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
@@ -47,38 +39,39 @@ import { HassioNetworkDialogParams } from "./show-dialog-network";
const IP_VERSIONS = ["ipv4", "ipv6"]; const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network") @customElement("dialog-hassio-network")
export class DialogHassioNetwork extends LitElement export class DialogHassioNetwork
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;
@internalProperty() private _accessPoints?: AccessPoints; @state() private _accessPoints?: AccessPoints;
@internalProperty() private _curTabIndex = 0; @state() private _curTabIndex = 0;
@internalProperty() private _dirty = false; @state() private _dirty = false;
@internalProperty() private _interface?: NetworkInterface; @state() private _interface?: NetworkInterface;
@internalProperty() private _interfaces!: NetworkInterface[]; @state() private _interfaces!: NetworkInterface[];
@internalProperty() private _params?: HassioNetworkDialogParams; @state() private _params?: HassioNetworkDialogParams;
@internalProperty() private _processing = false; @state() private _processing = false;
@internalProperty() private _scanning = false; @state() private _scanning = false;
@internalProperty() private _wifiConfiguration?: WifiConfiguration; @state() private _wifiConfiguration?: WifiConfiguration;
public async showDialog(params: HassioNetworkDialogParams): Promise<void> { public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
this._params = params; this._params = params;
this._dirty = false; this._dirty = false;
this._curTabIndex = 0; this._curTabIndex = 0;
this.supervisor = params.supervisor; this.supervisor = params.supervisor;
this._interfaces = params.supervisor.network.interfaces.sort((a, b) => { this._interfaces = params.supervisor.network.interfaces.sort((a, b) =>
return a.primary > b.primary ? -1 : 1; a.primary > b.primary ? -1 : 1
}); );
this._interface = { ...this._interfaces[this._curTabIndex] }; this._interface = { ...this._interfaces[this._curTabIndex] };
await this.updateComplete; await this.updateComplete;
@@ -542,7 +535,7 @@ export class DialogHassioNetwork extends LitElement
this._wifiConfiguration![id] = value; this._wifiConfiguration![id] = value;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyleDialog, haStyleDialog,
css` css`

View File

@@ -3,16 +3,8 @@ import "@material/mwc-icon-button/mwc-icon-button";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDelete } from "@mdi/js"; import { mdiDelete } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
@@ -39,21 +31,21 @@ class HassioRegistriesDialog extends LitElement {
username: string; username: string;
}[]; }[];
@internalProperty() private _registry?: string; @state() private _registry?: string;
@internalProperty() private _username?: string; @state() private _username?: string;
@internalProperty() private _password?: string; @state() private _password?: string;
@internalProperty() private _opened = false; @state() private _opened = false;
@internalProperty() private _addingRegistry = false; @state() private _addingRegistry = false;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-dialog <ha-dialog
.open=${this._opened} .open=${this._opened}
@closing=${this.closeDialog} @closed=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
@@ -108,8 +100,8 @@ class HassioRegistriesDialog extends LitElement {
</mwc-button> </mwc-button>
` `
: html`${this._registries?.length : html`${this._registries?.length
? this._registries.map((entry) => { ? this._registries.map(
return html` (entry) => html`
<mwc-list-item class="option" hasMeta twoline> <mwc-list-item class="option" hasMeta twoline>
<span>${entry.registry}</span> <span>${entry.registry}</span>
<span slot="secondary" <span slot="secondary"
@@ -129,8 +121,8 @@ class HassioRegistriesDialog extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon> <ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</mwc-list-item> </mwc-list-item>
`; `
}) )
: html` : html`
<mwc-list-item> <mwc-list-item>
<span <span
@@ -220,7 +212,7 @@ class HassioRegistriesDialog extends LitElement {
} }
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,
@@ -252,9 +244,6 @@ class HassioRegistriesDialog extends LitElement {
mwc-list-item span[slot="secondary"] { mwc-list-item span[slot="secondary"] {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-paper-dropdown-menu {
display: block;
}
`, `,
]; ];
} }

View File

@@ -5,17 +5,8 @@ import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, query, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
@@ -37,15 +28,15 @@ class HassioRepositoriesDialog extends LitElement {
@query("#repository_input", true) private _optionInput?: PaperInputElement; @query("#repository_input", true) private _optionInput?: PaperInputElement;
@internalProperty() private _repositories?: HassioAddonRepository[]; @state() private _repositories?: HassioAddonRepository[];
@internalProperty() private _dialogParams?: HassioRepositoryDialogParams; @state() private _dialogParams?: HassioRepositoryDialogParams;
@internalProperty() private _opened = false; @state() private _opened = false;
@internalProperty() private _prosessing = false; @state() private _processing = false;
@internalProperty() private _error?: string; @state() private _error?: string;
public async showDialog( public async showDialog(
dialogParams: HassioRepositoryDialogParams dialogParams: HassioRepositoryDialogParams
@@ -76,7 +67,7 @@ class HassioRepositoriesDialog extends LitElement {
return html` return html`
<ha-dialog <ha-dialog
.open=${this._opened} .open=${this._opened}
@closing=${this.closeDialog} @closed=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
@@ -87,8 +78,8 @@ class HassioRepositoriesDialog extends LitElement {
${this._error ? html`<div class="error">${this._error}</div>` : ""} ${this._error ? html`<div class="error">${this._error}</div>` : ""}
<div class="form"> <div class="form">
${repositories.length ${repositories.length
? repositories.map((repo) => { ? repositories.map(
return html` (repo) => html`
<paper-item class="option"> <paper-item class="option">
<paper-item-body three-line> <paper-item-body three-line>
<div>${repo.name}</div> <div>${repo.name}</div>
@@ -105,13 +96,9 @@ class HassioRepositoriesDialog 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-item> </paper-item>
`; `
}) )
: html` : html` <paper-item> No repositories </paper-item> `}
<paper-item>
No repositories
</paper-item>
`}
<div class="layout horizontal bottom"> <div class="layout horizontal bottom">
<paper-input <paper-input
class="flex-auto" class="flex-auto"
@@ -123,8 +110,11 @@ class HassioRepositoriesDialog extends LitElement {
@keydown=${this._handleKeyAdd} @keydown=${this._handleKeyAdd}
></paper-input> ></paper-input>
<mwc-button @click=${this._addRepository}> <mwc-button @click=${this._addRepository}>
${this._prosessing ${this._processing
? html`<ha-circular-progress active></ha-circular-progress>` ? html`<ha-circular-progress
active
size="small"
></ha-circular-progress>`
: this._dialogParams!.supervisor.localize( : this._dialogParams!.supervisor.localize(
"dialog.repositories.add" "dialog.repositories.add"
)} )}
@@ -138,7 +128,7 @@ class HassioRepositoriesDialog extends LitElement {
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,
@@ -160,9 +150,6 @@ class HassioRepositoriesDialog extends LitElement {
mwc-button { mwc-button {
margin-left: 8px; margin-left: 8px;
} }
ha-paper-dropdown-menu {
display: block;
}
ha-circular-progress { ha-circular-progress {
display: block; display: block;
margin: 32px; margin: 32px;
@@ -205,11 +192,9 @@ class HassioRepositoriesDialog extends LitElement {
if (!input || !input.value) { if (!input || !input.value) {
return; return;
} }
this._prosessing = true; this._processing = true;
const repositories = this._filteredRepositories(this._repositories!); const repositories = this._filteredRepositories(this._repositories!);
const newRepositories = repositories.map((repo) => { const newRepositories = repositories.map((repo) => repo.source);
return repo.source;
});
newRepositories.push(input.value); newRepositories.push(input.value);
try { try {
@@ -222,25 +207,19 @@ class HassioRepositoriesDialog extends LitElement {
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._prosessing = false; this._processing = false;
} }
private async _removeRepository(ev: Event) { private async _removeRepository(ev: Event) {
const slug = (ev.currentTarget as any).slug; const slug = (ev.currentTarget as any).slug;
const repositories = this._filteredRepositories(this._repositories!); const repositories = this._filteredRepositories(this._repositories!);
const repository = repositories.find((repo) => { const repository = repositories.find((repo) => repo.slug === slug);
return repo.slug === slug;
});
if (!repository) { if (!repository) {
return; return;
} }
const newRepositories = repositories const newRepositories = repositories
.map((repo) => { .map((repo) => repo.source)
return repo.source; .filter((repo) => repo !== repository.source);
})
.filter((repo) => {
return repo !== repository.source;
});
try { try {
await setSupervisorOption(this.hass, { await setSupervisorOption(this.hass, {

View File

@@ -0,0 +1,151 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
} from "../../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _error?: string;
@state() private _creatingSnapshot = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public showDialog(params: HassioCreateSnapshotDialogParams) {
this._dialogParams = params;
this._creatingSnapshot = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
)}
>
${this._creatingSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")}
</mwc-button>
<mwc-button
.disabled=${this._creatingSnapshot}
slot="primaryAction"
@click=${this._createSnapshot}
>
${this._dialogParams.supervisor.localize("snapshot.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createSnapshot(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingSnapshot = true;
this._error = "";
if (snapshotDetails.password && !snapshotDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password"
);
this._creatingSnapshot = false;
return;
}
if (
snapshotDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.passwords_not_matching"
);
this._creatingSnapshot = false;
return;
}
delete snapshotDetails.confirm_password;
try {
if (this._snapshotContent.snapshotType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails);
} else {
await createHassioPartialSnapshot(this.hass, snapshotDetails);
}
this._dialogParams!.onCreate();
this.closeDialog();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingSnapshot = false;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
}
}

View File

@@ -1,14 +1,6 @@
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
@@ -18,11 +10,12 @@ import "../../components/hassio-upload-snapshot";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"; import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
@customElement("dialog-hassio-snapshot-upload") @customElement("dialog-hassio-snapshot-upload")
export class DialogHassioSnapshotUpload extends LitElement export class DialogHassioSnapshotUpload
extends LitElement
implements HassDialog<HassioSnapshotUploadDialogParams> { implements HassDialog<HassioSnapshotUploadDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _params?: HassioSnapshotUploadDialogParams; @state() private _params?: HassioSnapshotUploadDialogParams;
public async showDialog( public async showDialog(
params: HassioSnapshotUploadDialogParams params: HassioSnapshotUploadDialogParams
@@ -57,9 +50,7 @@ export class DialogHassioSnapshotUpload extends LitElement
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> <span slot="title"> Upload snapshot </span>
Upload snapshot
</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>
@@ -79,7 +70,7 @@ export class DialogHassioSnapshotUpload extends LitElement
this.closeDialog(); this.closeDialog();
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyleDialog, haStyleDialog,
css` css`

View File

@@ -1,19 +1,12 @@
import "@material/mwc-button"; import { ActionDetail } from "@material/mwc-list";
import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js"; import "@material/mwc-list/mwc-list-item";
import "@polymer/paper-checkbox/paper-checkbox"; import { mdiClose, mdiDotsVertical } from "@mdi/js";
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "@polymer/paper-input/paper-input"; import { customElement, property, query, state } from "lit/decorators";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar"; 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";
@@ -22,96 +15,47 @@ import {
fetchHassioSnapshotInfo, fetchHassioSnapshotInfo,
HassioSnapshotDetail, HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box"; } from "../../../../src/dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../../../../src/polymer-types"; 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 "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
const _computeFolders = (folders) => {
const list: Array<{ slug: string; name: string; checked: boolean }> = [];
if (folders.includes("homeassistant")) {
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: true,
});
}
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: true });
}
if (folders.includes("share")) {
list.push({ slug: "share", name: "Share", checked: true });
}
if (folders.includes("addons/local")) {
list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
}
return list;
};
const _computeAddons = (addons) => {
return addons.map((addon) => ({
slug: addon.slug,
name: addon.name,
version: addon.version,
checked: true,
}));
};
interface AddonItem {
slug: string;
name: string;
version: string;
checked: boolean | null | undefined;
}
interface FolderItem {
slug: string;
name: string;
checked: boolean | null | undefined;
}
@customElement("dialog-hassio-snapshot") @customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends LitElement { class HassioSnapshotDialog
extends LitElement
implements HassDialog<HassioSnapshotDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor?: Supervisor; @state() private _error?: string;
@internalProperty() private _error?: string; @state() private _snapshot?: HassioSnapshotDetail;
@internalProperty() private _onboarding = false; @state() private _dialogParams?: HassioSnapshotDialogParams;
@internalProperty() private _snapshot?: HassioSnapshotDetail; @state() private _restoringSnapshot = false;
@internalProperty() private _folders!: FolderItem[]; @query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
@internalProperty() private _addons!: AddonItem[];
@internalProperty() private _dialogParams?: HassioSnapshotDialogParams;
@internalProperty() private _snapshotPassword!: string;
@internalProperty() private _restoreHass = true;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._folders = _computeFolders(
this._snapshot?.folders
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
this._addons = _computeAddons(
this._snapshot?.addons
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
this._dialogParams = params; this._dialogParams = params;
this._onboarding = params.onboarding ?? false; this._restoringSnapshot = false;
this.supervisor = params.supervisor; }
if (!this._snapshot.homeassistant) {
this._restoreHass = false; public closeDialog() {
} this._snapshot = undefined;
this._dialogParams = undefined;
this._restoringSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -119,204 +63,110 @@ class HassioSnapshotDialog extends LitElement {
return html``; return html``;
} }
return html` return html`
<ha-dialog open @closing=${this._closeDialog} .heading=${true}> <ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${true}
>
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> <span slot="title">${this._snapshot.name}</span>
${this._computeName}
</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>
<div class="details"> ${this._restoringSnapshot
${this._snapshot.type === "full" ? html` <ha-circular-progress active></ha-circular-progress>`
? "Full snapshot" : html`<supervisor-snapshot-content
: "Partial snapshot"} .hass=${this.hass}
(${this._computeSize})<br /> .supervisor=${this._dialogParams.supervisor}
${this._formatDatetime(this._snapshot.date)} .snapshot=${this._snapshot}
</div> .onboarding=${this._dialogParams.onboarding || false}
${this._snapshot.homeassistant .localize=${this._dialogParams.localize}
? html`<div>Home Assistant:</div> >
<paper-checkbox </supervisor-snapshot-content>`}
.checked=${this._restoreHass} ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
@change="${(ev: Event) => {
this._restoreHass = (ev.target as PaperCheckboxElement).checked!;
}}"
>
Home Assistant ${this._snapshot.homeassistant}
</paper-checkbox>`
: ""}
${this._folders.length
? html`
<div>Folders:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._folders.map((item) => {
return html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateFolders(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`;
})}
</paper-dialog-scrollable>
`
: ""}
${this._addons.length
? html`
<div>Add-on:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._addons.map((item) => {
return html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateAddons(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`;
})}
</paper-dialog-scrollable>
`
: ""}
${this._snapshot.protected
? html`
<paper-input
autofocus=""
label="Password"
type="password"
@value-changed=${this._passwordInput}
.value=${this._snapshotPassword}
></paper-input>
`
: ""}
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
<div class="button-row" slot="primaryAction"> <mwc-button
<mwc-button @click=${this._partialRestoreClicked}> .disabled=${this._restoringSnapshot}
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> slot="secondaryAction"
Restore Selected @click=${this._restoreClicked}
</mwc-button> >
${!this._onboarding Restore
? html` </mwc-button>
<mwc-button @click=${this._deleteClicked}>
<ha-svg-icon .path=${mdiDelete} class="icon warning"> ${!this._dialogParams.onboarding
</ha-svg-icon> ? html`<ha-button-menu
<span class="warning">Delete Snapshot</span> fixed
</mwc-button> slot="primaryAction"
` @action=${this._handleMenuAction}
: ""} @closed=${(ev: Event) => ev.stopPropagation()}
</div> >
<div class="button-row" slot="secondaryAction"> <mwc-icon-button slot="trigger" alt="menu">
${this._snapshot.type === "full" <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
? html` </mwc-icon-button>
<mwc-button @click=${this._fullRestoreClicked}> <mwc-list-item>Download Snapshot</mwc-list-item>
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> <mwc-list-item class="error">Delete Snapshot</mwc-list-item>
Restore Everything </ha-button-menu>`
</mwc-button> : ""}
`
: ""}
${!this._onboarding
? html`<mwc-button @click=${this._downloadClicked}>
<ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon>
Download Snapshot
</mwc-button>`
: ""}
</div>
</ha-dialog> </ha-dialog>
`; `;
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,
css` css`
paper-checkbox { ha-svg-icon {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block; display: block;
margin: 4px; text-align: center;
}
mwc-button ha-svg-icon {
margin-right: 4px;
}
.button-row {
display: grid;
gap: 8px;
margin-right: 8px;
}
.details {
color: var(--secondary-text-color);
}
.warning,
.error {
color: var(--error-color);
}
.buttons li {
list-style-type: none;
}
.buttons .icon {
margin-right: 16px;
}
.no-margin-top {
margin-top: 0;
} }
ha-header-bar { ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color); --mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface); --mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0; flex-shrink: 0;
} display: block;
/* overrule the ha-style-dialog max-height on small screens */
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-header-bar {
--mdc-theme-primary: var(--app-header-background-color);
--mdc-theme-on-primary: var(--app-header-text-color, white);
}
} }
`, `,
]; ];
} }
private _updateFolders(item: FolderItem, value: boolean | null | undefined) { private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
this._folders = this._folders.map((folder) => { switch (ev.detail.index) {
if (folder.slug === item.slug) { case 0:
folder.checked = value; this._downloadClicked();
} break;
return folder; case 1:
}); this._deleteClicked();
break;
}
} }
private _updateAddons(item: AddonItem, value: boolean | null | undefined) { private async _restoreClicked() {
this._addons = this._addons.map((addon) => { const snapshotDetails = this._snapshotContent.snapshotDetails();
if (addon.slug === item.slug) { this._restoringSnapshot = true;
addon.checked = value; if (this._snapshotContent.snapshotType === "full") {
} await this._fullRestoreClicked(snapshotDetails);
return addon; } else {
}); await this._partialRestoreClicked(snapshotDetails);
}
this._restoringSnapshot = false;
} }
private _passwordInput(ev: PolymerChangedEvent<string>) { private async _partialRestoreClicked(snapshotDetails) {
this._snapshotPassword = ev.detail.value;
}
private async _partialRestoreClicked() {
if ( if (
this.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this.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 snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`, text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
@@ -330,41 +180,17 @@ class HassioSnapshotDialog extends LitElement {
return; return;
} }
const addons = this._addons if (!this._dialogParams?.onboarding) {
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this._folders
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data: {
homeassistant: boolean;
addons: any;
folders: any;
password?: string;
} = {
homeassistant: this._restoreHass,
addons,
folders,
};
if (this._snapshot!.protected) {
data.password = this._snapshotPassword;
}
if (!this._onboarding) {
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`, `hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
data snapshotDetails
) )
.then( .then(
() => { () => {
alert("Snapshot restored!"); this.closeDialog();
this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -374,20 +200,20 @@ class HassioSnapshotDialog extends LitElement {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(snapshotDetails),
}); });
this._closeDialog(); this.closeDialog();
} }
} }
private async _fullRestoreClicked() { private async _fullRestoreClicked(snapshotDetails) {
if ( if (
this.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this.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 snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`, text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
@@ -402,20 +228,16 @@ class HassioSnapshotDialog extends LitElement {
return; return;
} }
const data = this._snapshot!.protected if (!this._dialogParams?.onboarding) {
? { password: this._snapshotPassword }
: undefined;
if (!this._onboarding) {
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`, `hassio/snapshots/${this._snapshot!.slug}/restore/full`,
data snapshotDetails
) )
.then( .then(
() => { () => {
alert("Snapshot restored!"); this.closeDialog();
this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -425,9 +247,9 @@ class HassioSnapshotDialog extends LitElement {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(snapshotDetails),
}); });
this._closeDialog(); this.closeDialog();
} }
} }
@@ -450,7 +272,7 @@ class HassioSnapshotDialog extends LitElement {
if (this._dialogParams!.onDelete) { if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete(); this._dialogParams!.onDelete();
} }
this._closeDialog(); this.closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -466,7 +288,9 @@ class HassioSnapshotDialog extends LitElement {
`/api/hassio/snapshots/${this._snapshot!.slug}/download` `/api/hassio/snapshots/${this._snapshot!.slug}/download`
); );
} catch (err) { } catch (err) {
alert(`Error: ${extractApiErrorMessage(err)}`); await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
return; return;
} }
@@ -483,13 +307,11 @@ class HassioSnapshotDialog extends LitElement {
} }
} }
const name = this._computeName.replace(/[^a-z0-9]+/gi, "_"); fileDownload(
const a = document.createElement("a"); this,
a.href = signedPath.path; signedPath.path,
a.download = `Hass_io_${name}.tar`; `home_assistant_snapshot_${slugify(this._computeName)}.tar`
this.shadowRoot!.appendChild(a); );
a.click();
this.shadowRoot!.removeChild(a);
} }
private get _computeName() { private get _computeName() {
@@ -497,29 +319,6 @@ class HassioSnapshotDialog extends LitElement {
? this._snapshot.name || this._snapshot.slug ? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot"; : "Unnamed snapshot";
} }
private get _computeSize() {
return Math.ceil(this._snapshot!.size * 10) / 10 + " MB";
}
private _formatDatetime(datetime) {
return new Date(datetime).toLocaleDateString(navigator.language, {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
}
private _closeDialog() {
this._dialogParams = undefined;
this._snapshot = undefined;
this._snapshotPassword = "";
this._folders = [];
this._addons = [];
}
} }
declare global { declare global {

View File

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

View File

@@ -1,4 +1,5 @@
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 HassioSnapshotDialogParams {
@@ -6,6 +7,7 @@ export interface HassioSnapshotDialogParams {
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
supervisor?: Supervisor; supervisor?: Supervisor;
localize?: LocalizeFunc;
} }
export const showHassioSnapshotDialog = ( export const showHassioSnapshotDialog = (

View File

@@ -1,4 +1,4 @@
import type { LitElement } from "lit-element"; import type { LitElement } from "lit";
import { import {
HassioAddonDetails, HassioAddonDetails,
restartHassioAddon, restartHassioAddon,

View File

@@ -1,41 +1,47 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, state } from "lit/decorators";
CSSResult,
customElement,
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-checkbox";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import { import {
extractApiErrorMessage, extractApiErrorMessage,
ignoreSupervisorError, ignoreSupervisorError,
} from "../../../../src/data/hassio/common"; } from "../../../../src/data/hassio/common";
import {
SupervisorFrontendPrefrences,
fetchSupervisorFrontendPreferences,
saveSupervisorFrontendPreferences,
} from "../../../../src/data/supervisor/supervisor";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot"; import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
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";
import memoizeOne from "memoize-one";
const snapshot_before_update = memoizeOne(
(slug: string, frontendPrefrences: SupervisorFrontendPrefrences) =>
slug in frontendPrefrences.snapshot_before_update
? frontendPrefrences.snapshot_before_update[slug]
: true
);
@customElement("dialog-supervisor-update") @customElement("dialog-supervisor-update")
class DialogSupervisorUpdate extends LitElement { class DialogSupervisorUpdate extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@internalProperty() private _opened = false; @state() private _opened = false;
@internalProperty() private _createSnapshot = true; @state() private _action: "snapshot" | "update" | null = null;
@internalProperty() private _action: "snapshot" | "update" | null = null; @state() private _error?: string;
@internalProperty() private _error?: string; @state() private _frontendPrefrences?: SupervisorFrontendPrefrences;
@internalProperty() @state()
private _dialogParams?: SupervisorDialogSupervisorUpdateParams; private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
public async showDialog( public async showDialog(
@@ -43,14 +49,17 @@ class DialogSupervisorUpdate extends LitElement {
): Promise<void> { ): Promise<void> {
this._opened = true; this._opened = true;
this._dialogParams = params; this._dialogParams = params;
this._frontendPrefrences = await fetchSupervisorFrontendPreferences(
this.hass
);
await this.updateComplete; await this.updateComplete;
} }
public closeDialog(): void { public closeDialog(): void {
this._action = null; this._action = null;
this._createSnapshot = true;
this._error = undefined; this._error = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._frontendPrefrences = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@@ -63,7 +72,7 @@ class DialogSupervisorUpdate extends LitElement {
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._dialogParams) { if (!this._dialogParams || !this._frontendPrefrences) {
return html``; return html``;
} }
return html` return html`
@@ -89,6 +98,16 @@ class DialogSupervisorUpdate extends LitElement {
</div> </div>
<ha-settings-row> <ha-settings-row>
<ha-checkbox
.checked=${snapshot_before_update(
this._dialogParams.slug,
this._frontendPrefrences
)}
haptic
@click=${this._toggleSnapshot}
slot="prefix"
>
</ha-checkbox>
<span slot="heading"> <span slot="heading">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.snapshot" "dialog.update.snapshot"
@@ -101,12 +120,6 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.name this._dialogParams.name
)} )}
</span> </span>
<ha-switch
.checked=${this._createSnapshot}
haptic
@click=${this._toggleSnapshot}
>
</ha-switch>
</ha-settings-row> </ha-settings-row>
<mwc-button @click=${this.closeDialog} slot="secondaryAction"> <mwc-button @click=${this.closeDialog} slot="secondaryAction">
${this._dialogParams.supervisor.localize("common.cancel")} ${this._dialogParams.supervisor.localize("common.cancel")}
@@ -140,12 +153,27 @@ class DialogSupervisorUpdate extends LitElement {
`; `;
} }
private _toggleSnapshot() { private async _toggleSnapshot(): Promise<void> {
this._createSnapshot = !this._createSnapshot; this._frontendPrefrences!.snapshot_before_update[
this._dialogParams!.slug
] = !snapshot_before_update(
this._dialogParams!.slug,
this._frontendPrefrences!
);
await saveSupervisorFrontendPreferences(
this.hass,
this._frontendPrefrences!
);
} }
private async _update() { private async _update() {
if (this._createSnapshot) { if (
snapshot_before_update(
this._dialogParams!.slug,
this._frontendPrefrences!
)
) {
this._action = "snapshot"; this._action = "snapshot";
try { try {
await createHassioPartialSnapshot( await createHassioPartialSnapshot(
@@ -173,7 +201,7 @@ class DialogSupervisorUpdate extends LitElement {
this.closeDialog(); this.closeDialog();
} }
static get styles(): CSSResult[] { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,

View File

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

View File

@@ -15,5 +15,11 @@ body {
padding: 0; padding: 0;
height: 100vh; height: 100vh;
} }
@media (prefers-color-scheme: dark) {
body {
background-color: #111111;
color: #e1e1e1;
}
}
`; `;
document.head.appendChild(styleEl); document.head.appendChild(styleEl);

View File

@@ -1,7 +1,11 @@
import { customElement, html, property, PropertyValues } from "lit-element"; import { html, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../src/common/config/version"; import { atLeastVersion } from "../../src/common/config/version";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event"; import { fireEvent } from "../../src/common/dom/fire_event";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { mainWindow } from "../../src/common/dom/get_main_window";
import { navigate } from "../../src/common/navigate";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
@@ -46,14 +50,23 @@ export class HassioMain extends SupervisorBaseElement {
// listen on this element for navigation events, so we need to forward them. // listen on this element for navigation events, so we need to forward them.
// Joakim - April 26, 2021 // Joakim - April 26, 2021
// Due to changes in behavior in Google Chrome, we changed navigate to fire on the top element // Due to changes in behavior in Google Chrome, we changed navigate to listen on the top element
top.addEventListener("location-changed", (ev) => mainWindow.addEventListener("location-changed", (ev) =>
// @ts-ignore // @ts-ignore
fireEvent(this, ev.type, ev.detail, { fireEvent(this, ev.type, ev.detail, {
bubbles: false, bubbles: false,
}) })
); );
// Paulus - May 17, 2021
// Convert the <a> tags to native nav in Home Assistant < 2021.6
document.body.addEventListener("click", (ev) => {
const href = isNavigationClick(ev);
if (href) {
navigate(href);
}
});
// Forward haptic events to parent window. // Forward haptic events to parent window.
window.addEventListener("haptic", (ev) => { window.addEventListener("haptic", (ev) => {
// @ts-ignore // @ts-ignore
@@ -90,7 +103,7 @@ export class HassioMain extends SupervisorBaseElement {
private _applyTheme() { private _applyTheme() {
let themeName: string; let themeName: string;
let options: Partial<HomeAssistant["selectedTheme"]> | undefined; let themeSettings: Partial<HomeAssistant["selectedTheme"]> | undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) { if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName = themeName =
@@ -99,9 +112,9 @@ export class HassioMain extends SupervisorBaseElement {
? this.hass.themes.default_dark_theme! ? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme); : this.hass.themes.default_theme);
options = this.hass.selectedTheme; themeSettings = this.hass.selectedTheme;
if (themeName === "default" && options?.dark === undefined) { if (themeSettings?.dark === undefined) {
options = { themeSettings = {
...this.hass.selectedTheme, ...this.hass.selectedTheme,
dark: this.hass.themes.darkMode, dark: this.hass.themes.darkMode,
}; };
@@ -116,7 +129,7 @@ export class HassioMain extends SupervisorBaseElement {
this.parentElement, this.parentElement,
this.hass.themes, this.hass.themes,
themeName, themeName,
options themeSettings
); );
} }
} }

View File

@@ -1,11 +1,4 @@
import { import { html, LitElement, TemplateResult } from "lit";
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { sanitizeUrl } from "@braintree/sanitize-url"; import { sanitizeUrl } from "@braintree/sanitize-url";
import { import {
createSearchParam, createSearchParam,
@@ -20,6 +13,7 @@ import {
import { navigate } from "../../src/common/navigate"; 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 { Supervisor } from "../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
const REDIRECTS: Redirects = { const REDIRECTS: Redirects = {
supervisor: { supervisor: {
@@ -43,6 +37,12 @@ const REDIRECTS: Redirects = {
addon: "string", addon: "string",
}, },
}, },
supervisor_ingress: {
redirect: "/hassio/ingress",
params: {
addon: "string",
},
},
supervisor_add_addon_repository: { supervisor_add_addon_repository: {
redirect: "/hassio/store", redirect: "/hassio/store",
params: { params: {
@@ -59,7 +59,7 @@ class HassioMyRedirect extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@internalProperty() public _error?: TemplateResult | string; @state() public _error?: TemplateResult | string;
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
@@ -89,7 +89,7 @@ class HassioMyRedirect extends LitElement {
return; return;
} }
navigate(this, url, true); navigate(url, { replace: true });
} }
protected render(): TemplateResult { protected render(): TemplateResult {

View File

@@ -1,4 +1,4 @@
import { customElement, property } from "lit-element"; import { customElement, property } from "lit/decorators";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { import {
HassRouterPage, HassRouterPage,

View File

@@ -1,12 +1,5 @@
import { import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { import {
Supervisor, Supervisor,
supervisorCollection, supervisorCollection,
@@ -46,7 +39,7 @@ class HassioPanel extends LitElement {
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
--app-header-background-color: var(--sidebar-background-color); --app-header-background-color: var(--sidebar-background-color);

View File

@@ -1,4 +1,4 @@
import { customElement, property } from "lit-element"; import { customElement, property } from "lit/decorators";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { import {
@@ -61,11 +61,10 @@ class HassioRouter extends HassRouterPage {
el.hass = this.hass; el.hass = this.hass;
el.narrow = this.narrow; el.narrow = this.narrow;
el.route = route; el.route = route;
el.supervisor = this.supervisor;
if (el.localName === "hassio-ingress-view") { if (el.localName === "hassio-ingress-view") {
el.ingressPanel = this.panel.config && this.panel.config.ingress; el.ingressPanel = this.panel.config && this.panel.config.ingress;
} else {
el.supervisor = this.supervisor;
} }
} }

View File

@@ -1,25 +1,27 @@
import { mdiMenu } from "@mdi/js"; import { mdiMenu } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResultGroup,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import { nextRender } from "../../../src/common/util/render-status";
import { import {
fetchHassioAddonInfo, fetchHassioAddonInfo,
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
createHassioSession, createHassioSession,
validateHassioSession, validateHassioSession,
} from "../../../src/data/hassio/ingress"; } from "../../../src/data/hassio/ingress";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
@@ -29,11 +31,13 @@ import { HomeAssistant, Route } from "../../../src/types";
class HassioIngressView extends LitElement { class HassioIngressView extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property() public route!: Route; @property() public route!: Route;
@property() public ingressPanel = false; @property() public ingressPanel = false;
@internalProperty() private _addon?: HassioAddonDetails; @state() private _addon?: HassioAddonDetails;
@property({ type: Boolean }) @property({ type: Boolean })
public narrow = false; public narrow = false;
@@ -80,6 +84,43 @@ class HassioIngressView extends LitElement {
: iframe}`; : iframe}`;
} }
protected async firstUpdated(): Promise<void> {
if (this.route.path === "") {
const requestedAddon = extractSearchParam("addon");
let addonInfo: HassioAddonDetails;
if (requestedAddon) {
try {
addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon);
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
title: requestedAddon,
});
await nextRender();
navigate("/hassio/store", { replace: true });
return;
}
if (!addonInfo.version) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_not_installed"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else if (!addonInfo.ingress) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_no_ingress"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else {
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
}
}
}
}
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
super.updated(changedProps); super.updated(changedProps);
@@ -109,6 +150,7 @@ class HassioIngressView extends LitElement {
text: "Unable to fetch add-on info to start Ingress", text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor", title: "Supervisor",
}); });
await nextRender();
history.back(); history.back();
return; return;
} }
@@ -118,6 +160,7 @@ class HassioIngressView extends LitElement {
text: "Add-on does not support Ingress", text: "Add-on does not support Ingress",
title: addon.name, title: addon.name,
}); });
await nextRender();
history.back(); history.back();
return; return;
} }
@@ -127,7 +170,8 @@ class HassioIngressView extends LitElement {
text: "Add-on is not running. Please start it first", text: "Add-on is not running. Please start it first",
title: addon.name, title: addon.name,
}); });
navigate(this, `/hassio/addon/${addon.slug}/info`, true); await nextRender();
navigate(`/hassio/addon/${addon.slug}/info`, { replace: true });
return; return;
} }
@@ -140,6 +184,7 @@ class HassioIngressView extends LitElement {
text: "Unable to create an Ingress session", text: "Unable to create an Ingress session",
title: addon.name, title: addon.name,
}); });
await nextRender();
history.back(); history.back();
return; return;
} }
@@ -162,7 +207,7 @@ class HassioIngressView extends LitElement {
fireEvent(this, "hass-toggle-menu"); fireEvent(this, "hass-toggle-menu");
} }
static get styles(): CSSResult { static get styles(): CSSResultGroup {
return css` return css`
iframe { iframe {
display: block; display: block;

View File

@@ -1,4 +1,4 @@
import { css } from "lit-element"; import { css } from "lit";
export const hassioStyle = css` export const hassioStyle = css`
.content { .content {

View File

@@ -1,118 +1,179 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@material/mwc-icon-button"; import { ActionDetail } from "@material/mwc-list";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
mdiDotsVertical,
mdiPackageVariant,
mdiPackageVariantClosed,
} from "@mdi/js";
import "@polymer/paper-checkbox/paper-checkbox";
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
import { import {
css, css,
CSSResultArray, CSSResultGroup,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import "../../../src/components/buttons/ha-progress-button"; import relativeTime from "../../../src/common/datetime/relative_time";
import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
RowClickedEvent,
SelectionChangedEvent,
} from "../../../src/components/data-table/ha-data-table";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card"; import "../../../src/components/ha-fab";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
fetchHassioSnapshots, fetchHassioSnapshots,
HassioFullSnapshotCreateParams, friendlyFolderName,
HassioPartialSnapshotCreateParams,
HassioSnapshot, HassioSnapshot,
reloadHassioSnapshots, reloadHassioSnapshots,
removeSnapshot,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import {
import "../../../src/layouts/hass-tabs-subpage"; showAlertDialog,
import { PolymerChangedEvent } from "../../../src/polymer-types"; showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import "../../../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 "../components/hassio-card-content"; import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
import "../components/hassio-upload-snapshot";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload"; import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
interface CheckboxItem {
slug: string;
checked: boolean;
name?: string;
}
@customElement("hassio-snapshots") @customElement("hassio-snapshots")
class HassioSnapshots extends LitElement { export class HassioSnapshots extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _snapshotName = ""; @property({ type: Object }) public route!: Route;
@internalProperty() private _snapshotPassword = ""; @property({ type: Boolean }) public narrow!: boolean;
@internalProperty() private _snapshotHasPassword = false; @property({ type: Boolean }) public isWide!: boolean;
@internalProperty() private _snapshotType: HassioSnapshot["type"] = "full"; @state() private _selectedSnapshots: string[] = [];
@internalProperty() private _snapshots?: HassioSnapshot[] = []; @state() private _snapshots?: HassioSnapshot[] = [];
@internalProperty() private _addonList: CheckboxItem[] = []; @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
@internalProperty() private _folderList: CheckboxItem[] = [ private _firstUpdatedCalled = false;
{
slug: "homeassistant",
checked: true,
},
{ slug: "ssl", checked: true },
{ slug: "share", checked: true },
{ slug: "media", checked: true },
{ slug: "addons/local", checked: true },
];
@internalProperty() private _error = ""; public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
this.refreshData();
}
}
public async refreshData() { public async refreshData() {
await reloadHassioSnapshots(this.hass); await reloadHassioSnapshots(this.hass);
await this._updateSnapshots(); await this.fetchSnapshots();
} }
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
}
const content: string[] = [];
if (snapshot.content.homeassistant) {
content.push("Home Assistant");
}
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
)?.name || addon
);
}
}
return content.join(", ");
};
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass && this.isConnected) {
this.refreshData();
}
this._firstUpdatedCalled = true;
}
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("snapshot.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("snapshot.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.localize),
},
secondary: {
title: "",
hidden: true,
filterable: true,
},
})
);
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
}))
);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisor) {
return html``;
}
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage-data-table
.tabs=${supervisorTabs}
.hass=${this.hass} .hass=${this.hass}
.localizeFunc=${this.supervisor.localize} .localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
.tabs=${supervisorTabs} .columns=${this._columns(this.narrow)}
.data=${this._snapshotData(this._snapshots || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
clickable
selectable
hasFab
main-page main-page
supervisor supervisor
> >
<span slot="header">
${this.supervisor.localize("panel.snapshots")}
</span>
<ha-button-menu <ha-button-menu
corner="BOTTOM_START" corner="BOTTOM_START"
slot="toolbar-icon" slot="toolbar-icon"
@@ -122,174 +183,66 @@ class HassioSnapshots extends LitElement {
<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> <mwc-list-item>
${this.supervisor.localize("common.reload")} ${this.supervisor?.localize("common.reload")}
</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("snapshot.upload_snapshot")}
</mwc-list-item>` </mwc-list-item>`
: ""} : ""}
</ha-button-menu> </ha-button-menu>
<div class="content"> ${this._selectedSnapshots.length
<h1> ? html`<div
${this.supervisor.localize("snapshot.create_snapshot")} class=${classMap({
</h1> "header-toolbar": this.narrow,
<p class="description"> "table-header": !this.narrow,
${this.supervisor.localize("snapshot.description")} })}
</p> slot="header"
<div class="card-group"> >
<ha-card> <p class="selected-txt">
<div class="card-content"> ${this.supervisor.localize("snapshot.selected", {
<paper-input number: this._selectedSnapshots.length,
autofocus })}
.label=${this.supervisor.localize("snapshot.name")} </p>
name="snapshotName" <div class="header-btns">
.value=${this._snapshotName} ${!this.narrow
@value-changed=${this._handleTextValueChanged}
></paper-input>
${this.supervisor.localize("snapshot.type")}:
<paper-radio-group
name="snapshotType"
type="${this.supervisor.localize("snapshot.type")}"
.selected=${this._snapshotType}
@selected-changed=${this._handleRadioValueChanged}
>
<paper-radio-button name="full">
${this.supervisor.localize("snapshot.full_snapshot")}
</paper-radio-button>
<paper-radio-button name="partial">
${this.supervisor.localize("snapshot.partial_snapshot")}
</paper-radio-button>
</paper-radio-group>
${this._snapshotType === "full"
? undefined
: html`
${this.supervisor.localize("snapshot.folders")}:
${this._folderList.map(
(folder, idx) => html`
<paper-checkbox
.idx=${idx}
.checked=${folder.checked}
@checked-changed=${this._folderChecked}
>
${this.supervisor.localize(
`snapshot.folder.${folder.slug}`
)}
</paper-checkbox>
`
)}
${this.supervisor.localize("snapshot.addons")}:
${this._addonList.map(
(addon, idx) => html`
<paper-checkbox
.idx=${idx}
.checked=${addon.checked}
@checked-changed=${this._addonChecked}
>
${addon.name}
</paper-checkbox>
`
)}
`}
${this.supervisor.localize("snapshot.security")}:
<paper-checkbox
name="snapshotHasPassword"
.checked=${this._snapshotHasPassword}
@checked-changed=${this._handleCheckboxValueChanged}
>
${this.supervisor.localize("snapshot.password_protection")}
</paper-checkbox>
${this._snapshotHasPassword
? html` ? html`
<paper-input <mwc-button
.label=${this.supervisor.localize("snapshot.password")} @click=${this._deleteSelected}
type="password" class="warning"
name="snapshotPassword" >
.value=${this._snapshotPassword} ${this.supervisor.localize("snapshot.delete_selected")}
@value-changed=${this._handleTextValueChanged} </mwc-button>
></paper-input>
` `
: undefined} : html`
${this._error !== "" <mwc-icon-button
? html` <p class="error">${this._error}</p> ` id="delete-btn"
: undefined} class="warning"
@click=${this._deleteSelected}
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("snapshot.delete_selected")}
</paper-tooltip>
`}
</div> </div>
<div class="card-actions"> </div> `
<ha-progress-button : ""}
@click=${this._createSnapshot}
.title=${this.supervisor.info.state !== "running"
? this.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor.info.state
)
: ""}
.disabled=${this.supervisor.info.state !== "running"}
>
${this.supervisor.localize("snapshot.create")}
</ha-progress-button>
</div>
</ha-card>
</div>
<h1>${this.supervisor.localize("snapshot.available_snapshots")}</h1> <ha-fab
<div class="card-group"> slot="fab"
${this._snapshots === undefined @click=${this._createSnapshot}
? undefined .label=${this.supervisor.localize("snapshot.create_snapshot")}
: this._snapshots.length === 0 extended
? html` >
<ha-card> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
<div class="card-content"> </ha-fab>
${this.supervisor.localize("snapshot.no_snapshots")} </hass-tabs-subpage-data-table>
</div>
</ha-card>
`
: this._snapshots.map(
(snapshot) => html`
<ha-card
class="pointer"
.snapshot=${snapshot}
@click=${this._snapshotClicked}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${snapshot.name || snapshot.slug}
.description=${this._computeDetails(snapshot)}
.datetime=${snapshot.date}
.icon=${snapshot.type === "full"
? mdiPackageVariantClosed
: mdiPackageVariant}
icon-class="snapshot"
></hassio-card-content>
</div>
</ha-card>
`
)}
</div>
</div>
</hass-tabs-subpage>
`; `;
} }
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.refreshData();
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("supervisor")) {
this._addonList = this.supervisor.supervisor.addons
.map((addon) => ({
slug: addon.slug,
name: addon.name,
checked: true,
}))
.sort((a, b) => (a.name < b.name ? -1 : 1));
}
}
private _handleAction(ev: CustomEvent<ActionDetail>) { private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
@@ -301,121 +254,10 @@ class HassioSnapshots extends LitElement {
} }
} }
private _handleTextValueChanged(ev: PolymerChangedEvent<string>) { private _handleSelectionChanged(
const input = ev.currentTarget as PaperInputElement; ev: HASSDomEvent<SelectionChangedEvent>
this[`_${input.name}`] = ev.detail.value; ): void {
} this._selectedSnapshots = ev.detail.value;
private _handleCheckboxValueChanged(ev) {
const input = ev.currentTarget as PaperCheckboxElement;
this[`_${input.name}`] = input.checked;
}
private _handleRadioValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperRadioGroupElement;
this[`_${input.getAttribute("name")}`] = ev.detail.value;
}
private _folderChecked(ev) {
const { idx, checked } = ev.currentTarget!;
this._folderList = this._folderList.map((folder, curIdx) =>
curIdx === idx ? { ...folder, checked } : folder
);
}
private _addonChecked(ev) {
const { idx, checked } = ev.currentTarget!;
this._addonList = this._addonList.map((addon, curIdx) =>
curIdx === idx ? { ...addon, checked } : addon
);
}
private async _updateSnapshots() {
try {
this._snapshots = await fetchHassioSnapshots(this.hass);
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
} catch (err) {
this._error = extractApiErrorMessage(err);
}
}
private async _createSnapshot(ev: CustomEvent): Promise<void> {
if (this.supervisor.info.state !== "running") {
await showAlertDialog(this, {
title: this.supervisor.localize("snapshot.could_not_create"),
text: this.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor.info.state
),
});
}
const button = ev.currentTarget as any;
button.progress = true;
this._error = "";
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
this._error = this.supervisor.localize("snapshot.enter_password");
button.progress = false;
return;
}
await this.updateComplete;
const name =
this._snapshotName ||
new Date().toLocaleDateString(navigator.language, {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
});
try {
if (this._snapshotType === "full") {
const data: HassioFullSnapshotCreateParams = { name };
if (this._snapshotHasPassword) {
data.password = this._snapshotPassword;
}
await createHassioFullSnapshot(this.hass, data);
} else {
const addons = this._addonList
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this._folderList
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data: HassioPartialSnapshotCreateParams = {
name,
folders,
addons,
};
if (this._snapshotHasPassword) {
data.password = this._snapshotPassword;
}
await createHassioPartialSnapshot(this.hass, data);
}
this._updateSnapshots();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
button.progress = false;
}
private _computeDetails(snapshot: HassioSnapshot) {
const type =
snapshot.type === "full"
? this.supervisor.localize("snapshot.full_snapshot")
: this.supervisor.localize("snapshot.partial_snapshot");
return snapshot.protected ? `${type}, password protected` : type;
}
private _snapshotClicked(ev) {
showHassioSnapshotDialog(this, {
slug: ev.currentTarget!.snapshot.slug,
supervisor: this.supervisor,
onDelete: () => this._updateSnapshots(),
});
} }
private _showUploadSnapshotDialog() { private _showUploadSnapshotDialog() {
@@ -424,31 +266,110 @@ class HassioSnapshots extends LitElement {
showHassioSnapshotDialog(this, { showHassioSnapshotDialog(this, {
slug, slug,
supervisor: this.supervisor, supervisor: this.supervisor,
onDelete: () => this._updateSnapshots(), onDelete: () => this.fetchSnapshots(),
}), }),
reloadSnapshot: () => this.refreshData(), reloadSnapshot: () => this.refreshData(),
}); });
} }
static get styles(): CSSResultArray { private async fetchSnapshots() {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
}
private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
number: this._selectedSnapshots.length,
}),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
});
if (!confirm) {
return;
}
try {
await Promise.all(
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("snapshot.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
});
}
private _createSnapshot() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"),
text: this.supervisor!.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateSnapshotDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(),
});
}
static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
paper-radio-group { .table-header {
display: block; display: flex;
justify-content: space-between;
align-items: center;
height: 58px;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
} }
paper-radio-button { .header-toolbar {
padding: 0 0 2px 2px; display: flex;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
position: relative;
top: -4px;
} }
paper-radio-button, .selected-txt {
paper-checkbox, font-weight: bold;
paper-input[type="password"] { padding-left: 16px;
display: block; color: var(--primary-text-color);
margin: 4px 0 4px 48px;
} }
.pointer { .table-header .selected-txt {
cursor: pointer; margin-top: 20px;
}
.header-toolbar .selected-txt {
font-size: 16px;
}
.header-toolbar .header-btns {
margin-right: -12px;
}
.header-btns > mwc-button,
.header-btns > mwc-icon-button {
margin: 8px;
} }
`, `,
]; ];

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