Compare commits

...

477 Commits

Author SHA1 Message Date
Thomas Lovén
bda951e6d1 POC 2020-09-14 14:25:40 +02:00
Joakim Sørensen
92ed14c0e4 Merge pull request #6983 from home-assistant/fix-mnuted
Fix muted on video
2020-09-14 11:29:14 +02:00
Bram Kragten
5b94a4de9a Fix muted on video 2020-09-14 11:14:32 +02:00
Kendell R
709112c498 Do safety check before detecting hex value and handle YAML numbers better (#6956)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-14 09:40:35 +02:00
Kendell R
e465ec8835 Make code editor font family follow theme (#6958) 2020-09-14 09:39:47 +02:00
Kendell R
f6eb31bf9d Use --error-color instead of a fixed color (#6961)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2020-09-14 09:37:59 +02:00
Thomas Lovén
426f939982 Add Execute button to script editor (#6957) 2020-09-14 09:34:59 +02:00
Zack Barett
fab6cebf0d fix hard to read text (#6980) 2020-09-14 09:33:23 +02:00
HomeAssistant Azure
ff081dd0f1 [ci skip] Translation update 2020-09-14 00:32:37 +00:00
Zack Barett
868399ed6f HA Logs: Copy log (#6945) 2020-09-13 15:27:16 -05:00
Kendell R
1bc9b95289 Remove useless "My Title" (#6970) 2020-09-13 21:43:54 +02:00
Kendell R
9af805ab5e Make moon icon more readable (#6969) 2020-09-13 21:43:17 +02:00
HomeAssistant Azure
6b88081360 [ci skip] Translation update 2020-09-13 00:32:53 +00:00
Joakim Sørensen
50d37ce4f6 Remove icon slot (#6964) 2020-09-12 21:25:40 +02:00
Joakim Sørensen
af0246cd27 convert ha-refresh-tokens-card (#6962) 2020-09-12 21:05:01 +02:00
Ludeeus
857e4e49d8 Bumped version to 20200912.0 2020-09-12 18:58:49 +00:00
Joakim Sørensen
c1afed7f98 Sort media sources (#6960)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-12 20:34:34 +02:00
Bram Kragten
5480e54185 Mute stream outside of more info (#6959) 2020-09-12 20:07:22 +02:00
Bram Kragten
99d0a0a6fd Lazy load more info content, split logbook and history (#6936) 2020-09-12 19:39:54 +02:00
Joakim Sørensen
8a998369d6 Add padding to rendered template result (#6954) 2020-09-12 19:15:00 +02:00
Zack Barett
8b490c5047 Media Browser: Use Media Class (#6904)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-12 11:59:19 -05:00
Bram Kragten
7e70ba6ab2 FIx entities picker (#6951)
Fixes #6947 ?
2020-09-12 18:18:57 +02:00
Bram Kragten
90e09fc384 Add default hold actions (#6952)
Fixes #6942
2020-09-12 18:07:14 +02:00
Bram Kragten
266f2e763d Sort listening entity and domain in template dev tools (#6953) 2020-09-12 17:48:40 +02:00
Bram Kragten
c979cfb912 Fix sidebar for not existing hidden panel (#6944)
Fixes #6940
2020-09-12 12:52:37 +02:00
HomeAssistant Azure
8ee29b1e43 [ci skip] Translation update 2020-09-12 00:32:19 +00:00
Bram Kragten
26fbc07cac Add edit sidebar button to profile (#6943) 2020-09-12 00:04:25 +02:00
Bram Kragten
f01fe65be4 Show title and name for default panels (#6941)
Fixes #6927
2020-09-11 22:42:11 +02:00
Bram Kragten
3fdd6a80f9 Update codeql-analysis.yml 2020-09-11 22:15:08 +02:00
Bram Kragten
da1de8db1d Create codeql-analysis.yml 2020-09-11 22:13:57 +02:00
Ian Richardson
dd1bf7b49d show first visible view on default (#6567)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-11 22:10:36 +02:00
J. Nick Koston
f18913b5a0 Show which state changed events a template listeners for in dev tools (#6939)
* Show which state changed events a template listeners for in dev tools

* Update src/data/ws-templates.ts

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

* Update src/data/ws-templates.ts

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

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* merge

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

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

* fix string reversal

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

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

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* error to string

* cleanup

* cleanup

* no listeners is probably worth warning about as well

* handle unknown error

* fix error alignment in pre

* fix error alignment in pre

* fix error alignment in pre

* fix error alignment in pre

* reformat

* reformat

* reformat

* fix accidential revert

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

* clear error on success

* tweak to not error if listeners are not returned

* Update src/panels/developer-tools/template/developer-tools-template.ts

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-11 21:36:39 +02:00
Bram Kragten
a2cd227f1a Remove backpath in ozw (#6937)
Fixes #6934
2020-09-11 17:31:04 +02:00
Bram Kragten
78e64e1f60 Show brigtness slider when light is off (#6935)
Fixes #6928
2020-09-11 17:07:46 +02:00
Joakim Sørensen
23a9b79320 Expand groups in entitry row to check toggle (#6930) 2020-09-11 15:46:41 +02:00
Joakim Sørensen
76394ce341 Use secondary text color for no entries (#6931) 2020-09-11 14:52:12 +02:00
Arielpod
1935df1faa Fixed height of circular progress in history (#6929) 2020-09-11 14:45:47 +02:00
Bram Kragten
5af4ce28ab Restrict long press to header of sidebar (#6933) 2020-09-11 14:42:39 +02:00
Bram Kragten
ce8ee569c4 Check if history and logbook are loaded (#6908) 2020-09-11 10:17:05 +02:00
HomeAssistant Azure
b0508f430e [ci skip] Translation update 2020-09-11 00:32:26 +00:00
Philip Allgaier
2139a80a7a Use proper constants for "unavailable" checks (#6922)
* Use proper constants for "unavailable"

* Additional usage of constants
2020-09-10 22:59:45 +02:00
Bram Kragten
aa4bc2ce03 Make logbook a bit smaller in more info (#6921) 2020-09-10 15:40:54 -05:00
Joakim Sørensen
fa65f84e09 Ignore disconnect codes for shutdown and reboot (#6901)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-10 22:04:16 +02:00
Bram Kragten
c06357a351 Only show what triggered a change if it wasn't a user (#6919)
* Only show what triggered a change if it wasn't a user

* Update ha-logbook.ts
2020-09-10 21:47:18 +02:00
Joakim Sørensen
092a02a624 Convert ha-long-lived-access-tokens-card (#6917)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-10 20:51:23 +02:00
J. Nick Koston
b9699f745f Avoid watching all states in the default template (#6918) 2020-09-10 20:50:24 +02:00
Bram Kragten
7fa9f10c30 Don't add space on the bottom when not showing tabs (#6913) 2020-09-10 17:24:01 +02:00
Bram Kragten
7bf0655dae Diable tts inputs when entities is unavailable (#6909)
Fixes #6890
2020-09-10 16:59:12 +02:00
Bram Kragten
96c5fdcbeb Fix some lovelace editors (#6911)
* Fix some lovelace editors

* let -> const
2020-09-10 15:17:32 +02:00
dependabot[bot]
c2e6d40382 Bump http-proxy from 1.17.0 to 1.18.1 (#6914)
Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.17.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.17.0...1.18.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-10 15:16:03 +02:00
Bram Kragten
810d2a1ceb Fix onboarding dark mode (#6910)
Fixes #6882
2020-09-10 13:11:27 +02:00
Bram Kragten
af74f21af9 Dont virtualize logbook in more info (#6907) 2020-09-10 11:12:05 +02:00
HomeAssistant Azure
cdf7558a8e [ci skip] Translation update 2020-09-10 00:32:03 +00:00
Zack Barett
41b86e6c10 Fix media browse item width (#6870) 2020-09-09 17:03:11 -05:00
Bram Kragten
3039c678a5 Fix check 2020-09-09 23:08:16 +02:00
Joakim Sørensen
498882d014 Remove mobile_app from generated Lovelace (#6873)
* Hide mobile_app from generated Lovelace

* simplify

* Move to computeDefaultViewStates

* removed -> hidden

* Update src/panels/lovelace/common/generate-lovelace-config.ts

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

* Adjust for Set

* Review comments

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-09 23:01:50 +02:00
Bram Kragten
6c2b8c2abb Bumped version to 20200909.0 2020-09-09 23:00:48 +02:00
Bram Kragten
e955cc4378 Check for hass when setting themes (#6897) 2020-09-09 22:57:44 +02:00
Bram Kragten
eb96dd4803 Handle not defined entities (#6898) 2020-09-09 22:55:43 +02:00
Bram Kragten
e0bdef98a6 Only show history tabs for certain domains (#6895)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
2020-09-09 22:10:23 +02:00
Bram Kragten
1130007d14 Fix mjpeg player (#6896) 2020-09-09 22:09:56 +02:00
Bram Kragten
d99d092784 Enlarge touch target delete button (#6893) 2020-09-09 21:02:14 +02:00
Philip Allgaier
e3b18a33ca Disable "Execute" if automation is unavailable (#6866) 2020-09-09 20:49:56 +02:00
Philip Allgaier
1890aab1e6 Color all deletion options consistenly red (#6891)
* Color all deletion options consistenly red

* CSS cleanup

* Color the "Remove Selected" entity config button

* Make eslint happy

* Getting rid of a wayward bracket
2020-09-09 20:48:51 +02:00
Joakim Sørensen
42bf350034 Add ha-user-badge to view visibility editor (#6885) 2020-09-09 17:26:22 +02:00
epenet
5ff52ea113 Update constant name to make it clearer (#6881) 2020-09-09 17:24:13 +02:00
Bram Kragten
432e3ba636 Fix entity drag (#6884) 2020-09-09 17:23:03 +02:00
Zack Barett
f7ab52fe9a Remove sort from frontend for now (#6886) 2020-09-09 17:22:34 +02:00
Bram Kragten
ad8430049d Merge pull request #6878 from home-assistant/external-header-fallback 2020-09-09 17:02:24 +02:00
epenet
2dffe7ba9e Add binary sensor icon for DEVICE_CLASS_BATTERY_CHARGING (#6876)
* Add binary sensor icon for DEVICE_CLASS_BATTERY_CHARGING

* Update icons for DEVICE_CLASS_BATTERY_CHARGING
2020-09-09 13:27:54 +02:00
Ludeeus
5b8f97e0f6 fix missing step 2020-09-09 10:17:57 +00:00
Ludeeus
b3a763a48d Add fallback for renderExternalStepHeader 2020-09-09 10:16:54 +00:00
HomeAssistant Azure
07569f10b5 [ci skip] Translation update 2020-09-09 00:32:22 +00:00
Philip Allgaier
7c5a78a1cf Media player visual improvements (#6817)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-08 16:36:44 -05:00
Bram Kragten
0e021e7d7d Bumped version to 20200908.0 2020-09-08 20:58:47 +02:00
Zack Barett
b30ee884a7 Fix for Camera streams that don't support stream (#6863) 2020-09-08 20:57:17 +02:00
J. Nick Koston
869b7c85ca Ensure we pickup all the reloadable domains (#6861) 2020-09-08 20:56:45 +02:00
Zack Barett
4d0d1ed2a1 Undo my commit into dev (#6864) 2020-09-08 20:52:21 +02:00
Zack Arnett
291983e4c3 Merge branch 'dev' of https://github.com/home-assistant/frontend into dev 2020-09-08 10:25:50 -05:00
Bram Kragten
909cff2158 Fix timer entity display (#6849) 2020-09-08 17:01:04 +02:00
Bram Kragten
4e676b1dba Fix light more info (#6855) 2020-09-08 09:17:01 -05:00
Paulus Schoutsen
9149bb9333 Remove deprecated HTML support (#6858) 2020-09-08 15:41:17 +02:00
Bram Kragten
4631994f20 Fix sidebar issues (#6853)
* Fix sidebar issues

* fix navigate in demo
2020-09-08 14:10:34 +02:00
Joakim Sørensen
82e9178320 Add warning class to delete (#6852)
* Add error class to delete

* Apply suggestions from code review

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

* Add missing haStyle

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-08 13:56:51 +02:00
Bram Kragten
67b4688168 Make time wider in logbook (#6854)
Fixes #6842
2020-09-08 13:28:29 +02:00
Joakim Sørensen
6e0e169b6e Add reload for platforms with reload service (#6851) 2020-09-08 13:27:59 +02:00
Zack Barett
100ba8edfa Add allowed options to entities struct so UI editor can still be used (#6823) 2020-09-08 11:37:49 +02:00
Zack Barett
d7448ecb95 Fix Calendar Card in Add Card dialog (#6833) 2020-09-08 09:17:49 +02:00
Zack Barett
8b1801f378 Fix header on media browser in safari (#6838) 2020-09-08 09:14:34 +02:00
Bram Kragten
01a4d57566 Merge pull request #6835 from home-assistant/fix-more-info
Fix More info content from having space on right
2020-09-08 09:13:52 +02:00
Zack Arnett
7edc9064d9 Fix light extra attributes start fix for history 2020-09-07 20:49:53 -05:00
Zack Arnett
30c47a65f4 fix more info content 2020-09-07 19:47:25 -05:00
HomeAssistant Azure
0889f42a00 [ci skip] Translation update 2020-09-08 00:32:39 +00:00
Bram Kragten
f15fbe53cf Bumped version to 20200907.0 2020-09-07 20:40:03 +02:00
Bram Kragten
046f7b5153 Handle media browser errors (#6813)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2020-09-07 20:39:26 +02:00
Zack Barett
5339fe6e06 Updates to correct zindexs on new dialogs (#6816) 2020-09-07 20:11:49 +02:00
Bram Kragten
de7ffb10cb Automation editor tweaks (#6713)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2020-09-07 19:53:10 +02:00
Bram Kragten
80224e6974 Fix onboarding styling (#6819) 2020-09-07 19:48:19 +02:00
Joakim Sørensen
0c7c536f73 Fixes issues with channel toggle (#6812) 2020-09-07 19:30:16 +02:00
Bram Kragten
e5c386c39a Fix white flash in dark mode (#6815) 2020-09-07 18:19:28 +02:00
Zack Barett
bb2462483e Use Sortable to move entities in entities editor (#6810) 2020-09-07 13:47:24 +02:00
Bram Kragten
d5bc498373 Fix action handler bugs (#6811) 2020-09-07 13:41:59 +02:00
Bram Kragten
979b7ae651 Add attention required for config flows in progress (#6808) 2020-09-07 09:09:42 +02:00
HomeAssistant Azure
c73330a466 [ci skip] Translation update 2020-09-07 00:32:51 +00:00
Zack Barett
efe8eca4e3 Media browser updates (#6801) 2020-09-06 18:28:15 -05:00
Sean Mooney
a37aad18b7 Minor typo fix (#6809)
Unkown = Unknown
2020-09-06 23:53:00 +02:00
Philip Allgaier
cfa0c45213 Fix media browser panel title + selection header color (#6807) 2020-09-06 22:17:34 +02:00
Joakim Sørensen
509481ef06 Ignore more proxy disconnect codes (#6805)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-06 20:29:02 +02:00
Bram Kragten
9aa8175e23 Update ha-logbook.ts 2020-09-06 19:59:29 +02:00
Joakim Sørensen
76f59d99a2 Remove extra > from button (#6804) 2020-09-06 18:29:39 +02:00
Joakim Sørensen
bd66bd6cf0 Add color to hass-error-screen (#6803) 2020-09-06 18:29:22 +02:00
HomeAssistant Azure
d69333dea4 [ci skip] Translation update 2020-09-06 00:32:31 +00:00
Joakim Sørensen
3fd7899b93 Display services as services and not devices (#6798)
* Display services as services and not devices

* remove seperator

* Add comma

* Update src/panels/config/integrations/ha-integration-card.ts

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

* Fix spacing

* Remove check

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-06 02:16:50 +02:00
Pawel
8f8a2cea56 Don't show source select dialog when media player Unavailable. (#6799) 2020-09-05 17:35:34 -05:00
Donnie
879011c8e9 Fix incorrect link to mode documentation in automation editor (#6793) 2020-09-05 15:51:52 +02:00
HomeAssistant Azure
d5794c3e2e [ci skip] Translation update 2020-09-05 00:32:46 +00:00
Charles Garwood
fcc22ba560 Add node details shortcut to OZW device pages (#6791)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-04 23:14:15 +02:00
Bram Kragten
2adeb88fe6 Bumped version to 20200904.0 2020-09-04 23:02:24 +02:00
Zack Arnett
e63a78bcdb Media Browser Panel (#6772)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-04 23:01:20 +02:00
Bram Kragten
b065f002a4 Allow local storage decorator to register as property (#6776) 2020-09-04 22:22:55 +02:00
Zack Arnett
349a5f52b1 More Info History: Scrollbar Style (#6790) 2020-09-04 20:56:48 +02:00
Charles Garwood
aa5e20df05 Add basic nodes list & node metadata to OZW config panel (#6719)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-04 20:55:40 +02:00
Bram Kragten
793b9f238c Tweak card create dialog a bit (#6787) 2020-09-04 18:30:04 +02:00
Philip Allgaier
9c4fdaa4f3 Minor EN text improvements / fixes (#6788) 2020-09-04 16:21:15 +02:00
Bram Kragten
d1a9cb488a Add person badge (#6785)
* Add person badge

* Update src/components/user/ha-person-badge.ts

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* Revert screwup by @ludeeus

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
Co-authored-by: Ludeeus <ludeeus@ludeeus.dev>
2020-09-04 15:54:42 +02:00
Bram Kragten
faee2c3e1b Fix gauge editor (#6783) 2020-09-04 15:18:01 +02:00
Joakim Sørensen
b7845c318e Error extraction and target cleanup (#6782) 2020-09-04 15:08:30 +02:00
Bram Kragten
426a0727c3 Add person picture to user badge (#6784)
* Use person picture ha-user-badge

* Fix missing import

* lint

* User person picture in user-badge

Co-authored-by: Ludeeus <ludeeus@ludeeus.dev>
2020-09-04 14:52:21 +02:00
HomeAssistant Azure
584e509a9c [ci skip] Translation update 2020-09-04 00:32:27 +00:00
Zack Arnett
f3639c2663 Card Picker: Entity Picker (#6693) 2020-09-03 18:28:53 -05:00
Zack Arnett
1431e75f8b More Info: Add History Tab (#6758)
Co-authored-by: J. Nick Koston <nick@koston.org>
2020-09-03 18:28:10 -05:00
Bram Kragten
be8812e0af Add input field to ha form integer when it has a min and max (#6781) 2020-09-03 23:23:10 +02:00
J. Nick Koston
fd6436d490 Update reloadables to include telegram/smtp/mqtt (#6759) 2020-09-03 17:47:08 +02:00
Tomasz
fd1342f9d1 add rpi_gpio translation (#6778) 2020-09-03 16:55:31 +02:00
Joakim Sørensen
5fa0012195 hassio-addon-info feedback (#6734)
* hassio-addon-info feedback

* lint

* init config validation

* better error

* Finish

* sort imports

* Use startup type for watchdog

* Only show error if issue with config

* Adjust
2020-09-03 16:38:59 +02:00
Bram Kragten
9dbb67ef01 Fix shouldHandleRequestSelectedEvent (#6777) 2020-09-03 15:36:15 +02:00
Bram Kragten
d16e2f37d4 Generalize reloadableDomains (#6773)
* Generalize reloadableDomains

* Add back translations
2020-09-03 15:24:43 +02:00
Florian Gareis
d9e8b53ffe Add static color for home and not_home states (#6700)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-03 14:39:11 +02:00
Bram Kragten
1997e63b7c Fix action handler for touch (#6775)
* Fix action handler for touch

* Console
2020-09-03 14:09:25 +02:00
Joakim Sørensen
6f673359ff hassio-supervisor-log feedback (#6736)
* hassio-supervisor-log feedback

* Update hassio/src/system/hassio-supervisor-log.ts

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-03 11:49:10 +02:00
Joakim Sørensen
45dfbff10a hassio-addon-config feedback (#6732)
* hassio-addon-config feedback

* Update hassio/src/addon-view/config/hassio-addon-config.ts

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

* Update hassio/src/addon-view/config/hassio-addon-config.ts

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-09-03 11:03:08 +02:00
Joakim Sørensen
348ee96274 hassio-addon-audio feedback (#6731) 2020-09-03 10:45:18 +02:00
Joakim Sørensen
8edee32e77 hassio-supervisor-info feedback (#6737) 2020-09-03 10:44:53 +02:00
Joakim Sørensen
6d8d263ca6 hassio-addon-network feedback (#6733) 2020-09-03 10:33:45 +02:00
Joakim Sørensen
35923709e2 hassio-snapshots feedback (#6735) 2020-09-03 10:32:21 +02:00
Joakim Sørensen
fdd4d53448 hassio-host-info feedback (#6738)
* hassio-host-info feedback

* lint
2020-09-03 10:27:34 +02:00
HomeAssistant Azure
06419f662e [ci skip] Translation update 2020-09-03 00:32:39 +00:00
Bram Kragten
57763ef032 Fix layout of domain toggler dialog (#6771) 2020-09-02 20:46:38 +02:00
Joakim Sørensen
8e506f7749 Handle connection drops when upgrading (#6767) 2020-09-02 16:21:59 +02:00
Joakim Sørensen
c7f8fe1468 Don't show NM before 115 (#6768) 2020-09-02 15:58:14 +02:00
HomeAssistant Azure
4156a4e36d [ci skip] Translation update 2020-09-02 00:32:17 +00:00
Bram Kragten
0c212d39eb Bumped version to 20200901.0 2020-09-01 23:31:59 +02:00
Bram Kragten
3bd2e8dbf5 Allow to move and hide sidebar items (#6755) 2020-09-01 23:28:03 +02:00
Bram Kragten
5292119e6e Allow exposing domains in cloud (#6696)
* Allow exposing domains in cloud

https://github.com/home-assistant/core/pull/39216

* Update styles

* Lint

* Apply suggestions from code review

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

* Comments

* Add translations

* Apply suggestions from code review

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2020-09-01 10:23:59 +02:00
HomeAssistant Azure
994a397231 [ci skip] Translation update 2020-09-01 00:32:43 +00:00
Aidan Timson
353b71f803 Stop image from rendering for camera when disconnected and update when reconnected (#6677)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-31 23:54:57 +02:00
Bram Kragten
eb12afe8cc Media browser tweaks (#6720)
Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
2020-08-31 23:30:41 +02:00
Joakim Sørensen
4a176f1b43 Call service button feedback (#6752) 2020-08-31 14:38:24 -05:00
Kendell R
8e228baa82 Change spot clean icon (#6750)
* Change spot clean icon

* Switch to target-variant

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-31 09:38:32 -05:00
Pascal Roeleven
154b53b0d8 Fix automation modes documentation link (#6754) 2020-08-31 16:04:02 +02:00
Tomasz
a3f680d80c Fix render modifiers - public to protected (#6753) 2020-08-31 14:59:12 +02:00
HomeAssistant Azure
0d75fe6b81 [ci skip] Translation update 2020-08-31 00:32:41 +00:00
Paulus Schoutsen
4070380ded Remove credentials for load module (#6746)
Fixes #6745
2020-08-30 16:42:02 +02:00
Bram Kragten
41195dcef0 Remove animation delay from paper tooltip (#6716) 2020-08-30 10:03:04 +02:00
Joakim Sørensen
78a1e45be2 Dismiss dialog if the user clicks outside it or hit the escape button (#6741)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-30 09:42:58 +02:00
Joakim Sørensen
d8e88bc58d Ignore 504 errors while updating (#6743) 2020-08-30 09:39:34 +02:00
HomeAssistant Azure
448e9b71b8 [ci skip] Translation update 2020-08-30 00:32:28 +00:00
HomeAssistant Azure
2e178164cc [ci skip] Translation update 2020-08-29 00:32:29 +00:00
Zack Arnett
9f2e3f05a1 Entities & Glance Card: Add state color to editors (#6723) 2020-08-29 00:09:42 +02:00
Joakim Sørensen
405bd29ebd Convert ha-progress-button to lit (#6728) 2020-08-29 00:08:56 +02:00
Bram Kragten
b39b54e0ac Enable filtering on hidden columns (#6717) 2020-08-28 15:50:32 +02:00
J. Nick Koston
119c5c9071 Add generic/generic_thermostat/homekit/min_max/history_stats/trend/ping/filesize to the list of reloadables (#6721)
* Add generic/generic_thermostat/homekit/min_max/history_stats/trend/ping/filesize to the list of reloadables

* Update src/translations/en.json

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>

* Update src/translations/en.json

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>

* Update src/translations/en.json

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>

* Update src/translations/en.json

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
2020-08-28 08:31:54 -05:00
Bram Kragten
7a4c9b128c Allow owner users to change password of any user (#6698)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-08-28 14:30:42 +02:00
Joakim Sørensen
dc5b92030f Use ha-progress-button for update cards (#6725) 2020-08-28 14:16:55 +02:00
Zack Arnett
db0a010d7c Graph Footer: Fix Editor Warning (#6724) 2020-08-28 13:20:23 +02:00
HomeAssistant Azure
a117a19bdf [ci skip] Translation update 2020-08-28 00:32:05 +00:00
dklemm
5f46fdb406 Restored card margin for narrow viewports (#6454) 2020-08-27 16:13:21 +02:00
Joakim Sørensen
f0201de4cc Round with 1 decimal (#6715) 2020-08-27 07:52:57 -05:00
HomeAssistant Azure
6cd51a318b [ci skip] Translation update 2020-08-27 00:32:23 +00:00
Joakim Sørensen
c1a4b27bc7 Adds confirmation dialog to updates (#6709) 2020-08-26 18:13:22 +02:00
Joakim Sørensen
5113222050 Adds ha-bar component (#6708)
* Adds ha-bar component

* Move calculate logic to util

* Add test

* Prove overshot with test

* Remove stuff

* remove unused styles

* commit correct file

* remove root style

* Move to CSS

* html -> svg
2020-08-26 11:08:21 -05:00
Joakim Sørensen
90f12eea5e Limit changing network to systems that have that support (#6711) 2020-08-26 17:41:02 +02:00
Bram Kragten
2403743701 Fix more info media player dropdowns (#6712) 2020-08-26 17:34:00 +02:00
Joakim Sørensen
3e6a759309 Changes to add-on options (#6706) 2020-08-26 15:53:25 +02:00
Joakim Sørensen
35a430e9f4 Add watchdog toggle (#6703) 2020-08-26 15:27:19 +02:00
Bram Kragten
b644f7d23d Theme tweaks (#6701) 2020-08-26 12:38:48 +02:00
Bram Kragten
7702a05464 Filter attributes in more info light (#6707) 2020-08-26 11:13:29 +02:00
J. Nick Koston
493af5fe82 Add rest/command_line/filter/statistics to the list of reloadables (#6705)
* Add rest and command_line to the list of reloadables

* Update src/translations/en.json

* Add the latest ones
2020-08-25 21:33:29 -05:00
HomeAssistant Azure
ac66a59cec [ci skip] Translation update 2020-08-26 00:35:17 +00:00
J. Nick Koston
e10c8faa47 Add UI control to reload a config entry (integration) (#6656)
* Add UI control to reload an integration

* Refactor to move reload above delete and check supports_unload

* Avoid index switch

* Update src/panels/config/integrations/ha-integration-card.ts
2020-08-25 18:00:50 -05:00
Bram Kragten
9b7d17433c Add aria roles to data table (#6702) 2020-08-26 00:38:02 +02:00
J. Nick Koston
a40eb1ff43 Add universal to the list of reloadables (#6697)
* Add universal to the list of reloadables

* Update src/translations/en.json

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-26 00:30:02 +02:00
Joakim Sørensen
04df6c3e9e Supervisor system (#6699)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-25 23:34:02 +02:00
Bram Kragten
1b970e5a66 No background repeat media browser (#6695) 2020-08-25 14:06:03 +02:00
Bram Kragten
75406c2d01 Add disabled text color to dark mode (#6694) 2020-08-25 11:09:23 +02:00
HomeAssistant Azure
64d3511fbc [ci skip] Translation update 2020-08-25 00:32:10 +00:00
Paulus Schoutsen
c610f54977 Add methods for new trigger/condition commands (#6675) 2020-08-24 23:02:04 +02:00
Bram Kragten
358c5205d2 Fix calendar (#6691) 2020-08-24 13:32:55 -05:00
Bram Kragten
5503cd0589 Bumped version to 20200824.0 2020-08-24 20:01:54 +02:00
Zack Arnett
dae42b1bd9 Calendar Card: New Card (#5813)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-24 19:59:50 +02:00
fabiocastagnino
06a25284e8 Add icons for new sensor device classes (#6193)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-24 19:50:47 +02:00
J. Nick Koston
5989560f15 Show the entity id that first used the context in logbook 2020-08-24 12:45:04 -05:00
J. Nick Koston
63c995e5da cleanups 2020-08-24 12:24:24 -05:00
Charles Garwood
dc5607f554 Beginning pages for OZW Config Panel (#6670) 2020-08-24 19:21:25 +02:00
J. Nick Koston
d49302c032 typo 2020-08-24 12:20:48 -05:00
J. Nick Koston
63fef9bd4b Adjust to handle service calls and described events 2020-08-24 11:46:16 -05:00
Bram Kragten
6599351d45 Replace confirm with confirmation dialogs in snapshots (#6690) 2020-08-24 18:36:47 +02:00
Joakim Sørensen
47e9531972 Use media query for darkmode on login and onboarding (#6625)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-24 18:29:16 +02:00
Bram Kragten
3ba31483f4 Convert dev tools template to Lit and store last used template (#6669) 2020-08-24 18:26:41 +02:00
Bram Kragten
f4ca94f2e1 Move click listener (#6688)
* Move click listener

* Move label
2020-08-24 10:52:04 -05:00
Zack Arnett
67f9be2b77 Media Browser: Fix dark mode (#6687) 2020-08-24 17:31:16 +02:00
Bram Kragten
e2fd155e1b Fix updating history card + only update when entity changed (#6647) 2020-08-24 17:03:42 +02:00
Bram Kragten
931068dede Use default panel if panel in settings doesn't exist (#6667) 2020-08-24 16:49:32 +02:00
Bram Kragten
bc4c9cc40d Adjust tags just scanned time display (#6663) 2020-08-24 16:48:00 +02:00
Bram Kragten
294665fbe8 Fix time format in charts (#6671) 2020-08-24 16:45:30 +02:00
Bram Kragten
e8f6a79c8f Fix create write tag (#6679) 2020-08-24 16:34:55 +02:00
Zack Arnett
5fd8b5c5b9 Media browser (#6672)
* Add media browser stub

* Updates from first night

* Visual updates

* First pr push?

* Updates

* Add to dialog Havent tested it idk where to put it

* comments - Add overflow menu

* change to flex end

* lint

* Refresh the previous item

* simplify child render logic

* Add show media browser dialog func (thanks bram)

* Add to more info dialog. Not perfect. Visual bugs

* Change play/picked event to callback

* Don't use data table

* Move play button

* Fix dialog getting too wide

* Style tweaks

* tweaks

* Fix padding mobile

* Update ha-media-player-browse.ts

* Remove Color on folder icon

* Leave dialog open on play

* Move more info icon

* Remove unneeded files

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-24 09:31:25 -05:00
Bram Kragten
226b2a73af Update mocha and eslint-import-resolver-webpack (#6497) 2020-08-24 14:35:50 +02:00
J. Nick Koston
42d421a6fc Add template to the list of reloadables (#6673)
Make reloadable check to see if the service
is loaded instead of the component
2020-08-24 14:32:52 +02:00
Joakim Sørensen
a90203f256 Use secure cookie if https (#6644) 2020-08-24 14:21:57 +02:00
Aidan Timson
c3ef79caa9 Improve messaging of empty device info cards (#6628) 2020-08-24 14:20:32 +02:00
Joakim Sørensen
1439afcd9c Update MDI to 5.5.55 (#6598) 2020-08-24 13:59:31 +02:00
uvjustin
d263b19910 Play HLS with Exoplayer on Android (#6606) 2020-08-24 11:50:40 +02:00
HomeAssistant Azure
1e477226ea [ci skip] Translation update 2020-08-24 00:32:13 +00:00
J. Nick Koston
026fc1d2e3 Show the entity id that first used the context in logbook 2020-08-23 17:00:27 -05:00
HomeAssistant Azure
2d4bd9857a [ci skip] Translation update 2020-08-23 00:32:24 +00:00
HomeAssistant Azure
8f48f5b45c [ci skip] Translation update 2020-08-22 00:32:43 +00:00
Bram Kragten
22210b7400 Clarify renaming entity ids (#6668) 2020-08-21 16:25:33 +02:00
HomeAssistant Azure
7d05855ee0 [ci skip] Translation update 2020-08-21 00:32:24 +00:00
Bram Kragten
4561957e56 Merge branch 'master' into dev 2020-08-20 15:53:59 +02:00
Bram Kragten
3367fadc3a Bumped version to 20200820.0 2020-08-20 15:52:06 +02:00
Paulus Schoutsen
d7e409b042 Add tag config panel (#6601)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-20 15:34:52 +02:00
HomeAssistant Azure
a0b28e8ad1 [ci skip] Translation update 2020-08-20 00:32:18 +00:00
Paulus Schoutsen
f928a8e58e Add picture upload component (#6646)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-19 11:33:18 +02:00
Bram Kragten
0bc4b3d0fa Adds theming and dark mode to code editor (#6547) 2020-08-19 11:04:05 +02:00
Zack Arnett
e352768388 Save Config Dialog - Convert to MWC (#6590) 2020-08-19 11:02:21 +02:00
HomeAssistant Azure
6835b73e49 [ci skip] Translation update 2020-08-19 00:33:28 +00:00
HomeAssistant Azure
f1503f871b [ci skip] Translation update 2020-08-18 00:32:11 +00:00
Charles Garwood
c4d8aba5c8 Add OZW Refresh Node Dialog (#6530) 2020-08-17 19:54:03 +02:00
Zack Arnett
39f24c41ad Media More Info: Convert to Lit Element (#6619)
* lit element

* Remove Properties

* review comments

* This should be somewhat better.
2020-08-17 11:24:19 -05:00
Zack Arnett
21644ec889 Light More Info: Convert to Lit Element (#6592)
* Update more info

* Remove is unavailable

* Remove divs

* updates

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

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

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-17 09:07:29 -05:00
Joakim Sørensen
613470b44d Set SameSite=Strict attribute for ingress_session (#6556) 2020-08-17 15:16:28 +02:00
David F. Mulcahey
6c918e346b Fix navigation after ZHA device removal (#6638) 2020-08-17 14:17:18 +02:00
Joakim Sørensen
bce8539572 Add style to combobox (#6630) 2020-08-17 12:00:06 +02:00
HomeAssistant Azure
aab86e00ec [ci skip] Translation update 2020-08-17 00:32:14 +00:00
gibwar
2a58726caf Fix exceptional weither icon size (#6425) (#6634)
The weather icon for the `exceptional` state uses a different DOM layout
than the other icons, using a `.weather-icon` class that sets the size
to 64px (52px on narrow). This updates the `forecast-icon > *` class to
set the correct variable to match the expected `40px` and works on all
sizes from `veryverynarrow` to normal.
2020-08-16 17:51:50 -05:00
HomeAssistant Azure
4163b35b32 [ci skip] Translation update 2020-08-15 00:32:27 +00:00
HomeAssistant Azure
9c6dac8180 [ci skip] Translation update 2020-08-14 00:32:15 +00:00
HomeAssistant Azure
80fc37724b [ci skip] Translation update 2020-08-13 00:32:21 +00:00
Joakim Sørensen
77b25f5132 Merge pull request #6603 from home-assistant/supervisor-theme-legacy-backendselected 2020-08-12 15:40:48 +02:00
Joakim Sørensen
684f098450 Merge pull request #6597 from home-assistant/show-if-healthy 2020-08-12 15:40:18 +02:00
Ludeeus
d09f74d30f console.die 2020-08-12 13:15:39 +00:00
Ludeeus
3d973b112e Use default as fallback theme for older versions 2020-08-12 13:15:24 +00:00
Ludeeus
96986164a4 Show error if not supported 2020-08-11 14:12:38 +00:00
Joakim Sørensen
78152c20a9 Merge pull request #6596 from home-assistant/202008110 2020-08-11 14:49:12 +02:00
Ludeeus
2bb64e9e2f Use supported instead 2020-08-11 12:28:35 +00:00
Ludeeus
746844dfc8 Only show diagnostics if healthy 2020-08-11 12:15:08 +00:00
Joakim Sørensen
41b613a2d7 Fix wrapping for diagnostics row (#6595) 2020-08-11 14:01:20 +02:00
Ludeeus
3b3aeea224 Bumped version to 20200811.0 2020-08-11 12:00:48 +00:00
Joakim Sørensen
71c592a0ce Use primary-background-color when it exists (#6594) 2020-08-11 12:00:36 +00:00
Joakim Sørensen
15193fcf5f Set header and tab color (#6582) 2020-08-11 12:00:14 +00:00
Joakim Sørensen
a31f53395f Set min width (#6583) 2020-08-11 11:59:27 +00:00
Ludeeus
283b134d84 Bumped version to 20200811.0 2020-08-11 11:57:03 +00:00
Joakim Sørensen
271eb614cd Use primary-background-color when it exists (#6594) 2020-08-11 13:22:46 +02:00
HomeAssistant Azure
16167bef07 [ci skip] Translation update 2020-08-11 00:32:11 +00:00
Joakim Sørensen
1eac9fa1cd Set header and tab color (#6582) 2020-08-10 16:42:55 +02:00
Joakim Sørensen
7f819f0020 Set min width (#6583) 2020-08-10 16:23:14 +02:00
Bram Kragten
dec1f99a5f Fix hassio panel dark mode (#6569) 2020-08-10 09:36:01 +02:00
HomeAssistant Azure
c705e74fc8 [ci skip] Translation update 2020-08-10 00:32:35 +00:00
HomeAssistant Azure
01df10f93e [ci skip] Translation update 2020-08-09 00:32:33 +00:00
HomeAssistant Azure
9877f08cf4 [ci skip] Translation update 2020-08-08 00:32:34 +00:00
Joakim Sørensen
3dc4b1d775 Merge to master for 20200807.1 (#6566)
* Reorder to not break jinja templates (#6564)

* Bumped version to 20200807.1
2020-08-07 16:36:49 +02:00
Ludeeus
02791c51ae Bumped version to 20200807.1 2020-08-07 14:00:10 +00:00
Joakim Sørensen
49683326e6 Reorder to not break jinja templates (#6564) 2020-08-07 15:59:22 +02:00
Joakim Sørensen
947773a82e Add diagnostics toggle (#6525)
* Add diagnostics toggle

* No need to check

* Expected blank line between class members

* Mimic the profile page

* Move settings-row to components and use button

* Update src/components/ha-settings-row.ts

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

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-07 08:47:25 -05:00
Zack Arnett
2a229df624 Change English/default naming of configure ui (#6555) 2020-08-07 08:42:38 -05:00
Bram Kragten
e605ad5e46 Merge pull request #6562 from home-assistant/dev
Merge dev to master for 20200807.0
2020-08-07 14:20:30 +02:00
Ludeeus
0d4f43472b Bumped version to 20200807.0 2020-08-07 10:47:19 +00:00
Bram Kragten
b30e467685 Fix z-index light and thermostat more info button (#6561) 2020-08-07 11:43:20 +02:00
HomeAssistant Azure
a56c0b52d5 [ci skip] Translation update 2020-08-07 00:32:18 +00:00
Bram Kragten
c17ebfd279 Show small header on ingress panels when the sidebar is hidden (#6488) 2020-08-06 23:42:10 +02:00
Joakim Sørensen
5400b1da96 Fixes display issues with longer dates (#6540)
* Fixes display issues with longer dates

* review

* Remove size
2020-08-06 19:42:27 +02:00
Joakim Sørensen
69f4a618b2 Fix color and overlap in close dialog header (#6539)
* Fix color and overlap in close dialog header

* Use haStyleDialog instead
2020-08-06 17:51:41 +02:00
Joakim Sørensen
16b8b6698c Fixes display issues with the new darkmode (#6532)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-08-06 17:51:14 +02:00
Joakim Sørensen
b29a700d40 Only show stage bage if not stable (#6546) 2020-08-06 17:50:46 +02:00
Bram Kragten
bbb1468439 Mark card editor dirty on prefilled create (#6545) 2020-08-06 15:18:08 +02:00
Joakim Sørensen
72f9d6a8d3 Open more info on top (#6543) 2020-08-06 14:28:53 +02:00
Joakim Sørensen
3ec8da1f17 Fix theme loading on earlier versions (#6521) 2020-08-06 14:04:22 +02:00
Joakim Sørensen
dbea3848df Remove !important from h2 (#6541) 2020-08-06 14:02:49 +02:00
HomeAssistant Azure
33871435e1 [ci skip] Translation update 2020-08-06 00:32:16 +00:00
Bram Kragten
f1f22b43dc Merge pull request #6524 from home-assistant/dev
20200805.0
2020-08-05 13:11:08 +02:00
Ludeeus
2fb9a56e0b Bumped version to 20200805.0 2020-08-05 10:49:41 +00:00
Joakim Sørensen
14e8f66ed7 Fix allignment in integration badge during onboarding (#6523) 2020-08-05 12:20:32 +02:00
HomeAssistant Azure
e6f5072462 [ci skip] Translation update 2020-08-05 00:32:16 +00:00
Bram Kragten
a64f50fa72 Bump mwc to 0.18 (#6517) 2020-08-04 20:52:05 +02:00
Bram Kragten
bb5f6e88d0 Close entity registry dialog when navigation away (#6511) 2020-08-04 11:13:55 +02:00
Bram Kragten
6991403203 Fix location editor in onboarding (#6512) 2020-08-03 23:26:41 +02:00
Bram Kragten
410bd22f8a Punycode client id on auth page (#6513) 2020-08-03 22:44:20 +02:00
Charles Garwood
b81d823602 Add Z-Wave device info to OZW device pages (#6508)
* Add basic device info to devices page for OZW devices

* Remove unused HassEntity

* connection -> identifier

* async fetch

* Cleanup fetch call
2020-08-03 18:53:41 +02:00
Bram Kragten
bd5115f9aa Merge pull request #6510 from home-assistant/dev 2020-08-03 16:48:29 +02:00
Bram Kragten
7bcbed80d7 Bumped version to 20200803.0 2020-08-03 16:35:40 +02:00
Bram Kragten
8fb62ebf5f Fix gauge when safari is zoomed (#6492)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-08-03 16:32:23 +02:00
Bram Kragten
209dd9923f Allow to select a different dashboard when adding entities / moving cards (#6478) 2020-08-03 16:29:26 +02:00
Charles Garwood
c75207e391 Add identifiers to DeviceRegistryEntry (#6507) 2020-08-03 15:35:52 +02:00
HomeAssistant Azure
d957f36927 [ci skip] Translation update 2020-08-03 00:33:23 +00:00
Bram Kragten
9ac459b6d9 Update lint rules (#6490) 2020-08-03 02:11:28 +02:00
Bram Kragten
e08b2817ba Move view edit dialog to mwc (#6479) 2020-08-03 02:08:05 +02:00
Bram Kragten
4ca13c409b Introduce dark mode and primary color picker (#6430)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-08-03 02:07:12 +02:00
Bram Kragten
0d515e2303 Replace createValidEntityId with slugify (#6505) 2020-08-03 02:06:08 +02:00
Bram Kragten
a2153bc6aa Add UI for new script functions (#6491) 2020-08-02 20:56:26 +02:00
HomeAssistant Azure
ca171afe6f [ci skip] Translation update 2020-08-02 00:33:15 +00:00
HomeAssistant Azure
bf4e97bd48 [ci skip] Translation update 2020-08-01 00:33:31 +00:00
dependabot[bot]
8c59a12a03 Bump elliptic from 6.4.1 to 6.5.3 (#6502)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.4.1 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.4.1...v6.5.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-31 20:54:52 +02:00
Bram Kragten
89569355be Don't add undefined values to extra data in device automations (#6499) 2020-07-31 19:07:11 +02:00
MeIchthys
3a41b3bdcf standardize config menu descriptions (#6495) 2020-07-31 11:27:38 +02:00
HomeAssistant Azure
12bd7037b3 [ci skip] Translation update 2020-07-31 00:33:23 +00:00
Bram Kragten
ca4f573be0 Add support for safe area insets (#6473) 2020-07-30 18:27:27 +02:00
Andrey Kupreychik
07fceeab5a Using entity_picture_local for entity card and badge (#6489)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-07-30 16:33:08 +02:00
HomeAssistant Azure
3aa376e912 [ci skip] Translation update 2020-07-30 00:34:00 +00:00
Michael Irigoyen
92d30a8896 Update Material Design Icons to v5.4.55 (#6485) 2020-07-29 16:19:45 +02:00
Bram Kragten
83876fb9da Nullish coalescing entity card (#6484)
Fixes #6483
2020-07-29 08:40:28 -05:00
HomeAssistant Azure
29bdf7877c [ci skip] Translation update 2020-07-29 00:32:28 +00:00
Bram Kragten
29199e2782 Fix scroll to top dev tools (#6455)
Fixes https://github.com/home-assistant/frontend/issues/6448
2020-07-28 11:16:47 +02:00
Bram Kragten
68e1378615 Fix automation/scripts dirty on start edit (#6474) 2020-07-28 11:12:58 +02:00
HomeAssistant Azure
cf7efb5bfc [ci skip] Translation update 2020-07-28 00:32:36 +00:00
HomeAssistant Azure
8634ee536d [ci skip] Translation update 2020-07-27 00:32:48 +00:00
HomeAssistant Azure
632d3cda24 [ci skip] Translation update 2020-07-26 00:32:40 +00:00
HomeAssistant Azure
29b6a907d4 [ci skip] Translation update 2020-07-25 00:32:25 +00:00
HomeAssistant Azure
7474d09e5d [ci skip] Translation update 2020-07-24 00:32:32 +00:00
HomeAssistant Azure
fc7bcd7e00 [ci skip] Translation update 2020-07-23 00:32:33 +00:00
Bram Kragten
f6fb2e4b1d Missed the entities in the editors (#6443) 2020-07-22 14:16:25 +02:00
HomeAssistant Azure
8c8673a272 [ci skip] Translation update 2020-07-22 00:32:23 +00:00
Bram Kragten
4404a1173b Fix mwc-list/menu actions (#6442)
* Fix mwc-list/menu actions

Fix double actions when using request-selected

* Update ha-button-menu.ts

* Automation menu styling

* Update src/panels/lovelace/hui-root.ts

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>

* Move

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
2020-07-21 23:22:19 +02:00
Bram Kragten
e08c10315e Fix removing/moving device actions (#6441)
Fixes https://github.com/home-assistant/frontend/issues/6438
2020-07-21 09:05:21 -07:00
Bram Kragten
16473c9177 Bump superstruct, add struct to automation action (#6436)
Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
2020-07-21 12:42:07 +02:00
HomeAssistant Azure
235fd5603f [ci skip] Translation update 2020-07-21 00:32:32 +00:00
Quinn Casey
d07d5832f5 Increase z-index of save button on editor (#6435) 2020-07-20 18:46:10 +02:00
Yosi Levy
ef8be5d559 Merge pull request #6433 from yosilevy/edit-card-RTL-fix
Edit card rtl fix
2020-07-20 14:44:05 +03:00
Yosi Levy
ccafdc6e1f Merge pull request #6431 from yosilevy/Unused-entities-search
ha-data-table search label + no data text label
2020-07-20 14:31:41 +03:00
Yosi Levy
11827aa4c0 Merge pull request #6426 from yosilevy/Logbook-link-fix
Logbook link fix
2020-07-20 14:31:21 +03:00
Yosi Levy
6b0589d343 Update lint 2020-07-20 14:30:32 +03:00
Yosi Levy
cec1eed99e Merge branch 'dev' into edit-card-RTL-fix 2020-07-20 14:01:20 +03:00
Yosi Levy
d7e1e9e284 Merge pull request #6417 from yosilevy/RTL-date-selector
RTL fix for range list
2020-07-20 13:57:27 +03:00
Yosi Levy
cae46453a7 New parameterized label 2020-07-20 13:56:39 +03:00
Yosi Levy
a6e948c808 Typeo 2020-07-20 13:41:20 +03:00
Yosi Levy
7638020bfc Changed default wording 2020-07-20 13:37:18 +03:00
Yosi Levy
10a62ca17c Fix bad merge 2020-07-20 13:29:51 +03:00
Yosi Levy
0afc7c184f Fixed comments 2020-07-20 13:25:34 +03:00
Yosi Levy
168e26aeb4 Merge branch 'dev' into RTL-date-selector 2020-07-20 13:12:53 +03:00
Yosi Levy
e6b9389b33 Fixed heading (concat) to support RTL (name comes last) 2020-07-20 07:49:09 +03:00
Yosi Levy
377c37425e Refactor ha-dialog RTL to include dialogs not using createCloseHeader 2020-07-20 07:32:09 +03:00
HomeAssistant Azure
4af26602bb [ci skip] Translation update 2020-07-20 00:32:37 +00:00
Yosi Levy
c6624e5cb6 Optimized RTL check 2020-07-19 15:09:34 +03:00
Yosi Levy
f7ae5b91bf Remove rtl update check 2020-07-19 14:17:09 +03:00
Yosi Levy
07e68496c0 Removed directive 2020-07-19 06:14:09 +03:00
Yosi Levy
d5a947e2cc Removed style 2020-07-19 06:09:57 +03:00
Yosi Levy
3f920767f1 Added noDataText 2020-07-19 06:04:42 +03:00
Yosi Levy
3e14d27a1e Usage of search label 2020-07-19 05:58:01 +03:00
Yosi Levy
cfa4c14108 Added search label support to ha-data-table 2020-07-19 05:56:39 +03:00
HomeAssistant Azure
209056dbe1 [ci skip] Translation update 2020-07-19 00:32:25 +00:00
Aidan Timson
10356a7496 Fix typo in ZHA (#6429) 2020-07-18 19:27:46 +02:00
Yosi Levy
d4ae74de44 Removed style 2020-07-18 11:06:43 +03:00
Yosi Levy
88d5e7dd5e Line breaks 2020-07-18 11:02:13 +03:00
Yosi Levy
06c7b0b82e Optimized 2020-07-18 10:59:48 +03:00
Yosi Levy
689febda60 Fixed comments 2020-07-18 06:48:36 +03:00
Yosi Levy
80bc6fda8b Improved 2020-07-18 06:31:33 +03:00
Yosi Levy
346eb78c4e Fixed extra space issue + RTL support when no entries 2020-07-18 06:29:21 +03:00
HomeAssistant Azure
2df02f1b09 [ci skip] Translation update 2020-07-18 00:32:26 +00:00
Bram Kragten
92915eddc2 Make menu's work with keyboard (#6421) 2020-07-17 20:31:44 +02:00
Bram Kragten
cddbf460f8 Add close function to edit card dialog (#6423) 2020-07-17 11:29:08 -07:00
Bram Kragten
3c63c23e5a Fix spacing more info (#6419) 2020-07-17 12:04:20 +02:00
Yosi Levy
ba67b1291f Merge pull request #6404 from yosilevy/RTL-dev-changes
Removed LTR force - looks much better
2020-07-17 07:00:07 +03:00
Yosi Levy
7bced28327 RTL fix for range list 2020-07-17 06:57:42 +03:00
HomeAssistant Azure
db2b60700c [ci skip] Translation update 2020-07-17 00:32:26 +00:00
Bram Kragten
9034822c44 Bump lit-analyzer and add back cast types (#6409) 2020-07-16 12:01:24 -07:00
Bram Kragten
e8254f9aae Merge pull request #6411 from home-assistant/dev 2020-07-16 18:38:27 +02:00
Bram Kragten
a14179b81a Bumped version to 20200716.0 2020-07-16 17:51:47 +02:00
Bram Kragten
427c5db7f4 default 0 2020-07-16 17:51:01 +02:00
Bram Kragten
fcb5865468 Make gauge bit smaller 2020-07-16 17:49:35 +02:00
Bram Kragten
41370be2b8 Rewrite gauge (#6407) 2020-07-16 08:42:14 -07:00
Bram Kragten
d7d8dd8986 Debug was still true (#6410) 2020-07-16 17:18:55 +02:00
Bram Kragten
a0f596e419 Missing icon change (#6406) 2020-07-16 09:47:41 +02:00
Paulus Schoutsen
0a8894feb7 Random cleanups (#6402) 2020-07-16 08:24:16 +02:00
Bram Kragten
1db9eea0f8 Add visual-studio-code to icon conversion (#6401)
Fixes https://github.com/home-assistant/frontend/issues/6400
2020-07-16 08:22:22 +02:00
Sean Mooney
489783c398 Fix typo in icon rename mapping (#6405)
After updating all my MDI icons I noticed that one wasn't displaying anymore. Then realized there was a minor typo.
2020-07-16 08:21:32 +02:00
Yosi Levy
be62f327ee Removed LTR force - looks much better 2020-07-16 05:32:49 +03:00
HomeAssistant Azure
32359adb6d [ci skip] Translation update 2020-07-16 00:32:14 +00:00
Bram Kragten
2e198af8c3 Merge pull request #6399 from home-assistant/dev 2020-07-15 20:19:11 +02:00
Bram Kragten
d154fcbd71 Bumped version to 20200715.1 2020-07-15 19:56:56 +02:00
Bram Kragten
21e277b8a2 Change default automation mode (#6398) 2020-07-15 10:56:15 -07:00
Jaroslav Hanslík
f98cdd0749 Added missing translation to alarm panel card (#6390)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-07-15 19:42:06 +02:00
Mike Knudson
e60e306426 Update icon_color_css.ts (#6385)
* Update icon_color_css.ts

Adding the "group" data-domain to CSS check for active coloring.  

This fixes ha-icon to change to the active color based on an "on" state.

* Update icon_color_css.ts

Adding, home, open, locked, and problem to the list.
2020-07-15 19:31:05 +02:00
Bram Kragten
ec36d396d9 Merge pull request #6396 from home-assistant/dev 2020-07-15 16:23:49 +02:00
Bram Kragten
135232d880 Bumped version to 20200715.0 2020-07-15 15:40:55 +02:00
Bram Kragten
9c42ca0315 Fix filtering attributes (#6394) 2020-07-15 15:34:18 +02:00
Bram Kragten
9ad9c569a6 Fix button position entity settings dialog (#6395) 2020-07-15 15:33:55 +02:00
Bram Kragten
a9071d7920 Fix long entity name streching header (#6393) 2020-07-15 15:26:01 +02:00
Bram Kragten
1b4a10fac1 Merge pull request #6392 from home-assistant/fix-gauge-text-color
Fix text color in gauge
2020-07-15 15:21:19 +02:00
Bram Kragten
d340f3b383 Fix text color in gauge 2020-07-15 11:31:48 +02:00
Bram Kragten
f8c5eeab5d Replace all private properties with internalProperty decorator (#6386) 2020-07-14 21:38:36 -07:00
HomeAssistant Azure
9cd2d0df93 [ci skip] Translation update 2020-07-15 00:32:18 +00:00
Bram Kragten
78914091b1 Merge pull request #6389 from home-assistant/dev 2020-07-14 23:53:51 +02:00
Bram Kragten
e12c324613 Merge branch 'master' into dev 2020-07-14 23:37:43 +02:00
Bram Kragten
7cf396b518 Bumped version to 20200714.0 2020-07-14 23:36:14 +02:00
Ian Richardson
8b3b40e627 Button Card: Option to show state (#6383) 2020-07-14 23:26:21 +02:00
Bram Kragten
90e14762e3 Fix double toolbars on error/loading in dev tools (#6380) 2020-07-14 21:58:40 +02:00
Bram Kragten
d1dd8231cd Allow to set an id and icon when creating new script (#6373)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-07-14 21:58:24 +02:00
Bram Kragten
e70a3e09bf Change UI of script entities (#6371) 2020-07-14 10:48:11 -07:00
Bram Kragten
98656b0044 Little clean up (#6377) 2020-07-14 09:20:15 -07:00
Bram Kragten
a48aa3c778 Remove weblink and history_graph integrations (#6378)
Closes #4941
2020-07-14 09:19:42 -07:00
Bram Kragten
05d7b98ba0 Add icon to scenes (#6379) 2020-07-14 09:19:08 -07:00
Bram Kragten
f291ea6647 Add link to related area (#6381) 2020-07-14 09:14:26 -07:00
Bram Kragten
5d6e332044 Add more spacing around restored message more info (#6382) 2020-07-14 09:13:24 -07:00
Bram Kragten
acb471fbe5 Replace round slider gauge with svg-gauge (#6384) 2020-07-14 09:13:04 -07:00
HomeAssistant Azure
894f4379e6 [ci skip] Translation update 2020-07-14 00:32:22 +00:00
Bram Kragten
1c73007ae6 Entity dialogs: Remove paper element and align dialog header style (#6370)
* Entity dialogs: Remove paper element and align dialog header style

* Remove centerTitle
2020-07-13 19:37:48 +02:00
Paulus Schoutsen
2f7d744228 Rename whitelist error to allowed (#6372) 2020-07-13 10:19:29 -07:00
Bram Kragten
e2cba90f8d Fix entity filter menu on mobile (#6366)
Fixes #6364
2020-07-13 16:18:23 +02:00
Bram Kragten
352214ba0a Add UI for the script and automation modes (#6367) 2020-07-13 10:52:37 +02:00
HomeAssistant Azure
bd9b72fb22 [ci skip] Translation update 2020-07-13 00:32:42 +00:00
Bram Kragten
50c9a667b3 Fix graph mouse over in more info dialog (#6365) 2020-07-12 16:37:06 -07:00
Bram Kragten
3d32e6310d Close dialogs on history back (#6354) 2020-07-12 22:19:26 +02:00
J. Nick Koston
3bc54aa9e0 Show battery charging state in the config panels (#6356) 2020-07-12 21:30:27 +02:00
HomeAssistant Azure
def1ec3518 [ci skip] Translation update 2020-07-12 00:32:40 +00:00
Yosi Levy
077802f972 Added missing label to translations (#6362) 2020-07-11 22:17:14 +02:00
Bram Kragten
914b47f340 Bump MDI and add warning for removed and renamed icons (#6357) 2020-07-11 21:30:02 +02:00
Yosi Levy
b2a78fd063 Added forceLTR attribute on a column in ha-data-table (#6363) 2020-07-11 18:11:13 +02:00
Yosi Levy
7d1835e59c Localization updates (#6359) 2020-07-11 18:07:22 +02:00
Yosi Levy
833ccf3637 Card picker improvements (#6361) 2020-07-11 18:06:39 +02:00
HomeAssistant Azure
51be916f39 [ci skip] Translation update 2020-07-11 00:32:28 +00:00
Rohan Kapoor
e375408777 Provide credentials to relative links only (#6360)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-07-10 17:29:29 -07:00
Yosi Levy
5078dc1cbf Fixed X placement (#6358) 2020-07-10 21:54:38 +02:00
Yosi Levy
875148366e Fixed label + RTL + X position (#6348) 2020-07-10 17:09:07 +02:00
Bram Kragten
c9ec4b4e24 Migrate entity settings dialog (#6349) 2020-07-10 10:16:48 +02:00
HomeAssistant Azure
efa2b2db27 [ci skip] Translation update 2020-07-10 00:32:45 +00:00
Jelle Raaijmakers
8ce120b74d Round values for relative time instead of flooring (#6225) 2020-07-09 11:19:59 -07:00
Yosi Levy
26e678a97d mwc-fab fix where missing (#6352) 2020-07-09 14:00:01 +02:00
Bram Kragten
e71dd7409e Scenes: Fix entity picked from device doesn't add device (#6343)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-07-09 13:48:28 +02:00
HomeAssistant Azure
58ffc2c6ca [ci skip] Translation update 2020-07-09 00:32:29 +00:00
Bram Kragten
d3f29362b9 Fix optional with default value ha-form-integer (#6341) 2020-07-08 16:29:46 -07:00
Bram Kragten
b429fe8254 Migrate more-info-dialog to mwc and Lit (#6345)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-07-08 11:38:07 +02:00
HomeAssistant Azure
e1cb549b28 [ci skip] Translation update 2020-07-08 00:32:20 +00:00
Yosi Levy
65a22257cc Merge pull request #6333 from yosilevy/RTL-changes
RTL fixes - cloud section, ha-formfield, some missing + (mwc-fab)
2020-07-07 17:17:36 +03:00
Bram Kragten
e2f753eaa7 Merge branch 'dev' into RTL-changes 2020-07-07 09:25:02 +02:00
Yosi Levy
c7127b65bf Rtl updates 2 (#6340) 2020-07-07 09:24:09 +02:00
Bram Kragten
0c58c3572a Add support for constant in ha-form (#6324)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-07-07 09:23:08 +02:00
HomeAssistant Azure
26ae5fd728 [ci skip] Translation update 2020-07-07 00:32:26 +00:00
Yosi Levy
370d92213b Fixed comments 2020-07-06 21:03:31 +03:00
Bram Kragten
6e8321a22a Bumped version to 20200702.1 2020-07-06 17:37:30 +02:00
Christopher Masto
a8e8c1ce5d Fix missing UI elements in Z-Wave panel (#6299) (#6336)
This broke in d94df728e5 and prevents
use of the group association and protection controls.
2020-07-06 17:37:06 +02:00
Paulus Schoutsen
a8a8cafd2b Fix logbook showing user names (#6327)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-07-06 17:36:50 +02:00
Yosi Levy
b609890f28 Update ha-formfield.ts 2020-07-06 17:29:18 +03:00
Paulus Schoutsen
aac09ae092 Fix logbook showing user names (#6327)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-07-06 12:57:15 +02:00
Christopher Masto
f1ff872944 Fix missing UI elements in Z-Wave panel (#6299) (#6336)
This broke in d94df728e5 and prevents
use of the group association and protection controls.
2020-07-06 09:39:54 +02:00
HomeAssistant Azure
b195d2980a [ci skip] Translation update 2020-07-06 00:32:23 +00:00
Rohan Kapoor
d11736181f Provide credentials (cookies) when loading Javascript modules (#6328)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-07-05 13:35:26 -07:00
Bram Kragten
3e84486dd5 Fix changed dialog class selector (#6325) 2020-07-05 13:35:03 -07:00
Bram Kragten
a674ce36e4 Don't show null in hassio network settings (#6323) 2020-07-05 17:03:46 +02:00
Yosi Levy
f6569a2625 Fix lint 2020-07-05 06:35:11 +03:00
Yosi Levy
da10da79b3 RTL fixes - cloud section, ha-formfield, some missing + (mwc-fab) 2020-07-05 06:23:35 +03:00
HomeAssistant Azure
f236b76d5c [ci skip] Translation update 2020-07-05 00:32:32 +00:00
HomeAssistant Azure
a71c22bedd [ci skip] Translation update 2020-07-04 00:32:24 +00:00
HomeAssistant Azure
cc528e41cf [ci skip] Translation update 2020-07-03 00:32:26 +00:00
Vladimír Záhradník
351962475f Custom Door controls (#6195) 2020-07-02 23:18:40 +02:00
Bram Kragten
6c73392a57 Bump mdc and mwc elements (#6313)
* Bump mdc to stable 7.0.0

* Bump mdc and mwc

* Update ha-config-logs.ts

* Correct changed radius variable
2020-07-02 23:18:11 +02:00
Bram Kragten
072ad87831 Prevent doing yaml conversion twice (#6308) 2020-07-02 20:09:58 +02:00
Bram Kragten
370a1f0574 Merge pull request #6318 from home-assistant/dev 2020-07-02 19:52:20 +02:00
Bram Kragten
9ca7aca4b7 Bumped version to 20200702.0 2020-07-02 19:31:14 +02:00
Bram Kragten
0f2e9f66b1 Add option to disable suspend connection when hidden (#6304)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-07-02 19:30:27 +02:00
Sean Mooney
d03c3ab713 Update "Info" location in bug report template (#6317)
Location of this was moved from Developer Tools -> Info to Configuration -> Info.

Initially I was thinking of listing both locations (pre-0.112 and post-0.112) but thought maybe it's better not to. Because if the user doesn't see it under Configuration panel, that sets off the lightbulb in their head that they're running an older version and will (hopefully) update to latest before submitting the bug report.
2020-07-02 19:12:04 +02:00
Bram Kragten
57c0b34ae9 Refresh card embedded yaml editors on dialog open (#6307) 2020-07-02 09:15:20 -07:00
Bram Kragten
06a94f0f28 Fix spinner position logbook (#6306) 2020-07-02 17:55:06 +02:00
Bram Kragten
750e7b1262 Fix week period selectors (#6303) 2020-07-02 17:38:03 +02:00
Joakim Sørensen
19e32752bb Fixes hassio build script for prod (#6315) 2020-07-02 17:37:08 +02:00
Bram Kragten
89c0729964 Show the header when the sidebar is hidden (#6305) 2020-07-02 04:20:35 -07:00
HomeAssistant Azure
fd07152aea [ci skip] Translation update 2020-07-02 00:32:32 +00:00
Bram Kragten
b656f189b6 The top of templates dev tools was hidden behind the header (#6297) 2020-07-01 14:15:48 -07:00
669 changed files with 36998 additions and 12724 deletions

View File

@@ -1,7 +1,7 @@
{
"extends": [
"plugin:@typescript-eslint/recommended",
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended",
"plugin:wc/recommended",
"plugin:lit/recommended",
"prettier",
@@ -45,16 +45,16 @@
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"no-var": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 0,
"no-bitwise": 2,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"no-console": 2,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,

View File

@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!--
Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the
Home Assistant frontend: Developer tools -> Info.
Home Assistant frontend: Configuration -> Info.
Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings.

60
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: "CodeQL"
on:
push:
branches: [dev, master]
pull_request:
# The branches below must be a subset of the branches above
branches: [dev]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -265,7 +265,10 @@ gulp.task("gen-index-gallery-prod", (done) => {
});
gulp.task("gen-index-hassio-dev", async () => {
writeHassioEntrypoint("entrypoint.js", "entrypoints.js");
writeHassioEntrypoint(
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
);
});
gulp.task("gen-index-hassio-prod", async () => {
@@ -289,10 +292,10 @@ function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
try {
new Function("import('${paths.hassio_publicPath}/frontend_latest/${latestEntrypoint}')")();
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${paths.hassio_publicPath}/frontend_es5/${es5Entrypoint}';
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
`,

View File

@@ -11,6 +11,7 @@ const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json");
const encoding = "utf8";
@@ -25,6 +26,13 @@ const getMeta = () => {
});
};
const addRemovedMeta = (meta) => {
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
const removed = JSON.parse(file);
const combinedMeta = [...meta, ...removed];
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
};
const splitBySize = (meta) => {
const chunks = [];
const CHUNK_SIZE = 50000;
@@ -69,7 +77,7 @@ const findDifferentiator = (curString, prevString) => {
};
gulp.task("gen-icons-json", (done) => {
const meta = getMeta();
const meta = addRemovedMeta(getMeta());
const split = splitBySize(meta);
if (!fs.existsSync(OUTPUT_DIR)) {

View File

@@ -0,0 +1,267 @@
[
{
"path": "M17.5,15.61C17.33,15.37 9.53,5.4 9.27,5.08C9,4.75 9.08,4.65 9.13,4.59C9.22,4.5 9.36,4.5 9.93,4.5C10.26,4.5 13.59,4.5 13.94,4.47C14.66,4.47 14.78,4.53 14.85,4.56C14.93,4.58 15.13,4.75 15.26,4.92C15.33,5 22.32,13.36 22.39,13.45C22.46,13.54 22.59,13.69 22.67,13.84C22.76,14 22.77,14.18 22.64,14.25C22.56,14.3 18.7,15.89 18.59,15.93C18.5,16 18.27,16.06 18.11,16.04C18,16 17.77,15.92 17.5,15.61M21.47,15.42L21.75,15.47C21.75,15.47 22.68,15.65 22.77,15.67C22.87,15.69 22.96,15.76 22.95,15.79C22.94,15.87 22.9,15.91 22.83,15.95C22.77,16 18.58,18.58 18.5,18.62C18.43,18.66 18.33,18.72 18.11,18.75C17.7,18.83 16.91,18.61 16.66,18.56C16.41,18.5 6.15,16.23 6.06,16.2C5.97,16.17 5.91,16.16 5.9,16.08C5.89,15.94 6.11,15.88 6.28,15.81C6.46,15.75 11.28,14 11.45,13.93C11.62,13.86 11.84,13.84 11.95,13.83C12.06,13.82 12.73,13.93 13.03,13.97C13.34,14 14.2,14.15 14.2,14.15L16.16,16.7C16.5,17.09 16.72,17.25 17,17.28C17.15,17.29 17.31,17.25 17.42,17.2C17.5,17.16 21.47,15.42 21.47,15.42M10.25,9.18L11.96,11.37L12,11.45V11.5C11.96,11.54 8.93,14.32 8.91,14.35L5.72,15.5C5.72,15.5 5.63,15.55 5.58,15.58C5.53,15.61 5.47,15.67 5.5,15.82C5.5,15.87 5.5,16.59 5.5,16.79L1.56,18.04C1.37,18.1 1,18.23 0.95,18.19C0.88,18.14 0.97,18.03 1,17.97C1.06,17.91 9.08,10 9.39,9.7C9.84,9.24 10.25,9.18 10.25,9.18",
"name": "accusoft"
},
{
"path": "M4.94,11.12C5.23,11.12 5.5,11.16 5.76,11.23C5.77,9.09 7.5,7.35 9.65,7.35C11.27,7.35 12.67,8.35 13.24,9.77C13.83,9 14.74,8.53 15.76,8.53C17.5,8.53 18.94,9.95 18.94,11.71C18.94,11.95 18.91,12.2 18.86,12.43C19.1,12.34 19.37,12.29 19.65,12.29C20.95,12.29 22,13.35 22,14.65C22,15.95 20.95,17 19.65,17C18.35,17 6.36,17 4.94,17C3.32,17 2,15.68 2,14.06C2,12.43 3.32,11.12 4.94,11.12Z",
"name": "amazon-drive"
},
{
"path": "M8,11.5A1.25,1.25 0 0,0 6.75,12.75A1.25,1.25 0 0,0 8,14A1.25,1.25 0 0,0 9.25,12.75A1.25,1.25 0 0,0 8,11.5M16,11.5A1.25,1.25 0 0,0 14.75,12.75A1.25,1.25 0 0,0 16,14A1.25,1.25 0 0,0 17.25,12.75A1.25,1.25 0 0,0 16,11.5M12,7C13.5,7 14.9,7.33 16.18,7.91L18.34,5.75C18.73,5.36 19.36,5.36 19.75,5.75C20.14,6.14 20.14,6.77 19.75,7.16L17.95,8.96C20.41,10.79 22,13.71 22,17H2C2,13.71 3.59,10.79 6.05,8.96L4.25,7.16C3.86,6.77 3.86,6.14 4.25,5.75C4.64,5.36 5.27,5.36 5.66,5.75L7.82,7.91C9.1,7.33 10.5,7 12,7Z",
"name": "android-head"
},
{
"path": "M2,16.25C2,16.25 4,3.75 12,3.75C20,3.75 22,16.25 22,16.25C22,16.25 20,20.25 12,20.25C4,20.25 2,16.25 2,16.25M3.35,15.65C3.35,15.65 4.3,19 12,19C17,19 20,17.8 20.65,15.85C21.3,13.9 15.65,7.6 14.65,7.6C13.65,7.6 11.2,12 10.45,12C8.45,12 8.9,10 7.15,10C5.4,10 3.35,15.65 3.35,15.65Z",
"name": "basecamp"
},
{
"path": "M7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7C10.87,7 9.84,7.37 9,8V2.46C9.95,2.16 10.95,2 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12C2,8.3 4,5.07 7,3.34V12M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z",
"name": "beats"
},
{
"path": "M19.58,12.27C19.54,11.65 19.33,11.18 18.96,10.86C18.59,10.54 18.13,10.38 17.58,10.38C17,10.38 16.5,10.55 16.19,10.89C15.86,11.23 15.65,11.69 15.57,12.27M21.92,12.04C22,12.45 22,13.04 22,13.81H15.5C15.55,14.71 15.85,15.33 16.44,15.69C16.79,15.92 17.22,16.03 17.73,16.03C18.26,16.03 18.69,15.89 19,15.62C19.2,15.47 19.36,15.27 19.5,15H21.88C21.82,15.54 21.53,16.07 21,16.62C20.22,17.5 19.1,17.92 17.66,17.92C16.47,17.92 15.43,17.55 14.5,16.82C13.62,16.09 13.16,14.9 13.16,13.25C13.16,11.7 13.57,10.5 14.39,9.7C15.21,8.87 16.27,8.46 17.58,8.46C18.35,8.46 19.05,8.6 19.67,8.88C20.29,9.16 20.81,9.59 21.21,10.2C21.58,10.73 21.81,11.34 21.92,12.04M9.58,14.07C9.58,13.42 9.31,12.97 8.79,12.73C8.5,12.6 8.08,12.53 7.54,12.5H4.87V15.84H7.5C8.04,15.84 8.46,15.77 8.76,15.62C9.31,15.35 9.58,14.83 9.58,14.07M4.87,10.46H7.5C8.04,10.46 8.5,10.36 8.82,10.15C9.16,9.95 9.32,9.58 9.32,9.06C9.32,8.5 9.1,8.1 8.66,7.91C8.27,7.78 7.78,7.72 7.19,7.72H4.87M11.72,12.42C12.04,12.92 12.2,13.53 12.2,14.24C12.2,15 12,15.64 11.65,16.23C11.41,16.62 11.12,16.94 10.77,17.21C10.37,17.5 9.9,17.72 9.36,17.83C8.82,17.94 8.24,18 7.61,18H2V5.55H8C9.53,5.58 10.6,6 11.23,6.88C11.61,7.41 11.8,8.04 11.8,8.78C11.8,9.54 11.61,10.15 11.23,10.61C11,10.87 10.7,11.11 10.28,11.32C10.91,11.55 11.39,11.92 11.72,12.42M20.06,7.32H15.05V6.07H20.06V7.32Z",
"name": "behance"
},
{
"path": "M5.45,10.28C6.4,10.28 7.5,11.05 7.5,12C7.5,12.95 6.4,13.72 5.45,13.72H2L2.69,10.28H5.45M6.14,4.76C7.09,4.76 8.21,5.53 8.21,6.5C8.21,7.43 7.09,8.21 6.14,8.21H2.69L3.38,4.76H6.14M13.03,4.76C14,4.76 15.1,5.53 15.1,6.5C15.1,7.43 14,8.21 13.03,8.21H9.41L10.1,4.76H13.03M12.34,10.28C13.3,10.28 14.41,11.05 14.41,12C14.41,12.95 13.3,13.72 12.34,13.72H8.72L9.41,10.28H12.34M10.97,15.79C11.92,15.79 13.03,16.57 13.03,17.5C13.03,18.47 11.92,19.24 10.97,19.24H7.5L8.21,15.79H10.97M18.55,13.72C19.5,13.72 20.62,14.5 20.62,15.45C20.62,16.4 19.5,17.17 18.55,17.17H15.1L15.79,13.72H18.55M19.93,8.21C20.88,8.21 22,9 22,9.93C22,10.88 20.88,11.66 19.93,11.66H16.5L17.17,8.21H19.93Z",
"name": "blackberry"
},
{
"path": "M12,3A9,9 0 0,1 21,12A9,9 0 0,1 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3M5.94,8.5C4,11.85 5.15,16.13 8.5,18.06C11.85,20 18.85,7.87 15.5,5.94C12.15,4 7.87,5.15 5.94,8.5Z",
"name": "cisco-webex"
},
{
"path": "M11.9,14.5H10.8V9.5H11.9C13.5,9.5 14.6,10.4 14.6,12C14.6,13.6 13.5,14.5 11.9,14.5M11.9,7H8.1V17H11.8C15.3,17 17.4,14.9 17.4,12V12C17.4,9.1 15.4,7 11.9,7M12,20C10.1,20 8.3,19.3 6.9,18.1L6.2,17.5L4.5,17.7L5.2,16.1L4.9,15.3C4.4,14.2 4.2,13.1 4.2,11.9C4.2,7.5 7.8,3.9 12.1,3.9C16.4,3.9 19.9,7.6 19.9,12C19.9,16.4 16.3,20 12,20M12,2C6.5,2 2.1,6.5 2.1,12C2.1,13.5 2.4,14.9 3,16.2L1.4,20.3L5.7,19.7C7.4,21.2 9.7,22.1 12.1,22.1C17.6,22.1 22,17.6 22,12.1C22,6.6 17.5,2 12,2Z",
"name": "disqus-outline"
},
{
"path": "M16.42,18.42C16,16.5 15.5,14.73 15,13.17C15.5,13.1 16,13.06 16.58,13.06H16.6V13.06H16.6C17.53,13.06 18.55,13.18 19.66,13.43C19.28,15.5 18.08,17.27 16.42,18.42M12,19.8C10.26,19.8 8.66,19.23 7.36,18.26C7.64,17.81 8.23,16.94 9.18,16.04C10.14,15.11 11.5,14.15 13.23,13.58C13.82,15.25 14.36,17.15 14.77,19.29C13.91,19.62 13,19.8 12,19.8M4.2,12C4.2,11.96 4.2,11.93 4.2,11.89C4.42,11.9 4.71,11.9 5.05,11.9H5.06C6.62,11.89 9.36,11.76 12.14,10.89C12.29,11.22 12.44,11.56 12.59,11.92C10.73,12.54 9.27,13.53 8.19,14.5C7.16,15.46 6.45,16.39 6.04,17C4.9,15.66 4.2,13.91 4.2,12M8.55,5C9.1,5.65 10.18,7.06 11.34,9.25C9,9.96 6.61,10.12 5.18,10.12C5.14,10.12 5.1,10.12 5.06,10.12H5.05C4.81,10.12 4.6,10.12 4.43,10.11C5,7.87 6.5,6 8.55,5M12,4.2C13.84,4.2 15.53,4.84 16.86,5.91C15.84,7.14 14.5,8 13.03,8.65C12,6.67 11,5.25 10.34,4.38C10.88,4.27 11.43,4.2 12,4.2M18.13,7.18C19.1,8.42 19.71,9.96 19.79,11.63C18.66,11.39 17.6,11.28 16.6,11.28V11.28H16.59C15.79,11.28 15.04,11.35 14.33,11.47C14.16,11.05 14,10.65 13.81,10.26C15.39,9.57 16.9,8.58 18.13,7.18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z",
"name": "dribbble"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M15.09,16.5C14.81,15.14 14.47,13.91 14.08,12.82L15.2,12.74H15.22V12.74C15.87,12.74 16.59,12.82 17.36,13C17.09,14.44 16.26,15.69 15.09,16.5M12,17.46C10.79,17.46 9.66,17.06 8.76,16.39C8.95,16.07 9.36,15.46 10,14.83C10.7,14.18 11.64,13.5 12.86,13.11C13.28,14.27 13.65,15.6 13.94,17.1C13.33,17.33 12.68,17.46 12,17.46M6.54,12V11.92L7.14,11.93V11.93C8.24,11.93 10.15,11.83 12.1,11.22L12.41,11.94C11.11,12.38 10.09,13.07 9.34,13.76C8.61,14.42 8.12,15.08 7.83,15.5C7.03,14.56 6.54,13.34 6.54,12M9.59,7.11C9.97,7.56 10.73,8.54 11.54,10.08C9.89,10.57 8.23,10.68 7.22,10.68H7.14V10.68H6.7C7.09,9.11 8.17,7.81 9.59,7.11M12,6.54C13.29,6.54 14.47,7 15.41,7.74C14.69,8.6 13.74,9.22 12.72,9.66C12,8.27 11.31,7.28 10.84,6.67C11.21,6.59 11.6,6.54 12,6.54M16.29,8.63C16.97,9.5 17.4,10.57 17.45,11.74C16.66,11.58 15.92,11.5 15.22,11.5V11.5C14.66,11.5 14.13,11.54 13.63,11.63L13.27,10.78C14.37,10.3 15.43,9.61 16.29,8.63M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z",
"name": "dribbble-box"
},
{
"path": "M6.72,20.78C8.23,20.71 10.07,20.78 11.87,20.78C13.72,20.78 15.62,20.66 17.12,20.78C17.72,20.83 18.28,21.19 18.77,20.87C19.16,20.38 18.87,19.71 18.96,19.05C19.12,17.78 20.28,16.27 18.59,15.95C17.87,16.61 18.35,17.23 17.95,18.05C17.45,19.03 15.68,19.37 14,19.5C12.54,19.62 10,19.76 9.5,18.77C9.04,17.94 9.29,16.65 9.29,15.58C9.29,14.38 9.16,13.22 9.5,12.3C11.32,12.43 13.7,11.69 15,12.5C15.87,13 15.37,14.06 16.38,14.4C17.07,14.21 16.7,13.32 16.66,12.5C16.63,11.94 16.63,11.19 16.66,10.57C16.69,9.73 17,8.76 16.1,8.74C15.39,9.3 15.93,10.23 15.18,10.75C14.95,10.92 14.43,11 14.08,11C12.7,11.17 10.54,11.05 9.38,10.84C9.23,9.16 9.24,6.87 9.38,5.19C10,4.57 11.45,4.54 12.42,4.55C14.13,4.55 16.79,4.7 17.3,5.55C17.58,6 17.36,7 17.85,7.1C18.85,7.33 18.36,5.55 18.41,4.73C18.44,4.11 18.71,3.72 18.59,3.27C18.27,2.83 17.79,3.05 17.5,3.09C14.35,3.5 9.6,3.27 6.26,3.27C5.86,3.27 5.16,3.07 4.88,3.54C4.68,4.6 6.12,4.16 6.62,4.73C6.79,4.91 7.03,5.73 7.08,6.28C7.23,7.74 7.08,9.97 7.08,12.12C7.08,14.38 7.26,16.67 7.08,18.05C7,18.53 6.73,19.3 6.62,19.41C6,20.04 4.34,19.35 4.5,20.69C5.09,21.08 5.93,20.82 6.72,20.78Z",
"name": "etsy"
},
{
"path": "M12,17.5C10.15,17.5 8.42,16.56 7.41,15L17.41,12.75L22.08,11.75C22.05,10.32 21.71,8.92 21.08,7.64C18.66,2.61 12.62,0.5 7.58,2.92C2.55,5.34 0.44,11.38 2.86,16.41C5.29,21.44 11.33,23.56 16.36,21.14C18.5,20.09 20.25,18.31 21.22,16.11L16.61,15C15.6,16.57 13.86,17.5 12,17.5M12,6.5C13.76,6.5 15.41,7.34 16.44,8.77L6.57,11.19C6.96,8.5 9.28,6.5 12,6.5Z",
"name": "eventbrite"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M18,5H15.5A3.5,3.5 0 0,0 12,8.5V11H10V14H12V21H15V14H18V11H15V9A1,1 0 0,1 16,8H18V5Z",
"name": "facebook-box"
},
{
"path": "M21,12A9,9 0 0,1 12,21H4.5L9.74,15.76L11.16,17.17L9.33,19H12A7,7 0 0,0 19,12V7L21,5V12M3,12A9,9 0 0,1 12,3H19.5L14.26,8.24L12.84,6.83L14.67,5H12A7,7 0 0,0 5,12V17L3,19V12Z",
"name": "flattr"
},
{
"path": "M11,12C11,14.5 9,16.5 6.5,16.5C4,16.5 2,14.5 2,12C2,9.5 4,7.5 6.5,7.5C9,7.5 11,9.5 11,12M17.5,7.5C15,7.5 13,9.5 13,12C13,14.5 15,16.5 17.5,16.5C20,16.5 22,14.5 22,12C22,9.5 20,7.5 17.5,7.5Z",
"name": "flickr"
},
{
"path": "M17,5L16.57,7.5C16.5,7.73 16.2,8 15.91,8C15.61,8 12,8 12,8C11.53,8 10.95,8.32 10.95,8.79V9.2C10.95,9.67 11.53,10 12,10C12,10 14.95,10 15.28,10C15.61,10 15.93,10.36 15.86,10.71C15.79,11.07 14.94,13.28 14.9,13.5C14.86,13.67 14.64,14 14.25,14C13.92,14 11.37,14 11.37,14C10.85,14 10.69,14.07 10.34,14.5C10,14.94 7.27,18.1 7.27,18.1C7.24,18.13 7,18.04 7,18V5C7,4.7 7.61,4 8,4C8,4 16.17,4 16.5,4C16.82,4 17.08,4.61 17,5M17,14.45C17.11,13.97 18.78,6.72 19.22,4.55M17.58,2C17.58,2 8.38,2 6.91,2C5.43,2 5,3.11 5,3.8C5,4.5 5,20.76 5,20.76C5,21.54 5.42,21.84 5.66,21.93C5.9,22.03 6.55,22.11 6.94,21.66C6.94,21.66 11.65,16.22 11.74,16.13C11.87,16 11.87,16 12,16C12.26,16 14.2,16 15.26,16C16.63,16 16.85,15 17,14.45C17.11,13.97 18.78,6.72 19.22,4.55C19.56,2.89 19.14,2 17.58,2Z",
"name": "foursquare"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H14.56C14.24,20.93 14.23,20.32 14.23,20.11L14.24,17.64C14.24,16.8 13.95,16.25 13.63,15.97C15.64,15.75 17.74,15 17.74,11.53C17.74,10.55 17.39,9.74 16.82,9.11C16.91,8.89 17.22,7.97 16.73,6.73C16.73,6.73 15.97,6.5 14.25,7.66C13.53,7.46 12.77,7.36 12,7.35C11.24,7.36 10.46,7.46 9.75,7.66C8.03,6.5 7.27,6.73 7.27,6.73C6.78,7.97 7.09,8.89 7.18,9.11C6.61,9.74 6.26,10.55 6.26,11.53C6.26,15 8.36,15.75 10.36,16C10.1,16.2 9.87,16.6 9.79,17.18C9.27,17.41 7.97,17.81 7.17,16.43C7.17,16.43 6.69,15.57 5.79,15.5C5.79,15.5 4.91,15.5 5.73,16.05C5.73,16.05 6.32,16.33 6.73,17.37C6.73,17.37 7.25,19.12 9.76,18.58L9.77,20.11C9.77,20.32 9.75,20.93 9.43,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z",
"name": "github-box"
},
{
"path": "M20.38,8.53C20.54,8.13 21.06,6.54 20.21,4.39C20.21,4.39 18.9,4 15.91,6C14.66,5.67 13.33,5.62 12,5.62C10.68,5.62 9.34,5.67 8.09,6C5.1,3.97 3.79,4.39 3.79,4.39C2.94,6.54 3.46,8.13 3.63,8.53C2.61,9.62 2,11 2,12.72C2,19.16 6.16,20.61 12,20.61C17.79,20.61 22,19.16 22,12.72C22,11 21.39,9.62 20.38,8.53M12,19.38C7.88,19.38 4.53,19.19 4.53,15.19C4.53,14.24 5,13.34 5.8,12.61C7.14,11.38 9.43,12.03 12,12.03C14.59,12.03 16.85,11.38 18.2,12.61C19,13.34 19.5,14.23 19.5,15.19C19.5,19.18 16.13,19.38 12,19.38M8.86,13.12C8.04,13.12 7.36,14.12 7.36,15.34C7.36,16.57 8.04,17.58 8.86,17.58C9.69,17.58 10.36,16.58 10.36,15.34C10.36,14.11 9.69,13.12 8.86,13.12M15.14,13.12C14.31,13.12 13.64,14.11 13.64,15.34C13.64,16.58 14.31,17.58 15.14,17.58C15.96,17.58 16.64,16.58 16.64,15.34C16.64,14.11 16,13.12 15.14,13.12Z",
"name": "github-face"
},
{
"path": "M8,2A3,3 0 0,0 5,5V16.5H8V5H19A3,3 0 0,0 16,2H8M16,7.5V19H5A3,3 0 0,0 8,22H16A3,3 0 0,0 19,19V7.5H16Z",
"name": "glassdoor"
},
{
"path": "M2,22L8.5,2H15.4L9.2,20C9.2,20 8.6,22 7,22C5.9,22 2,22 2,22M16.4,5L13,15L15,20.7C15,20.7 15.4,22 17,22C18.3,22 22,22 22,22L16.4,5Z",
"name": "google-adwords"
},
{
"path": "M19,3H13V8L17,7L16,11H21V5C21,3.89 20.1,3 19,3M17,17L13,16V21H19A2,2 0 0,0 21,19V13H16M8,13H3V19A2,2 0 0,0 5,21H11V16L7,17M3,5V11H8L7,7L11,8V3H5C3.89,3 3,3.89 3,5Z",
"name": "google-pages"
},
{
"path": "M12,1.5A9,9 0 0,1 21,10.5C21,13.11 19.89,15.47 18.11,17.11L17.05,16.05C18.55,14.68 19.5,12.7 19.5,10.5A7.5,7.5 0 0,0 12,3A7.5,7.5 0 0,0 4.5,10.5C4.5,12.7 5.45,14.68 6.95,16.05L5.89,17.11C4.11,15.47 3,13.11 3,10.5A9,9 0 0,1 12,1.5M12,4.5A6,6 0 0,1 18,10.5C18,12.28 17.22,13.89 16,15L14.92,13.92C15.89,13.1 16.5,11.87 16.5,10.5C16.5,8 14.5,6 12,6C9.5,6 7.5,8 7.5,10.5C7.5,11.87 8.11,13.1 9.08,13.92L8,15C6.78,13.89 6,12.28 6,10.5A6,6 0 0,1 12,4.5M8.11,17.65L11.29,14.46C11.68,14.07 12.32,14.07 12.71,14.46L15.89,17.65C16.28,18.04 16.28,18.67 15.89,19.06L12.71,22.24C12.32,22.63 11.68,22.63 11.29,22.24L8.11,19.06C7.72,18.67 7.72,18.04 8.11,17.65Z",
"name": "google-physical-web"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M19.5,12H18V10.5H17V12H15.5V13H17V14.5H18V13H19.5V12M9.65,11.36V12.9H12.22C12.09,13.54 11.45,14.83 9.65,14.83C8.11,14.83 6.89,13.54 6.89,12C6.89,10.46 8.11,9.17 9.65,9.17C10.55,9.17 11.13,9.56 11.45,9.88L12.67,8.72C11.9,7.95 10.87,7.5 9.65,7.5C7.14,7.5 5.15,9.5 5.15,12C5.15,14.5 7.14,16.5 9.65,16.5C12.22,16.5 13.96,14.7 13.96,12.13C13.96,11.81 13.96,11.61 13.89,11.36H9.65Z",
"name": "google-plus-box"
},
{
"path": "M14,20.95H20V10.78L8,7.34V3.05H4V20.95H10V15.31H14V20.95Z",
"name": "houzz"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M13.5,18.71H18V11.09L9,8.5V5.29H6V18.71H10.5V14.5H13.5V18.71Z",
"name": "houzz-box"
},
{
"path": "M10,5A1,1 0 0,0 9,4H8V2H16V4H15A1,1 0 0,0 14,5V19A1,1 0 0,0 15,20H16V22H8V20H9A1,1 0 0,0 10,19V5Z",
"name": "instapaper"
},
{
"path": "M7.85,17.07C7.03,17.17 3.5,17.67 4.06,20.26C4.69,23.3 9.87,22.59 9.83,19C9.81,16.57 9.83,9.2 9.83,9.2C9.83,9.2 9.76,8.53 10.43,8.39L18.19,6.79C18.19,6.79 18.83,6.65 18.83,7.29C18.83,7.89 18.83,14.2 18.83,14.2C18.83,14.2 18.9,14.83 18.12,15C17.34,15.12 13.91,15.4 14.19,18C14.5,21.07 20,20.65 20,17.07V2.61C20,2.61 20.04,1.62 18.9,1.87L9.5,3.78C9.5,3.78 8.66,3.96 8.66,4.77C8.66,5.5 8.66,16.11 8.66,16.11C8.66,16.11 8.66,16.96 7.85,17.07Z",
"name": "itunes"
},
{
"path": "M2,5.69C8.92,1.07 11.1,7 11.28,10.27C11.46,13.53 8.29,17.64 4.31,14.92V20.3L2,18.77V5.69M4.22,7.4V12.78C7.84,14.95 9.08,13.17 9.08,10.09C9.08,5.74 6.57,5.59 4.22,7.4M15.08,4.15C15.08,4.15 14.9,7.64 15.08,11.07C15.44,14.5 19.69,11.84 19.69,11.84V4.92L22,5.2V14.44C22,20.6 15.85,20.3 15.85,20.3L15.08,18C20.46,18 19.78,14.43 19.78,14.43C13.27,16.97 12.77,12.61 12.77,12.61V5.69L15.08,4.15Z",
"name": "language-python-text"
},
{
"path": "M18,17.93C15.92,17.92 14.81,16.9 14.04,15.09L13.82,14.6L11.92,10.23C11.29,8.69 9.72,7.64 7.96,7.64C5.57,7.64 3.63,9.59 3.63,12C3.63,14.41 5.57,16.36 7.96,16.36C9.62,16.36 11.08,15.41 11.8,14L12.57,15.81C11.5,17.15 9.82,18 7.96,18C4.67,18 2,15.32 2,12C2,8.69 4.67,6 7.96,6C10.44,6 12.45,7.34 13.47,9.7C13.54,9.89 14.54,12.24 15.42,14.24C15.96,15.5 16.42,16.31 17.91,16.36C19.38,16.41 20.39,15.5 20.39,14.37C20.39,13.26 19.62,13 18.32,12.56C16,11.79 14.79,11 14.79,9.15C14.79,7.33 16,6.12 18,6.12C19.31,6.12 20.24,6.7 20.89,7.86L19.62,8.5C19.14,7.84 18.61,7.57 17.94,7.57C17,7.57 16.33,8.23 16.33,9.1C16.33,10.34 17.43,10.53 18.97,11.03C21.04,11.71 22,12.5 22,14.42C22,16.45 20.27,17.93 18,17.93Z",
"name": "lastfm"
},
{
"path": "M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M18.5,18.5V13.2A3.26,3.26 0 0,0 15.24,9.94C14.39,9.94 13.4,10.46 12.92,11.24V10.13H10.13V18.5H12.92V13.57C12.92,12.8 13.54,12.17 14.31,12.17A1.4,1.4 0 0,1 15.71,13.57V18.5H18.5M6.88,8.56A1.68,1.68 0 0,0 8.56,6.88C8.56,5.95 7.81,5.19 6.88,5.19A1.69,1.69 0 0,0 5.19,6.88C5.19,7.81 5.95,8.56 6.88,8.56M8.27,18.5V10.13H5.5V18.5H8.27Z",
"name": "linkedin-box"
},
{
"path": "M9.56,12.5C9.56,12.6 9.5,12.72 9.4,12.79C9.2,12.97 8.89,12.94 8.71,12.74C8.63,12.65 8.59,12.53 8.59,12.41V8.5H5.66V13.39A2.44,2.44 0 0,0 8.1,15.83C8.68,15.83 9.24,15.62 9.68,15.24C9.64,15.6 9.43,15.93 9.11,16.11C8.75,16.31 8.35,16.42 7.94,16.41C7.46,16.41 7,16.3 6.56,16.09L6.39,16V18.6C7.04,18.86 7.74,19 8.44,19C9.47,19 10.46,18.66 11.25,18C12.08,17.25 12.54,16.18 12.5,15.06V8.5H9.56V12.5M4.93,13.39V5.59H2V12.9C1.84,14.35 2.88,15.65 4.33,15.81C4.41,15.82 4.5,15.83 4.56,15.83V15.83C4.93,15.83 5.29,15.74 5.63,15.59L5.75,15.5L5.65,15.41C5.17,14.85 4.91,14.13 4.93,13.39M22,11.39V8.5H21C20.59,6.38 18.53,5 16.41,5.41C16.17,5.45 15.94,5.5 15.71,5.61C14.28,6.24 13.33,7.62 13.26,9.18V15.83H13.39C14.95,15.76 16.19,14.47 16.19,12.9H17.41V10H16.15V9.17C16.15,8.86 16.32,8.57 16.59,8.41C17.06,8.13 17.68,8.28 17.96,8.76C18.05,8.91 18.09,9.07 18.1,9.24V11.93C18.07,14.05 19.75,15.79 21.87,15.83H22V12.9H22A1,1 0 0,1 21,11.9V11.41L22,11.39Z",
"name": "lyft"
},
{
"path": "M15.45,11.91C15.34,9.7 13.7,8.37 11.72,8.37H11.64C9.35,8.37 8.09,10.17 8.09,12.21C8.09,14.5 9.62,15.95 11.63,15.95C13.88,15.95 15.35,14.3 15.46,12.36M11.65,6.39C13.18,6.39 14.62,7.07 15.67,8.13V8.13C15.67,7.62 16,7.24 16.5,7.24H16.61C17.35,7.24 17.5,7.94 17.5,8.16V16.06C17.46,16.58 18.04,16.84 18.37,16.5C19.64,15.21 21.15,9.81 17.58,6.69C14.25,3.77 9.78,4.25 7.4,5.89C4.88,7.63 3.26,11.5 4.83,15.11C6.54,19.06 11.44,20.24 14.35,19.06C15.83,18.47 16.5,20.46 15,21.11C12.66,22.1 6.23,22 3.22,16.79C1.19,13.27 1.29,7.08 6.68,3.87C10.81,1.42 16.24,2.1 19.5,5.5C22.95,9.1 22.75,15.8 19.4,18.41C17.89,19.59 15.64,18.44 15.66,16.71L15.64,16.15C14.59,17.2 13.18,17.81 11.65,17.81C8.63,17.81 6,15.15 6,12.13C6,9.08 8.63,6.39 11.65,6.39Z",
"name": "mail-ru"
},
{
"path": "M20.93,14C20.66,15.4 18.5,16.95 15.97,17.25C14.66,17.4 13.38,17.55 12,17.5C9.76,17.38 8,16.95 8,16.95L8.03,17.57C8.32,19.78 10.22,19.92 12.03,20C13.85,20.04 15.47,19.53 15.47,19.53L15.55,21.17C15.55,21.17 14.27,21.86 12,22C10.75,22.05 9.2,21.95 7.39,21.47C3.47,20.43 2.79,16.25 2.69,12L2.68,8.57C2.68,4.23 5.5,2.96 5.5,2.96C6.95,2.3 9.41,2 11.97,2H12.03C14.59,2 17.05,2.3 18.5,2.96C18.5,2.96 21.33,4.23 21.33,8.57C21.33,8.57 21.36,11.77 20.93,14M8.33,10.32C8.33,9.54 7.7,8.91 6.93,8.91C6.15,8.91 5.5,9.54 5.5,10.32C5.5,11.09 6.15,11.72 6.93,11.72A1.4,1.4 0 0,0 8.33,10.32M13.41,10.32A1.41,1.41 0 0,0 12,8.91A1.41,1.41 0 0,0 10.59,10.32C10.59,11.09 11.22,11.72 12,11.72C12.78,11.72 13.41,11.09 13.41,10.32M18.5,10.32C18.5,9.54 17.85,8.91 17.07,8.91C16.3,8.91 15.67,9.54 15.67,10.32A1.4,1.4 0 0,0 17.07,11.72C17.85,11.72 18.5,11.09 18.5,10.32Z",
"name": "mastodon-variant"
},
{
"path": "M4.37,7.3C4.4,7.05 4.3,6.81 4.12,6.65L2.25,4.4V4.06H8.05L12.53,13.89L16.47,4.06H22V4.4L20.4,5.93C20.27,6.03 20.2,6.21 20.23,6.38V17.62C20.2,17.79 20.27,17.97 20.4,18.07L21.96,19.6V19.94H14.12V19.6L15.73,18.03C15.89,17.88 15.89,17.83 15.89,17.59V8.5L11.4,19.9H10.8L5.57,8.5V16.14C5.5,16.46 5.63,16.78 5.86,17L7.96,19.57V19.9H2V19.57L4.1,17C4.33,16.78 4.43,16.46 4.37,16.14V7.3Z",
"name": "medium"
},
{
"path": "M19.61,14.86C19.61,16.68 18.3,18.25 16.5,18.55C16.29,18.59 16.07,18.62 15.84,18.61C15.76,18.61 15.73,18.64 15.71,18.71C15.35,19.74 14.64,20.35 13.57,20.5C12.86,20.6 12.22,20.41 11.65,19.97C11.57,19.9 11.5,19.9 11.44,19.96C10.78,20.43 10.04,20.64 9.23,20.59C7.66,20.5 6.33,19.29 6.08,17.74C6.06,17.63 6.04,17.5 6.04,17.41C6.04,17.32 6,17.29 5.92,17.27C5.44,17.18 5,17 4.63,16.68C3.92,16.13 3.5,15.41 3.4,14.5C3.29,13.5 3.61,12.62 4.32,11.89C4.38,11.83 4.38,11.79 4.34,11.72C4.07,11.24 3.94,10.72 3.96,10.17C4,8.79 4.97,7.65 6.31,7.37C6.46,7.33 6.54,7.27 6.61,7.13C7.27,5.71 8.37,4.85 9.91,4.56C11,4.36 12,4.58 12.94,5.13C13,5.18 13.08,5.18 13.17,5.16C14.67,4.72 16,5.04 17.12,6.11C17.78,6.74 18.15,7.54 18.26,8.46C18.28,8.66 18.29,8.86 18.28,9.06C18.27,9.14 18.29,9.17 18.37,9.19C19.04,9.44 19.5,9.91 19.71,10.6C19.96,11.45 19.75,12.21 19.11,12.83C19.05,12.89 19.07,12.92 19.1,12.97C19.44,13.56 19.61,14.18 19.61,14.86M12.93,14.57C12.93,15.34 13.43,16 14.14,16.26C14.5,16.37 14.85,16.43 15.22,16.45C15.5,16.46 15.75,16.44 16,16.32C16.19,16.22 16.28,16.06 16.27,15.85C16.26,15.64 16.16,15.5 15.96,15.4C15.89,15.37 15.82,15.34 15.74,15.33C15.5,15.29 15.3,15.26 15.07,15.21C14.71,15.14 14.55,14.95 14.55,14.57C14.54,14.24 14.63,13.93 14.73,13.63C14.92,13.07 15.17,12.53 15.41,12C15.64,11.47 15.88,10.95 16.04,10.4C16.13,10.1 16.18,9.8 16.09,9.5C15.97,9 15.69,8.7 15.2,8.61C14.75,8.5 14.3,8.5 13.9,8.78C13.76,8.87 13.63,8.85 13.5,8.74C13.43,8.67 13.34,8.58 13.26,8.5C12.84,8.12 12.3,8.1 11.85,8.45C11.67,8.59 11.5,8.76 11.33,8.89C11.16,9 11,9.03 10.79,8.92C10.6,8.83 10.42,8.74 10.23,8.65C10.03,8.57 9.85,8.46 9.63,8.44C8.95,8.38 8.24,8.79 7.94,9.41C7.8,9.68 7.69,9.96 7.59,10.25C7.11,11.57 6.72,12.91 6.32,14.26C6.14,14.86 6.35,15.45 6.86,15.77C7.26,16 7.69,16.09 8.14,15.95C8.5,15.84 8.71,15.55 8.85,15.22C9.31,14.13 9.73,13 10.17,11.91C10.29,11.61 10.41,11.3 10.54,11C10.67,10.7 11.04,10.6 11.26,10.8C11.4,10.92 11.44,11.09 11.42,11.26C11.41,11.45 11.34,11.62 11.27,11.79C11,12.5 10.69,13.24 10.4,13.97C10.34,14.11 10.28,14.26 10.25,14.42C10.21,14.69 10.31,14.93 10.54,15C10.76,15.12 11,15.14 11.23,15.05C11.5,14.95 11.67,14.74 11.79,14.5C12.22,13.65 12.65,12.8 13.08,11.95C13.28,11.56 13.5,11.17 13.68,10.78C13.76,10.64 13.85,10.5 14,10.41C14.12,10.33 14.25,10.33 14.38,10.4C14.5,10.47 14.5,10.6 14.5,10.73C14.5,10.8 14.5,10.87 14.47,10.93C14.41,11.07 14.36,11.2 14.3,11.33C13.94,12.09 13.57,12.84 13.22,13.59C13.07,13.91 12.91,14.23 12.93,14.57M17.96,20.12C17.96,19.62 17.54,19.2 17.04,19.2C16.5,19.2 16.1,19.61 16.1,20.12A0.93,0.93 0 0,0 17.03,21.05A0.93,0.93 0 0,0 17.96,20.12M2.38,12.46C2.86,12.46 3.27,12.05 3.27,11.57C3.27,11.09 2.87,10.69 2.39,10.69C1.89,10.69 1.5,11.08 1.5,11.57C1.5,12.06 1.89,12.46 2.38,12.46M13.26,2.55C12.77,2.55 12.37,2.94 12.37,3.42C12.37,3.91 12.77,4.3 13.25,4.3C13.74,4.3 14.13,3.92 14.13,3.43C14.13,2.95 13.74,2.55 13.26,2.55M20.45,8.03C20.45,7.63 20.11,7.29 19.71,7.29C19.3,7.28 18.95,7.63 18.95,8.04C18.95,8.45 19.28,8.78 19.7,8.78C20.12,8.78 20.46,8.45 20.45,8.03M5.04,5.89C5.04,6.27 5.34,6.56 5.71,6.56C6.09,6.56 6.39,6.26 6.38,5.88C6.38,5.5 6.09,5.22 5.72,5.22C5.33,5.22 5.04,5.5 5.04,5.89M12.06,21.44C12.06,21.12 11.81,20.86 11.5,20.86C11.16,20.86 10.91,21.11 10.91,21.44C10.91,21.75 11.16,22 11.5,22C11.8,22 12.06,21.75 12.06,21.44M21,12.5C20.71,12.5 20.45,12.78 20.45,13.08A0.55,0.55 0 0,0 21,13.63C21.33,13.63 21.57,13.4 21.57,13.08C21.57,12.77 21.33,12.5 21,12.5M7.62,2C7.35,2 7.14,2.2 7.14,2.47C7.14,2.73 7.35,2.94 7.62,2.94A0.47,0.47 0 0,0 8.09,2.47C8.09,2.2 7.89,2 7.62,2M22.08,10C21.86,10 21.67,10.17 21.66,10.4C21.66,10.63 21.85,10.82 22.08,10.82C22.32,10.82 22.5,10.64 22.5,10.41C22.5,10.17 22.32,10 22.08,10M5.5,18.26C5.5,18.04 5.29,17.85 5.06,17.84C4.84,17.84 4.65,18.03 4.65,18.27C4.65,18.5 4.84,18.68 5.07,18.68C5.3,18.68 5.5,18.5 5.5,18.26Z",
"name": "meetup"
},
{
"path": "M21.11,18.5C20.97,18.5 20.83,18.44 20.71,18.36C20.37,18.13 20.28,17.68 20.5,17.34C21.18,16.34 21.54,15.16 21.54,13.93C21.54,12.71 21.18,11.53 20.5,10.5C20.28,10.18 20.37,9.73 20.71,9.5C21.04,9.28 21.5,9.37 21.72,9.7C22.56,10.95 23,12.41 23,13.93C23,15.45 22.56,16.91 21.72,18.16C21.58,18.37 21.35,18.5 21.11,18.5M19,17.29C18.88,17.29 18.74,17.25 18.61,17.17C18.28,16.94 18.19,16.5 18.42,16.15C18.86,15.5 19.1,14.73 19.1,13.93C19.1,13.14 18.86,12.37 18.42,11.71C18.19,11.37 18.28,10.92 18.61,10.69C18.95,10.47 19.4,10.55 19.63,10.89C20.24,11.79 20.56,12.84 20.56,13.93C20.56,15 20.24,16.07 19.63,16.97C19.5,17.18 19.25,17.29 19,17.29M14.9,15.73C15.89,15.73 16.7,14.92 16.7,13.93C16.7,13.17 16.22,12.5 15.55,12.25C15.5,12.55 15.43,12.85 15.34,13.14C15.23,13.44 14.95,13.64 14.64,13.64C14.57,13.64 14.5,13.62 14.41,13.6C14.03,13.47 13.82,13.06 13.95,12.67C14.09,12.24 14.17,11.78 14.17,11.32C14.17,8.93 12.22,7 9.82,7C8.1,7 6.56,8 5.87,9.5C6.54,9.7 7.16,10.04 7.66,10.54C7.95,10.83 7.95,11.29 7.66,11.58C7.38,11.86 6.91,11.86 6.63,11.58C6.17,11.12 5.56,10.86 4.9,10.86C3.56,10.86 2.46,11.96 2.46,13.3C2.46,14.64 3.56,15.73 4.9,15.73H14.9M15.6,10.75C17.06,11.07 18.17,12.37 18.17,13.93C18.17,15.73 16.7,17.19 14.9,17.19H4.9C2.75,17.19 1,15.45 1,13.3C1,11.34 2.45,9.73 4.33,9.45C5.12,7.12 7.33,5.5 9.82,5.5C12.83,5.5 15.31,7.82 15.6,10.75Z",
"name": "mixcloud"
},
{
"path": "M5.68,3.96L11.41,11.65C11.55,11.84 11.55,12.1 11.41,12.29L5.65,20L5.5,20.18C4.76,21 3.47,21.07 2.64,20.31C1.85,19.59 1.79,18.37 2.43,17.5L6.56,11.97L2.46,6.47C1.83,5.62 1.88,4.39 2.67,3.67L2.82,3.54C3.73,2.87 5,3.05 5.68,3.96M18.32,3.96C19,3.05 20.27,2.87 21.18,3.54L21.33,3.67C22.12,4.39 22.17,5.61 21.54,6.47L17.44,11.97L21.57,17.5C22.21,18.36 22.15,19.59 21.36,20.31C20.53,21.07 19.24,21 18.5,20.18L18.35,20L12.59,12.29C12.45,12.1 12.45,11.84 12.59,11.65L18.32,3.96Z",
"name": "mixer"
},
{
"path": "M3.25,4.03L19.95,20.73L18.7,22L14.86,18.13C14.77,18.12 14.68,18.09 14.59,18.05C14.26,17.89 14.14,17.62 14.11,17.38L12.18,15.45C12.14,15.53 12.09,15.6 12.05,15.66C11.62,16.26 11.19,16.26 10.86,16.04C10.54,15.83 5.5,12 5.23,11.87C4.95,11.76 4.85,12.03 5.12,13.5C5.39,15 4.95,15.39 4.57,15.45C4.2,15.5 3.06,15.18 3,12.14C2.95,9.11 3.76,8.62 4.14,8.62C4.6,8.62 7.08,10.69 8.84,12.12L2,5.28L3.25,4.03M18.38,16.56C18.75,15.4 19.12,13.8 19.1,12.03V12C19.14,8.5 17.66,5.58 17.66,5.58C17.66,5.58 17.42,4.72 18.12,4.39C18.83,4.06 19.3,4.61 19.3,4.61C21.12,8.22 21,11.64 21,12C21,12.27 21.09,14.96 19.88,18.05L18.38,16.56M15.14,13.31C15.19,12.92 15.22,12.5 15.24,12.03V12C15.14,8.5 14.13,7.21 14.13,7.21C14.13,7.21 13.89,6.34 14.59,6C15.3,5.69 15.77,6.23 15.77,6.23C17.26,8.94 17.16,11.64 17.14,12C17.15,12.2 17.2,13.38 16.82,15L15.14,13.31M10.2,8.38C10.23,7.77 10.59,7.64 10.59,7.64C10.59,7.64 11.19,7.37 11.57,7.8C11.91,8.19 12.72,9.57 12.89,11.07L10.2,8.38Z",
"name": "nfc-off"
},
{ "path": "M20,4H4V20H12V8H16V20H20V4", "name": "npm-variant" },
{
"path": "M3,3V21H21V3H3M6,6H18V18H15V9H12V18H6V6Z",
"name": "npm-variant-outline"
},
{
"path": "M8.32,21.97C8.21,21.92 8.08,21.76 8.06,21.65C8.03,21.5 8,21.76 8.66,17.56C9.26,13.76 9.25,13.82 9.33,13.71C9.46,13.54 9.44,13.54 10.94,13.53C12.26,13.5 12.54,13.5 13.13,13.41C16.38,12.96 18.39,11.05 19.09,7.75C19.13,7.53 19.17,7.34 19.18,7.34C19.18,7.33 19.25,7.38 19.33,7.44C20.36,8.22 20.71,9.66 20.32,11.58C19.86,13.87 18.64,15.39 16.74,16.04C15.93,16.32 15.25,16.43 14.05,16.46C13.25,16.5 13.23,16.5 13,16.65C12.83,16.82 12.84,16.79 12.45,19.2C12.18,20.9 12.08,21.45 12.04,21.55C11.97,21.71 11.83,21.85 11.67,21.93L11.56,22H10C8.71,22 8.38,22 8.32,21.97V21.97M3.82,19.74C3.63,19.64 3.5,19.47 3.5,19.27C3.5,19 6.11,2.68 6.18,2.5C6.27,2.32 6.5,2.13 6.68,2.06L6.83,2H10.36C14.27,2 14.12,2 15,2.2C17.62,2.75 18.82,4.5 18.37,7.13C17.87,10.06 16.39,11.8 13.87,12.43C13,12.64 12.39,12.7 10.73,12.7C9.42,12.7 9.32,12.71 9.06,12.85C8.8,13 8.59,13.27 8.5,13.6C8.46,13.67 8.23,15.07 7.97,16.7C7.71,18.33 7.5,19.69 7.5,19.72L7.47,19.78H5.69C4.11,19.78 3.89,19.78 3.82,19.74V19.74Z",
"name": "paypal"
},
{
"path": "M12,7A2,2 0 0,1 10,9A2,2 0 0,1 8,7C7.37,7.84 7,8.87 7,10A5,5 0 0,0 12,15A5,5 0 0,0 17,10A5,5 0 0,0 12,5C11.57,5 11.16,5.05 10.77,5.15C11.5,5.45 12,6.17 12,7M12,2A8,8 0 0,1 20,10C20,11.05 19.8,12.04 19.43,12.96C17.89,17.38 13.63,22 12,22C10.37,22 6.11,17.38 4.57,12.96C4.2,12.04 4,11.05 4,10A8,8 0 0,1 12,2Z",
"name": "periscope"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H9.29C9.69,20.33 10.19,19.38 10.39,18.64L11.05,16.34C11.36,16.95 12.28,17.45 13.22,17.45C16.17,17.45 18.22,14.78 18.22,11.45C18.22,8.28 15.64,5.89 12.3,5.89C8.14,5.89 5.97,8.67 5.97,11.72C5.97,13.14 6.69,14.89 7.91,15.45C8.08,15.56 8.19,15.5 8.19,15.34L8.47,14.28C8.5,14.14 8.5,14.06 8.41,14C7.97,13.45 7.69,12.61 7.69,11.78C7.69,9.64 9.3,7.61 12.03,7.61C14.42,7.61 16.08,9.19 16.08,11.5C16.08,14.11 14.75,15.95 13.03,15.95C12.05,15.95 11.39,15.11 11.55,14.17C11.83,13.03 12.39,11.83 12.39,11C12.39,10.22 12,9.61 11.16,9.61C10.22,9.61 9.39,10.61 9.39,11.95C9.39,12.83 9.66,13.39 9.66,13.39L8.55,18.17C8.39,19 8.47,20.25 8.55,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z",
"name": "pinterest-box"
},
{
"path": "M21.9,4.26C21.64,3.55 20.96,3.07 20.2,3.07H20.19L18.46,3.07H3.81C3.07,3.07 2.39,3.54 2.12,4.24C2.04,4.45 2,4.66 2,4.88V10.92L2.07,12.12C2.36,14.85 3.78,17.23 5.97,18.9C6,18.93 6.05,18.96 6.09,19H6.11C7.29,19.86 8.6,20.44 10,20.73C10.68,20.86 11.35,20.93 12,20.93C12.63,20.93 13.25,20.87 13.85,20.76C13.93,20.75 14,20.73 14.07,20.72C14.09,20.71 14.11,20.7 14.14,20.69C15.5,20.4 16.76,19.83 17.89,19H17.91C17.95,18.96 18,18.93 18.03,18.9C20.22,17.23 21.64,14.85 21.93,12.12L22,10.92V4.88C22,4.68 21.97,4.47 21.9,4.26M17.67,10.55L12.96,15.06C12.7,15.32 12.35,15.44 12,15.44C11.67,15.44 11.33,15.32 11.06,15.06L6.36,10.55C5.81,10.03 5.79,9.16 6.32,8.61C6.84,8.06 7.71,8.05 8.26,8.57L12,12.17L15.77,8.57C16.31,8.05 17.18,8.07 17.71,8.61C18.23,9.16 18.21,10.03 17.67,10.55Z",
"name": "pocket"
},
{
"path": "M12,3A9,9 0 0,1 21,12C21,13.76 20.5,15.4 19.62,16.79L21,18.17V20A1,1 0 0,1 20,21H18.18L16.79,19.62C15.41,20.5 13.76,21 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17C12.65,17 13.26,16.88 13.83,16.65L10.95,13.77C10.17,13 10.17,11.72 10.95,10.94C11.73,10.16 13,10.16 13.78,10.94L16.66,13.82C16.88,13.26 17,12.64 17,12A5,5 0 0,0 12,7Z",
"name": "quicktime"
},
{
"path": "M18.61,5.89C18.6,5.79 18.5,5.73 18.44,5.73C18.37,5.72 16.83,5.61 16.83,5.61C16.83,5.61 15.76,4.55 15.65,4.43C15.53,4.31 15.3,4.35 15.21,4.37C15.2,4.37 15,4.44 14.61,4.55C14.25,3.5 13.62,2.58 12.43,2.58C12.11,2.18 11.72,2 11.38,2C8.8,2 7.57,5.22 7.18,6.86C6.18,7.17 5.47,7.39 5.37,7.42C4.82,7.6 4.8,7.62 4.73,8.14C4.67,8.54 3.21,19.86 3.21,19.86L14.61,22L20.79,20.66C20.79,20.66 18.62,6 18.61,5.89M14,4.76C13.69,4.85 13.37,4.95 13,5.06C13,5 13,4.93 13,4.85C13,4.21 12.93,3.7 12.79,3.29C13.35,3.36 13.73,4 14,4.76M12.08,3.42C12.24,3.82 12.34,4.39 12.34,5.16C12.34,5.2 12.34,5.24 12.34,5.27C11.71,5.46 11.03,5.68 10.35,5.89C10.73,4.4 11.45,3.69 12.08,3.42M11.31,2.69C11.42,2.69 11.53,2.73 11.64,2.8C10.81,3.19 9.93,4.17 9.55,6.12C9,6.3 8.47,6.46 8,6.62C8.42,5.12 9.46,2.69 11.31,2.69M12.5,9.15L11.76,11.42C11.76,11.42 11.09,11.06 10.27,11.06C9.07,11.06 9,11.81 9,12C9,13.04 11.71,13.43 11.71,15.86C11.71,17.77 10.5,19 8.87,19C6.91,19 5.91,17.78 5.91,17.78L6.43,16.05C6.43,16.05 7.46,16.93 8.33,16.93C8.9,16.93 9.13,16.5 9.13,16.16C9.13,14.81 6.92,14.75 6.92,12.53C6.92,10.66 8.26,8.85 10.97,8.85C12,8.85 12.5,9.15 12.5,9.15M15.43,5.29L16.75,6.6L17.71,6.68C18.05,9 19.19,16.73 19.66,19.88L14.66,20.97L15.43,5.29Z",
"name": "shopify"
},
{
"path": "M7.47,17.19C7.37,17.95 7.08,18.21 6.19,18.21C5.27,18.21 4.87,17.79 4.81,16.87L4.61,13.65C4.61,12.91 4.87,12.55 5.86,12.55C7.21,12.55 7.04,13.54 7.64,14.5C8.33,15.62 10,16.35 12.31,16.35C15.17,16.35 17,15.14 17,13.57C17,12.23 15.89,11.39 13.85,11.16L11.5,10.89C7.21,10.42 5.1,9.19 5.1,6.62C5.1,4.07 8.06,2 12.21,2C13.5,2 14.81,2.29 16.29,2.76C16.29,2.26 16.58,2.1 17.3,2.1C18.46,2.1 18.55,2.39 18.62,3.08L18.85,5.88C18.85,6.5 18.39,6.83 17.63,6.83C16.35,6.83 16.55,5.88 15.86,5.07C15.17,4.26 13.79,3.73 12.08,3.73C9.44,3.73 7.7,4.89 7.7,6.5C7.7,7.8 8.92,8.56 11.38,8.82L13.95,9.08C17.7,9.5 19.61,10.92 19.61,13.33C19.61,16.17 16.71,18.08 12.21,18.08C10.56,18.08 9.08,17.77 7.47,17.19M1,16H2V21H23V22H1V16Z",
"name": "slackware"
},
{
"path": "M6,3H18A3,3 0 0,1 21,6V18A3,3 0 0,1 18,21H6A3,3 0 0,1 3,18V6A3,3 0 0,1 6,3M7,6A1,1 0 0,0 6,7V17A1,1 0 0,0 7,18H17A1,1 0 0,0 18,17V7A1,1 0 0,0 17,6H7M9.5,9H14.5A0.5,0.5 0 0,1 15,9.5V14.5A0.5,0.5 0 0,1 14.5,15H9.5A0.5,0.5 0 0,1 9,14.5V9.5A0.5,0.5 0 0,1 9.5,9Z",
"name": "square-inc"
},
{
"path": "M5.5,0H18.5A5.5,5.5 0 0,1 24,5.5V18.5A5.5,5.5 0 0,1 18.5,24H5.5A5.5,5.5 0 0,1 0,18.5V5.5A5.5,5.5 0 0,1 5.5,0M15.39,15.18C15.39,16.76 14.5,17.81 12.85,17.95V12.61C14.55,13.13 15.39,13.66 15.39,15.18M11.65,6V10.88C10.34,10.5 9.03,9.93 9.03,8.43C9.03,6.94 10.18,6.12 11.65,6M15.5,7.6L16.5,6.8C15.62,5.66 14.4,4.92 12.85,4.77V3.8H11.65V3.8L11.65,4.75C9.5,4.89 7.68,6.17 7.68,8.5C7.68,11 9.74,11.78 11.65,12.29V17.96C10.54,17.84 9.29,17.31 8.43,16.03L7.3,16.78C8.2,18.12 9.76,19 11.65,19.14V20.2H12.07L12.85,20.2V19.16C15.35,19 16.7,17.34 16.7,15.14C16.7,12.58 14.81,11.76 12.85,11.19V6.05C14,6.22 14.85,6.76 15.5,7.6Z",
"name": "square-inc-cash"
},
{
"path": "M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V15L6.19,16.31C6.45,17.6 7.6,18.58 8.97,18.58C10.53,18.58 11.8,17.31 11.8,15.75V15.62L15.2,13.19H15.28C17.36,13.19 19.05,11.5 19.05,9.42C19.05,7.34 17.36,5.65 15.28,5.65C13.2,5.65 11.5,7.34 11.5,9.42V9.47L9.13,12.93L8.97,12.92C8.38,12.92 7.83,13.1 7.38,13.41L3,11.6V5A2,2 0 0,1 5,3H19M8.28,17.17C9.08,17.5 10,17.13 10.33,16.33C10.66,15.53 10.28,14.62 9.5,14.29L8.22,13.76C8.71,13.58 9.26,13.57 9.78,13.79C10.31,14 10.72,14.41 10.93,14.94C11.15,15.46 11.15,16.04 10.93,16.56C10.5,17.64 9.23,18.16 8.15,17.71C7.65,17.5 7.27,17.12 7.06,16.67L8.28,17.17M17.8,9.42C17.8,10.81 16.67,11.94 15.28,11.94C13.9,11.94 12.77,10.81 12.77,9.42A2.51,2.51 0 0,1 15.28,6.91C16.67,6.91 17.8,8.04 17.8,9.42M13.4,9.42C13.4,10.46 14.24,11.31 15.29,11.31C16.33,11.31 17.17,10.46 17.17,9.42C17.17,8.38 16.33,7.53 15.29,7.53C14.24,7.53 13.4,8.38 13.4,9.42Z",
"name": "steam-box"
},
{
"path": "M14.92,17.16L16.75,13.53H19.45L14.94,22.5L10.37,13.53H13.07L14.92,17.16M10.63,8.66L8.18,13.55H4.55L10.61,1.5L16.74,13.55H13.11L10.63,8.66Z",
"name": "strava"
},
{
"path": "M12,14C11,14 9,15 9,16C9,18 12,18 12,18V17A1,1 0 0,1 11,16A1,1 0 0,1 12,15V14M12,19C12,19 8,18.5 8,16.5C8,13.5 11,12.75 12,12.75V11.5C11,11.5 7,13 7,16C7,20 12,20 12,20V19M10.07,7.03L11.26,7.56C11.69,5.12 12.84,3.5 12.84,3.5C12.41,4.53 12.13,5.38 11.95,6.05C13.16,3.55 15.61,2 15.61,2C14.43,3.18 13.56,4.46 12.97,5.53C14.55,3.85 16.74,2.75 16.74,2.75C14.05,4.47 12.84,7.2 12.54,7.96L13.09,8.04C13.09,8.56 13.09,9.04 13.34,9.42C14.1,11.31 18,11.47 18,16C18,20.53 13.97,22 11.83,22C9.69,22 5,21.03 5,16C5,10.97 9.95,10.93 10.83,8.92C10.95,8.54 10.07,7.03 10.07,7.03Z",
"name": "tor"
},
{
"path": "M17,11H13V15.5C13,16.44 13.28,17 14.5,17H17V21C17,21 15.54,21.05 14.17,21.05C10.8,21.05 9.5,19 9.5,16.75V11H7V7C10.07,6.74 10.27,4.5 10.5,3H13V7H17",
"name": "tumblr"
},
{
"path": "M16,11H13V14.9C13,15.63 13.14,16 14.1,16H16V19C16,19 14.97,19.1 13.9,19.1C11.25,19.1 10,17.5 10,15.7V11H8V8.2C10.41,8 10.62,6.16 10.8,5H13V8H16M20,2H4C2.89,2 2,2.89 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z",
"name": "tumblr-box"
},
{
"path": "M3.75,17L8,12.75V16H18V11.5L20,9.5V16A2,2 0 0,1 18,18H8V21.25L3.75,17M20.25,7L16,11.25V8H6V12.5L4,14.5V8A2,2 0 0,1 6,6H16V2.75L20.25,7Z",
"name": "tumblr-reblog"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M17.71,9.33C18.19,8.93 18.75,8.45 19,7.92C18.59,8.13 18.1,8.26 17.56,8.33C18.06,7.97 18.47,7.5 18.68,6.86C18.16,7.14 17.63,7.38 16.97,7.5C15.42,5.63 11.71,7.15 12.37,9.95C9.76,9.79 8.17,8.61 6.85,7.16C6.1,8.38 6.75,10.23 7.64,10.74C7.18,10.71 6.83,10.57 6.5,10.41C6.54,11.95 7.39,12.69 8.58,13.09C8.22,13.16 7.82,13.18 7.44,13.12C7.81,14.19 8.58,14.86 9.9,15C9,15.76 7.34,16.29 6,16.08C7.15,16.81 8.46,17.39 10.28,17.31C14.69,17.11 17.64,13.95 17.71,9.33Z",
"name": "twitter-box"
},
{
"path": "M17.71,9.33C18.19,8.93 18.75,8.45 19,7.92C18.59,8.13 18.1,8.26 17.56,8.33C18.06,7.97 18.47,7.5 18.68,6.86C18.16,7.14 17.63,7.38 16.97,7.5C15.42,5.63 11.71,7.15 12.37,9.95C9.76,9.79 8.17,8.61 6.85,7.16C6.1,8.38 6.75,10.23 7.64,10.74C7.18,10.71 6.83,10.57 6.5,10.41C6.54,11.95 7.39,12.69 8.58,13.09C8.22,13.16 7.82,13.18 7.44,13.12C7.81,14.19 8.58,14.86 9.9,15C9,15.76 7.34,16.29 6,16.08C7.15,16.81 8.46,17.39 10.28,17.31C14.69,17.11 17.64,13.95 17.71,9.33M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z",
"name": "twitter-circle"
},
{
"path": "M6.38,13.24V13.24C6.38,11.84 6.38,10.44 6.38,9.04H7.4V15.84H6.39C6.39,15.63 6.39,15.42 6.39,15.21C5.93,15.68 5.29,15.96 4.58,15.96C3.12,15.96 2,14.9 2,13.3V9.04H3V13.24C3,14.33 3.74,15.04 4.7,15.04C5.64,15.04 6.38,14.31 6.38,13.24M9.14,9.04V11.5C9.37,11.29 9.65,11.1 9.95,10.97C10.25,10.85 10.58,10.78 10.91,10.78C12.37,10.78 13.5,11.94 13.5,13.37C13.5,14.8 12.37,15.96 10.91,15.96C10.58,15.96 10.25,15.89 9.95,15.77C9.64,15.64 9.37,15.45 9.13,15.22C9.13,15.43 9.13,15.63 9.13,15.84C8.81,15.84 8.5,15.84 8.16,15.84V9.04H9.14M12.55,13.37V13.37C12.55,12.41 11.77,11.65 10.84,11.65C9.89,11.65 9.13,12.41 9.13,13.37C9.13,14.32 9.88,15.09 10.84,15.09C11.77,15.09 12.55,14.32 12.55,13.37M16.46,10.79C17.9,10.79 18.95,11.89 18.95,13.36V13.69H14.91C15.04,14.5 15.71,15.09 16.55,15.09C17.13,15.09 17.61,14.86 18,14.36L18.7,14.89C18.2,15.55 17.46,15.95 16.55,15.95C15.06,15.95 13.91,14.84 13.91,13.36C13.91,11.97 15,10.79 16.46,10.79M14.92,12.91H17.95C17.79,12.15 17.18,11.65 16.44,11.65C15.71,11.65 15.1,12.15 14.92,12.91M20.5,13V15.84H19.5V10.89C19.82,10.89 20.14,10.89 20.47,10.89V11.5C20.71,11.1 21.11,10.85 21.66,10.85H22V11.76H21.59C20.95,11.76 20.5,12.26 20.5,13",
"name": "uber"
},
{
"path": "M19.5,3C20.14,4.08 20.44,5.19 20.44,6.6C20.44,11.08 16.61,16.91 13.5,21H6.41L3.56,4L9.77,3.39L11.28,15.5C12.69,13.21 14.42,9.61 14.42,7.16C14.42,5.81 14.19,4.9 13.83,4.15L19.5,3Z",
"name": "venmo"
},
{
"path": "M5,3A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5M5.5,8.5H7C7.36,8.5 7.5,8.66 7.64,9.07C8.36,11.17 9.57,13 10.07,13C10.26,13 10.35,12.92 10.35,12.45V10.28C10.29,9.28 9.76,9.19 9.76,8.84C9.76,8.67 9.9,8.5 10.14,8.5H12.45C12.77,8.5 12.87,8.67 12.87,9.04V11.96C12.87,12.27 13,12.38 13.1,12.38C13.29,12.38 13.45,12.27 13.79,11.93C14.85,10.74 15.6,8.92 15.6,8.92C15.7,8.7 15.87,8.5 16.24,8.5H17.71C18.16,8.5 18.26,8.73 18.16,9.04C17.97,9.9 16.18,12.43 16.18,12.43C16,12.68 15.96,12.8 16.18,13.09C16.33,13.3 16.85,13.74 17.19,14.15C17.83,14.86 18.3,15.46 18.44,15.87C18.56,16.29 18.35,16.5 17.93,16.5H16.45C15.89,16.5 15.73,16.05 14.73,15.05C13.85,14.21 13.5,14.1 13.26,14.1C12.96,14.1 12.87,14.18 12.87,14.61V15.93C12.87,16.29 12.76,16.5 11.82,16.5C10.26,16.5 8.54,15.55 7.33,13.8C5.5,11.24 5,9.31 5,8.92C5,8.7 5.08,8.5 5.5,8.5Z",
"name": "vk-box"
},
{
"path": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M5.5,8.5H7C7.36,8.5 7.5,8.66 7.64,9.07C8.36,11.17 9.57,13 10.07,13C10.26,13 10.35,12.92 10.35,12.45V10.28C10.29,9.28 9.76,9.19 9.76,8.84C9.76,8.67 9.9,8.5 10.14,8.5H12.45C12.77,8.5 12.87,8.67 12.87,9.04V11.96C12.87,12.27 13,12.38 13.1,12.38C13.29,12.38 13.45,12.27 13.79,11.93C14.85,10.74 15.6,8.92 15.6,8.92C15.7,8.7 15.87,8.5 16.24,8.5H17.71C18.16,8.5 18.26,8.73 18.16,9.04C17.97,9.9 16.18,12.43 16.18,12.43C16,12.68 15.96,12.8 16.18,13.09C16.33,13.3 16.85,13.74 17.19,14.15C17.83,14.86 18.3,15.46 18.44,15.87C18.56,16.29 18.35,16.5 17.93,16.5H16.45C15.89,16.5 15.73,16.05 14.73,15.05C13.85,14.21 13.5,14.1 13.26,14.1C12.96,14.1 12.87,14.18 12.87,14.61V15.93C12.87,16.29 12.76,16.5 11.82,16.5C10.26,16.5 8.54,15.55 7.33,13.8C5.5,11.24 5,9.31 5,8.92C5,8.7 5.08,8.5 5.5,8.5Z",
"name": "vk-circle"
},
{
"path": "M17,17.5L12,15L7,17.5V5H5V19H19V5H17V17.5M12,12.42L14.25,13.77L13.65,11.22L15.64,9.5L13,9.27L12,6.86L11,9.27L8.36,9.5L10.35,11.22L9.75,13.77L12,12.42M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z",
"name": "wunderlist"
},
{
"path": "M0 16.7L3.2 12.9L0 9.1L1.5 7.8L4.5 11.3L7.5 7.8L9 9.1L5.8 12.9L9 16.7L7.5 18L4.5 14.4L1.5 18L0 16.7M24 16.9C24 17.4 23.6 17.9 23 17.9H20C18.9 17.9 18 17 18 15.9V13.9C18 12.8 18.9 11.9 20 11.9H22V9.9H18V8H23C23.5 8 24 8.4 24 9M22 14H20V16H22V14M16 16.9C16 17.4 15.6 17.9 15 17.9H12C10.9 17.9 10 17 10 15.9V9.9C10 8.8 10.9 7.9 12 7.9H14V5H16V16.9M14 15.9V9.9H12V15.9H14Z",
"name": "xda"
},
{
"path": "M4.8,3C3.8,3 3,3.8 3,4.8V19.2C3,20.2 3.8,21 4.8,21H19.2C20.2,21 21,20.2 21,19.2V4.8C21,3.8 20.2,3 19.2,3M16.07,5H18.11C18.23,5 18.33,5.04 18.37,5.13C18.43,5.22 18.43,5.33 18.37,5.44L13.9,13.36L16.75,18.56C16.81,18.67 16.81,18.78 16.75,18.87C16.7,18.95 16.61,19 16.5,19H14.47C14.16,19 14,18.79 13.91,18.61L11.04,13.35C11.18,13.1 15.53,5.39 15.53,5.39C15.64,5.19 15.77,5 16.07,5M7.09,7.76H9.1C9.41,7.76 9.57,7.96 9.67,8.15L11.06,10.57C10.97,10.71 8.88,14.42 8.88,14.42C8.77,14.61 8.63,14.81 8.32,14.81H6.3C6.18,14.81 6.09,14.76 6.04,14.67C6,14.59 6,14.47 6.04,14.36L8.18,10.57L6.82,8.2C6.77,8.09 6.75,8 6.81,7.89C6.86,7.81 6.96,7.76 7.09,7.76Z",
"name": "xing-box"
},
{
"path": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M15.85,6H17.74C17.86,6 17.94,6.04 18,6.12C18.04,6.2 18.04,6.3 18,6.41L13.84,13.76L16.5,18.59C16.53,18.69 16.53,18.8 16.5,18.88C16.43,18.96 16.35,19 16.24,19H14.36C14.07,19 13.93,18.81 13.84,18.64L11.17,13.76C11.31,13.5 15.35,6.36 15.35,6.36C15.45,6.18 15.57,6 15.85,6M7.5,8.57H9.39C9.67,8.57 9.81,8.75 9.9,8.92L11.19,11.17C11.12,11.3 9.17,14.75 9.17,14.75C9.07,14.92 8.94,15.11 8.66,15.11H6.78C6.67,15.11 6.59,15.06 6.54,15C6.5,14.9 6.5,14.8 6.54,14.69L8.53,11.17L7.27,9C7.21,8.87 7.2,8.77 7.25,8.69C7.3,8.61 7.39,8.57 7.5,8.57Z",
"name": "xing-circle"
},
{
"path": "M10.59,2C11.23,2 11.5,2.27 11.58,2.97L11.79,6.14L12.03,10.29C12.05,10.64 12,11 11.86,11.32C11.64,11.77 11.14,11.89 10.73,11.58C10.5,11.39 10.31,11.14 10.15,10.87L6.42,4.55C6.06,3.94 6.17,3.54 6.77,3.16C7.5,2.68 9.73,2 10.59,2M14.83,14.85L15.09,14.91L18.95,16.31C19.61,16.55 19.79,16.92 19.5,17.57C19.06,18.7 18.34,19.66 17.42,20.45C16.96,20.85 16.5,20.78 16.21,20.28L13.94,16.32C13.55,15.61 14.03,14.8 14.83,14.85M4.5,14C4.5,13.26 4.5,12.55 4.75,11.87C4.97,11.2 5.33,11 6,11.27L9.63,12.81C10.09,13 10.35,13.32 10.33,13.84C10.3,14.36 9.97,14.58 9.53,14.73L5.85,15.94C5.15,16.17 4.79,15.96 4.64,15.25C4.55,14.83 4.47,14.4 4.5,14M11.97,21C11.95,21.81 11.6,22.12 10.81,22C9.77,21.8 8.81,21.4 7.96,20.76C7.54,20.44 7.45,19.95 7.76,19.53L10.47,15.97C10.7,15.67 11.03,15.6 11.39,15.74C11.77,15.88 11.97,16.18 11.97,16.59V21M14.45,13.32C13.73,13.33 13.23,12.5 13.64,11.91C14.47,10.67 15.35,9.46 16.23,8.26C16.5,7.85 16.94,7.82 17.31,8.16C18.24,9 18.91,10 19.29,11.22C19.43,11.67 19.25,12.08 18.83,12.2L15.09,13.17L14.45,13.32Z",
"name": "yelp"
}
]

View File

@@ -8,6 +8,7 @@ import {
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager";
@@ -28,7 +29,7 @@ import {
getLovelaceCollection,
LovelaceConfig,
} from "../../../../src/data/lovelace";
import "../../../../src/layouts/loading-screen";
import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout";
import "@material/mwc-button/mwc-button";
@@ -41,13 +42,13 @@ class HcCast extends LitElement {
@property() public castManager!: CastManager;
@property() private askWrite = false;
@internalProperty() private askWrite = false;
@property() private lovelaceConfig?: LovelaceConfig | null;
@internalProperty() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) {
return html` <loading-screen></loading-screen>> `;
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
}
const error =

View File

@@ -17,8 +17,8 @@ import {
customElement,
html,
LitElement,
property,
TemplateResult,
internalProperty,
} from "lit-element";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
@@ -27,7 +27,7 @@ import {
saveTokens,
} from "../../../../src/common/auth/token_storage";
import "../../../../src/components/ha-icon";
import "../../../../src/layouts/loading-screen";
import "../../../../src/layouts/hass-loading-screen";
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
import "./hc-layout";
@@ -60,19 +60,19 @@ const INTRO = html`
@customElement("hc-connect")
export class HcConnect extends LitElement {
@property() private loading = false;
@internalProperty() private loading = false;
// If we had stored credentials but we cannot connect,
// show a screen asking retry or logout.
@property() private cannotConnect = false;
@internalProperty() private cannotConnect = false;
@property() private error?: string | TemplateResult;
@internalProperty() private error?: string | TemplateResult;
@property() private auth?: Auth;
@internalProperty() private auth?: Auth;
@property() private connection?: Connection;
@internalProperty() private connection?: Connection;
@property() private castManager?: CastManager | null;
@internalProperty() private castManager?: CastManager | null;
private openDemo = false;
@@ -98,7 +98,7 @@ export class HcConnect extends LitElement {
}
if (this.castManager === undefined || this.loading) {
return html` <loading-screen></loading-screen> `;
return html` <hass-loading-screen no-toolbar></hass-loading-screen> `;
}
if (this.castManager === null) {

View File

@@ -1,4 +1,10 @@
import { customElement, html, property, TemplateResult } from "lit-element";
import {
customElement,
html,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import {
@@ -13,9 +19,9 @@ import "./hc-lovelace";
@customElement("hc-demo")
class HcDemo extends HassElement {
@property() public lovelacePath!: string;
@property({ attribute: false }) public lovelacePath!: string;
@property() private _lovelaceConfig?: LovelaceConfig;
@internalProperty() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult {
if (!this._lovelaceConfig) {

View File

@@ -11,7 +11,7 @@ import { HomeAssistant } from "../../../../src/types";
@customElement("hc-launch-screen")
class HcLaunchScreen extends LitElement {
@property() public hass?: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public error?: string;

View File

@@ -16,12 +16,14 @@ import "./hc-launch-screen";
@customElement("hc-lovelace")
class HcLovelace extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public lovelaceConfig!: LovelaceConfig;
@property({ attribute: false }) public lovelaceConfig!: LovelaceConfig;
@property() public viewPath?: string | number;
public urlPath?: string | null;
protected render(): TemplateResult {
const index = this._viewIndex;
if (index === undefined) {
@@ -35,6 +37,7 @@ class HcLovelace extends LitElement {
const lovelace: Lovelace = {
config: this.lovelaceConfig,
editMode: false,
urlPath: this.urlPath!,
enableFullEditMode: () => undefined,
mode: "storage",
language: "en",

View File

@@ -3,7 +3,12 @@ import {
getAuth,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import { customElement, html, property, TemplateResult } from "lit-element";
import {
customElement,
html,
internalProperty,
TemplateResult,
} from "lit-element";
import { CAST_NS } from "../../../../src/cast/const";
import {
ConnectMessage,
@@ -31,13 +36,13 @@ let resourcesLoaded = false;
@customElement("hc-main")
export class HcMain extends HassElement {
@property() private _showDemo = false;
@internalProperty() private _showDemo = false;
@property() private _lovelaceConfig?: LovelaceConfig;
@internalProperty() private _lovelaceConfig?: LovelaceConfig;
@property() private _lovelacePath: string | number | null = null;
@internalProperty() private _lovelacePath: string | number | null = null;
@property() private _error?: string;
@internalProperty() private _error?: string;
private _unsubLovelace?: UnsubscribeFunc;
@@ -82,6 +87,7 @@ export class HcMain extends HassElement {
.hass=${this.hass}
.lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath}
.urlPath=${this._urlPath}
@config-refresh=${this._generateLovelaceConfig}
></hc-lovelace>
`;

View File

@@ -4,7 +4,7 @@ import {
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager";
@@ -20,7 +20,7 @@ import { HomeAssistant } from "../../../src/types";
class CastDemoRow extends LitElement implements LovelaceRow {
public hass!: HomeAssistant;
@property() private _castManager?: CastManager | null;
@internalProperty() private _castManager?: CastManager | null;
public setConfig(_config: CastConfig): void {
// No config possible.
@@ -52,7 +52,6 @@ class CastDemoRow extends LitElement implements LovelaceRow {
});
mgr.castContext.addEventListener(
// eslint-disable-next-line no-undef
// @ts-ignore
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
(ev) => {
// On Android, opening a new session always results in SESSION_RESUMED.

View File

@@ -5,6 +5,7 @@ import {
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
@@ -21,11 +22,11 @@ import {
} from "../configs/demo-configs";
export class HADemoCard extends LitElement implements LovelaceCard {
@property() public lovelace?: Lovelace;
@property({ attribute: false }) public lovelace?: Lovelace;
@property() public hass!: MockHomeAssistant;
@property({ attribute: false }) public hass!: MockHomeAssistant;
@property() private _switching?: boolean;
@internalProperty() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card;

View File

@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card";
import "../../../src/dialogs/more-info/controls/more-info-content";
import "../../../src/state-summary/state-card-content";
import "./more-info-content";
class DemoMoreInfo extends PolymerElement {
static get template() {

View File

@@ -0,0 +1,73 @@
import { HassEntity } from "home-assistant-js-websocket";
import { property, PropertyValues, UpdatingElement } from "lit-element";
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type";
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
import "../../../src/dialogs/more-info/controls/more-info-automation";
import "../../../src/dialogs/more-info/controls/more-info-camera";
import "../../../src/dialogs/more-info/controls/more-info-climate";
import "../../../src/dialogs/more-info/controls/more-info-configurator";
import "../../../src/dialogs/more-info/controls/more-info-counter";
import "../../../src/dialogs/more-info/controls/more-info-cover";
import "../../../src/dialogs/more-info/controls/more-info-default";
import "../../../src/dialogs/more-info/controls/more-info-fan";
import "../../../src/dialogs/more-info/controls/more-info-group";
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
import "../../../src/dialogs/more-info/controls/more-info-light";
import "../../../src/dialogs/more-info/controls/more-info-lock";
import "../../../src/dialogs/more-info/controls/more-info-media_player";
import "../../../src/dialogs/more-info/controls/more-info-person";
import "../../../src/dialogs/more-info/controls/more-info-script";
import "../../../src/dialogs/more-info/controls/more-info-sun";
import "../../../src/dialogs/more-info/controls/more-info-timer";
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
import "../../../src/dialogs/more-info/controls/more-info-weather";
import { HomeAssistant } from "../../../src/types";
class MoreInfoContent extends UpdatingElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public stateObj?: HassEntity;
private _detachedChild?: ChildNode;
protected firstUpdated(): void {
this.style.position = "relative";
this.style.display = "block";
}
// This is not a lit element, but an updating element, so we implement update
protected update(changedProps: PropertyValues): void {
super.update(changedProps);
const stateObj = this.stateObj;
const hass = this.hass;
if (!stateObj || !hass) {
if (this.lastChild) {
this._detachedChild = this.lastChild;
// Detach child to prevent it from doing work.
this.removeChild(this.lastChild);
}
return;
}
if (this._detachedChild) {
this.appendChild(this._detachedChild);
this._detachedChild = undefined;
}
const moreInfoType =
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
? stateObj.attributes.custom_ui_more_info
: "more-info-" + stateMoreInfoType(stateObj);
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
hass,
stateObj,
});
}
}
customElements.define("more-info-content", MoreInfoContent);

View File

@@ -420,15 +420,6 @@ export default {
last_changed: "2018-07-19T10:44:46.105940+00:00",
last_updated: "2018-07-19T10:44:46.105940+00:00",
},
"weblink.router": {
entity_id: "weblink.router",
state: "http://192.168.1.1",
attributes: {
friendly_name: "Router",
},
last_changed: "2018-07-19T10:44:46.107286+00:00",
last_updated: "2018-07-19T10:44:46.107286+00:00",
},
"group.all_plants": {
entity_id: "group.all_plants",
state: "ok",
@@ -1090,18 +1081,6 @@ export default {
last_changed: "2018-07-19T10:44:46.510448+00:00",
last_updated: "2018-07-19T10:44:46.510448+00:00",
},
"history_graph.recent_switches": {
entity_id: "history_graph.recent_switches",
state: "unknown",
attributes: {
hours_to_show: 1,
refresh: 60,
entity_id: ["switch.ac", "switch.decorative_lights"],
friendly_name: "Recent Switches",
},
last_changed: "2018-07-19T10:44:46.512351+00:00",
last_updated: "2018-07-19T10:44:46.512351+00:00",
},
"scene.switch_on_and_off": {
entity_id: "scene.switch_on_and_off",
state: "scening",

View File

@@ -3,10 +3,10 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card";
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
import "../../../src/dialogs/more-info/controls/more-info-content";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos";
import "../components/more-info-content";
const ENTITIES = [
getEntity("light", "bed_light", "on", {

View File

@@ -21,7 +21,7 @@ import { filterAndSort } from "../components/hassio-filter-addons";
import { hassioStyle } from "../resources/hassio-style";
class HassioAddonRepositoryEl extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public repo!: HassioAddonRepository;

View File

@@ -1,9 +1,11 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import {
css,
CSSResult,
internalProperty,
LitElement,
property,
PropertyValues,
@@ -18,8 +20,9 @@ import {
HassioAddonRepository,
reloadHassioAddons,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/loading-screen";
import { HomeAssistant, Route } from "../../../src/types";
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
import { supervisorTabs } from "../hassio-tabs";
@@ -52,7 +55,7 @@ class HassioAddonStore extends LitElement {
@property({ attribute: false }) private _repos?: HassioAddonRepository[];
@property() private _filter?: string;
@internalProperty() private _filter?: string;
public async refreshData() {
this._repos = undefined;
@@ -96,19 +99,23 @@ class HassioAddonStore extends LitElement {
.tabs=${supervisorTabs}
>
<span slot="header">Add-on store</span>
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item @tap=${this._manageRepositories}>
<mwc-list-item>
Repositories
</mwc-list-item>
<mwc-list-item @tap=${this.refreshData}>
<mwc-list-item>
Reload
</mwc-list-item>
</ha-button-menu>
${repos.length === 0
? html`<loading-screen></loading-screen>`
? html`<hass-loading-screen no-toolbar></hass-loading-screen>`
: html`
<div class="search">
<search-input
@@ -142,6 +149,17 @@ class HassioAddonStore extends LitElement {
this._loadData();
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._manageRepositories();
break;
case 1:
this.refreshData();
break;
}
}
private apiCalled(ev) {
if (ev.detail.success) {
this._loadData();
@@ -162,7 +180,7 @@ class HassioAddonStore extends LitElement {
this._repos.sort(sortRepos);
this._addons = addonsInfo.addons;
} catch (err) {
alert("Failed to fetch add-on info");
alert(extractApiErrorMessage(err));
}
}

View File

@@ -9,6 +9,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -27,6 +28,7 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/buttons/ha-progress-button";
@customElement("hassio-addon-audio")
class HassioAddonAudio extends LitElement {
@@ -34,15 +36,15 @@ class HassioAddonAudio extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property() private _inputDevices?: HassioHardwareAudioDevice[];
@internalProperty() private _inputDevices?: HassioHardwareAudioDevice[];
@property() private _outputDevices?: HassioHardwareAudioDevice[];
@internalProperty() private _outputDevices?: HassioHardwareAudioDevice[];
@property() private _selectedInput!: null | string;
@internalProperty() private _selectedInput!: null | string;
@property() private _selectedOutput!: null | string;
@internalProperty() private _selectedOutput!: null | string;
protected render(): TemplateResult {
return html`
@@ -90,7 +92,9 @@ class HassioAddonAudio extends LitElement {
</paper-dropdown-menu>
</div>
<div class="card-actions">
<mwc-button @click=${this._saveSettings}>Save</mwc-button>
<ha-progress-button @click=${this._saveSettings}>
Save
</ha-progress-button>
</div>
</ha-card>
`;
@@ -171,7 +175,10 @@ class HassioAddonAudio extends LitElement {
}
}
private async _saveSettings(): Promise<void> {
private async _saveSettings(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
this._error = undefined;
const data: HassioAddonSetOptionParams = {
audio_input:
@@ -181,12 +188,14 @@ class HassioAddonAudio extends LitElement {
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch {
this._error = "Failed to set addon audio device";
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
button.progress = false;
}
}

View File

@@ -5,6 +5,7 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
@@ -12,6 +13,7 @@ import {
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
@@ -20,6 +22,7 @@ import {
HassioAddonSetOptionParams,
setHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
@@ -32,7 +35,7 @@ class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property({ type: Boolean }) private _configHasChanged = false;
@@ -54,20 +57,103 @@ class HassioAddonConfig extends LitElement {
${valid ? "" : html` <div class="errors">Invalid YAML</div> `}
</div>
<div class="card-actions">
<mwc-button class="warning" @click=${this._resetTapped}>
<ha-progress-button class="warning" @click=${this._resetTapped}>
Reset to defaults
</mwc-button>
<mwc-button
</ha-progress-button>
<ha-progress-button
@click=${this._saveTapped}
.disabled=${!this._configHasChanged || !valid}
>
Save
</mwc-button>
</ha-progress-button>
</div>
</ha-card>
`;
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("addon")) {
this._editor.setValue(this.addon.options);
}
}
private _configChanged(): void {
this._configHasChanged = true;
this.requestUpdate();
}
private async _resetTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.addon.name,
text: "Are you sure you want to reset all your options?",
confirmText: "reset options",
dismissText: "no",
});
if (!confirmed) {
button.progress = false;
return;
}
this._error = undefined;
const data: HassioAddonSetOptionParams = {
options: null,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to reset addon configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
let data: HassioAddonSetOptionParams;
this._error = undefined;
try {
data = {
options: this._editor.value,
};
} catch (err) {
this._error = err;
return;
}
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch (err) {
this._error = `Failed to save addon configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
static get styles(): CSSResult[] {
return [
haStyle,
@@ -97,80 +183,6 @@ class HassioAddonConfig extends LitElement {
`,
];
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("addon")) {
this._editor.setValue(this.addon.options);
}
}
private _configChanged(): void {
this._configHasChanged = true;
this.requestUpdate();
}
private async _resetTapped(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.addon.name,
text: "Are you sure you want to reset all your options?",
confirmText: "reset options",
dismissText: "no",
});
if (!confirmed) {
return;
}
this._error = undefined;
const data: HassioAddonSetOptionParams = {
options: null,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to reset addon configuration, ${
err.body?.message || err
}`;
}
}
private async _saveTapped(): Promise<void> {
let data: HassioAddonSetOptionParams;
this._error = undefined;
try {
data = {
options: this._editor.value,
};
} catch (err) {
this._error = err;
return;
}
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to save addon configuration, ${
err.body?.message || err
}`;
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
}
}
declare global {

View File

@@ -4,18 +4,21 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
HassioAddonSetOptionParams,
setHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
@@ -37,9 +40,9 @@ class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property() private _config?: NetworkItem[];
@internalProperty() private _config?: NetworkItem[];
public connectedCallback(): void {
super.connectedCallback();
@@ -71,7 +74,7 @@ class HassioAddonNetwork extends LitElement {
<paper-input
@value-changed=${this._configChanged}
placeholder="disabled"
.value=${String(item.host)}
.value=${item.host ? String(item.host) : ""}
.container=${item.container}
no-label-float
></paper-input>
@@ -84,38 +87,17 @@ class HassioAddonNetwork extends LitElement {
</table>
</div>
<div class="card-actions">
<mwc-button class="warning" @click=${this._resetTapped}>
<ha-progress-button class="warning" @click=${this._resetTapped}>
Reset to defaults
</mwc-button>
<mwc-button @click=${this._saveTapped}>Save</mwc-button>
</ha-progress-button>
<ha-progress-button @click=${this._saveTapped}>
Save
</ha-progress-button>
</div>
</ha-card>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
}
`,
];
}
protected update(changedProperties: PropertyValues): void {
super.update(changedProperties);
if (changedProperties.has("addon")) {
@@ -148,7 +130,10 @@ class HassioAddonNetwork extends LitElement {
});
}
private async _resetTapped(): Promise<void> {
private async _resetTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const data: HassioAddonSetOptionParams = {
network: null,
};
@@ -161,17 +146,22 @@ class HassioAddonNetwork extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch (err) {
this._error = `Failed to set addon network configuration, ${
err.body?.message || err
}`;
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
private async _saveTapped(): Promise<void> {
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
this._error = undefined;
const networkconfiguration = {};
this._config!.forEach((item) => {
@@ -190,14 +180,38 @@ class HassioAddonNetwork extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch (err) {
this._error = `Failed to set addon network configuration, ${
err.body?.message || err
}`;
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
}
`,
];
}
}

View File

@@ -3,17 +3,19 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import {
fetchHassioAddonDocumentation,
HassioAddonDetails,
} from "../../../../src/data/hassio/addon";
import "../../../../src/layouts/loading-screen";
import "../../../../src/components/ha-circular-progress";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import "../../../../src/layouts/hass-loading-screen";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
@@ -24,9 +26,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
@property({ attribute: false }) public addon?: HassioAddonDetails;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property() private _content?: string;
@internalProperty() private _content?: string;
public async connectedCallback(): Promise<void> {
super.connectedCallback();
@@ -44,7 +46,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
<div class="card-content">
${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>`
: html`<loading-screen></loading-screen>`}
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
</div>
</ha-card>
</div>
@@ -79,9 +81,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
this.addon!.slug
);
} catch (err) {
this._error = `Failed to get addon documentation, ${
err.body?.message || err
}`;
this._error = `Failed to get addon documentation, ${extractApiErrorMessage(
err
)}`;
}
}
}

View File

@@ -9,18 +9,17 @@ import {
mdiExclamationThick,
mdiFlask,
mdiHomeAssistant,
mdiInformation,
mdiKey,
mdiNetwork,
mdiPound,
mdiShield,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
@@ -34,19 +33,27 @@ import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
fetchHassioAddonChangelog,
fetchHassioAddonInfo,
HassioAddonDetails,
HassioAddonSetOptionParams,
HassioAddonSetSecurityParams,
installHassioAddon,
setHassioAddonOption,
setHassioAddonSecurity,
startHassioAddon,
uninstallHassioAddon,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-card-content";
@@ -124,9 +131,7 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property() private _error?: string;
@property({ type: Boolean }) private _installing = false;
@internalProperty() private _error?: string;
protected render(): TemplateResult {
return html`
@@ -241,19 +246,23 @@ class HassioAddonInfo extends LitElement {
`
: ""}
<div class="security">
<ha-label-badge
class=${classMap({
green: this.addon.stage === "stable",
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
label="stage"
description=""
>
<ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
</ha-label-badge>
${this.addon.stage !== "stable"
? html` <ha-label-badge
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
label="stage"
description=""
>
<ha-svg-icon
.path=${STAGE_ICON[this.addon.stage]}
></ha-svg-icon>
</ha-label-badge>`
: ""}
<ha-label-badge
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
@@ -381,67 +390,94 @@ class HassioAddonInfo extends LitElement {
${this.addon.version
? html`
<div class="state">
<div>Start on boot</div>
<ha-switch
@change=${this._startOnBootToggled}
.checked=${this.addon.boot === "auto"}
haptic
></ha-switch>
</div>
${this.addon.auto_update || this.hass.userData?.showAdvanced
? html`
<div class="state">
<div>Auto update</div>
<ha-switch
@change=${this._autoUpdateToggled}
.checked=${this.addon.auto_update}
haptic
></ha-switch>
</div>
`
: ""}
${this.addon.ingress
? html`
<div class="state">
<div>Show in sidebar</div>
<ha-switch
@change=${this._panelToggled}
.checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar}
haptic
></ha-switch>
${this._computeCannotIngressSidebar
? html`
<span>
This option requires Home Assistant 0.92 or
later.
</span>
`
: ""}
</div>
`
: ""}
${this._computeUsesProtectedOptions
? html`
<div class="state">
<div>
Protection mode
<span>
<ha-svg-icon path=${mdiInformation}></ha-svg-icon>
<paper-tooltip>
Grant the add-on elevated system access.
</paper-tooltip>
<div class="addon-options">
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Start on boot
</span>
<span slot="description">
Make the add-on start during a system boot
</span>
<ha-switch
@change=${this._startOnBootToggled}
.checked=${this.addon.boot === "auto"}
haptic
></ha-switch>
</ha-settings-row>
${this.addon.startup !== "once"
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Watchdog
</span>
</div>
<ha-switch
@change=${this._protectionToggled}
.checked=${this.addon.protected}
haptic
></ha-switch>
</div>
`
: ""}
<span slot="description">
This will start the add-on if it crashes
</span>
<ha-switch
@change=${this._watchdogToggled}
.checked=${this.addon.watchdog}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.auto_update || this.hass.userData?.showAdvanced
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Auto update
</span>
<span slot="description">
Auto update the add-on when there is a new version
available
</span>
<ha-switch
@change=${this._autoUpdateToggled}
.checked=${this.addon.auto_update}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.ingress
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Show in sidebar
</span>
<span slot="description">
${this._computeCannotIngressSidebar
? "This option requires Home Assistant 0.92 or later."
: "Add this add-on to your sidebar"}
</span>
<ha-switch
@change=${this._panelToggled}
.checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this._computeUsesProtectedOptions
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Protection mode
</span>
<span slot="description">
Blocks elevated system access from the add-on
</span>
<ha-switch
@change=${this._protectionToggled}
.checked=${this.addon.protected}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
</div>
`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -467,12 +503,9 @@ class HassioAddonInfo extends LitElement {
</ha-call-api-button>
`
: html`
<ha-call-api-button
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/start"
>
<ha-progress-button @click=${this._startClicked}>
Start
</ha-call-api-button>
</ha-progress-button>
`}
${this._computeShowWebUI
? html`
@@ -496,12 +529,12 @@ class HassioAddonInfo extends LitElement {
</mwc-button>
`
: ""}
<mwc-button
<ha-progress-button
class=" right warning"
@click=${this._uninstallClicked}
>
Uninstall
</mwc-button>
</ha-progress-button>
${this.addon.build
? html`
<ha-call-api-button
@@ -523,8 +556,7 @@ class HassioAddonInfo extends LitElement {
`
: ""}
<ha-progress-button
.disabled=${!this.addon.available || this._installing}
.progress=${this._installing}
.disabled=${!this.addon.available}
@click=${this._installClicked}
>
Install
@@ -547,137 +579,6 @@ class HassioAddonInfo extends LitElement {
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
margin-bottom: 16px;
}
ha-card.warning {
background-color: var(--error-color);
color: white;
}
ha-card.warning .card-header {
color: white;
}
ha-card.warning .card-content {
color: white;
}
ha-card.warning mwc-button {
--mdc-theme-primary: white !important;
}
.warning {
color: var(--error-color);
--mdc-theme-primary: var(--error-color);
}
.light-color {
color: var(--secondary-text-color);
}
.addon-header {
padding-left: 8px;
font-size: 24px;
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.description {
margin-bottom: 16px;
}
img.logo {
max-height: 60px;
margin: 16px 0;
display: block;
}
.state {
display: flex;
margin: 33px 0;
}
.state div {
width: 180px;
display: inline-block;
}
.state ha-svg-icon {
width: 16px;
height: 16px;
color: var(--secondary-text-color);
}
ha-switch {
display: flex;
}
ha-svg-icon.running {
color: var(--paper-green-400);
}
ha-svg-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a {
color: var(--primary-color);
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.card-actions {
display: flow-root;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
.changelog-link {
color: var(--primary-color);
text-decoration: underline;
cursor: pointer;
}
ha-markdown {
padding: 16px;
}
`,
];
}
private get _computeHassioApi(): boolean {
return (
this.addon.hassio_api &&
@@ -762,7 +663,29 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${err.body?.message || err}`;
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
private async _watchdogToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
watchdog: !this.addon.watchdog,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -780,7 +703,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${err.body?.message || err}`;
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -798,9 +723,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon security option, ${
err.body?.message || err
}`;
this._error = `Failed to set addon security option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -818,12 +743,13 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${err.body?.message || err}`;
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
private async _openChangelog(): Promise<void> {
this._error = undefined;
try {
const content = await fetchHassioAddonChangelog(
this.hass,
@@ -834,15 +760,17 @@ class HassioAddonInfo extends LitElement {
content,
});
} catch (err) {
this._error = `Failed to get addon changelog, ${
err.body?.message || err
}`;
showAlertDialog(this, {
title: "Failed to get addon changelog",
text: extractApiErrorMessage(err),
});
}
}
private async _installClicked(): Promise<void> {
this._error = undefined;
this._installing = true;
private async _installClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
await installHassioAddon(this.hass, this.addon.slug);
const eventdata = {
@@ -852,12 +780,62 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to install addon, ${err.body?.message || err}`;
showAlertDialog(this, {
title: "Failed to install addon",
text: extractApiErrorMessage(err),
});
}
this._installing = false;
button.progress = false;
}
private async _uninstallClicked(): Promise<void> {
private async _startClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
const validate = await validateHassioAddonOption(
this.hass,
this.addon.slug
);
if (!validate.data.valid) {
await showConfirmationDialog(this, {
title: "Failed to start addon - configruation validation faled!",
text: validate.data.message.split(" Got ")[0],
confirm: () => this._openConfiguration(),
confirmText: "Go to configruation",
dismissText: "Cancel",
});
button.progress = false;
return;
}
} catch (err) {
showAlertDialog(this, {
title: "Failed to validate addon configuration",
text: extractApiErrorMessage(err),
});
button.progress = false;
return;
}
try {
await startHassioAddon(this.hass, this.addon.slug);
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
} catch (err) {
showAlertDialog(this, {
title: "Failed to start addon",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private _openConfiguration(): void {
navigate(this, `/hassio/addon/${this.addon.slug}/config`);
}
private async _uninstallClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.addon.name,
text: "Are you sure you want to uninstall this add-on?",
@@ -866,6 +844,7 @@ class HassioAddonInfo extends LitElement {
});
if (!confirmed) {
button.progress = false;
return;
}
@@ -879,8 +858,152 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to uninstall addon, ${err.body?.message || err}`;
showAlertDialog(this, {
title: "Failed to uninstall addon",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
margin-bottom: 16px;
}
ha-card.warning {
background-color: var(--error-color);
color: white;
}
ha-card.warning .card-header {
color: white;
}
ha-card.warning .card-content {
color: white;
}
ha-card.warning mwc-button {
--mdc-theme-primary: white !important;
}
.warning {
color: var(--error-color);
--mdc-theme-primary: var(--error-color);
}
.light-color {
color: var(--secondary-text-color);
}
.addon-header {
padding-left: 8px;
font-size: 24px;
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.description {
margin-bottom: 16px;
}
img.logo {
max-height: 60px;
margin: 16px 0;
display: block;
}
ha-switch {
display: flex;
}
ha-svg-icon.running {
color: var(--paper-green-400);
}
ha-svg-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a {
color: var(--primary-color);
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.card-actions {
display: flow-root;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
.changelog-link {
color: var(--primary-color);
text-decoration: underline;
cursor: pointer;
}
ha-markdown {
padding: 16px;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row > span[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
ha-settings-row[three-line] {
height: 74px;
}
.addon-options {
max-width: 50%;
}
@media (max-width: 720px) {
.addon-options {
max-width: 100%;
}
}
`,
];
}
}
declare global {

View File

@@ -4,6 +4,7 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
@@ -13,6 +14,7 @@ import {
fetchHassioAddonLogs,
HassioAddonDetails,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-ansi-to-html";
@@ -24,9 +26,9 @@ class HassioAddonLogs extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property() private _content?: string;
@internalProperty() private _content?: string;
public async connectedCallback(): Promise<void> {
super.connectedCallback();
@@ -74,7 +76,7 @@ class HassioAddonLogs extends LitElement {
try {
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
} catch (err) {
this._error = `Failed to get addon logs, ${err.body?.message || err}`;
this._error = `Failed to get addon logs, ${extractApiErrorMessage(err)}`;
}
}

View File

@@ -21,7 +21,7 @@ interface State {
class HassioAnsiToHtml extends LitElement {
@property() public content!: string;
public render(): TemplateResult | void {
protected render(): TemplateResult | void {
return html`${this._parseTextToColoredPre(this.content)}`;
}

View File

@@ -14,7 +14,7 @@ import { HomeAssistant } from "../../../src/types";
@customElement("hassio-card-content")
class HassioCardContent extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public title!: string;

View File

@@ -19,7 +19,7 @@ import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-addons")
class HassioAddons extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public addons?: HassioAddonInfo[];

View File

@@ -5,33 +5,43 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import {
extractApiErrorMessage,
HassioResponse,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-update")
export class HassioUpdate extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public hassInfo: HassioHomeAssistantInfo;
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
@property() public hassOsInfo?: HassioHassOSInfo;
@property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo;
@property() public supervisorInfo: HassioSupervisorInfo;
@property() private _error?: string;
@internalProperty() private _error?: string;
protected render(): TemplateResult {
const updatesAvailable: number = [
@@ -125,31 +135,46 @@ export class HassioUpdate extends LitElement {
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
<mwc-button>Release notes</mwc-button>
</a>
<ha-call-api-button
.hass=${this.hass}
.path=${apiPath}
@hass-api-called=${this._apiCalled}
<ha-progress-button
.apiPath=${apiPath}
.name=${name}
.version=${lastVersion}
@click=${this._confirmUpdate}
>
Update
</ha-call-api-button>
</ha-progress-button>
</div>
</ha-card>
`;
}
private _apiCalled(ev): void {
if (ev.detail.success) {
this._error = "";
private async _confirmUpdate(ev): Promise<void> {
const item = ev.currentTarget;
item.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: `Update ${item.name}`,
text: `Are you sure you want to upgrade ${item.name} to version ${item.version}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
item.progress = false;
return;
}
const response = ev.detail.response;
if (typeof response.body === "object") {
this._error = response.body.message || "Unknown error";
} else {
this._error = response.body;
try {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
} catch (err) {
// Only show an error if the status code was not expected (user behind proxy)
// or no status at all(connection terminated)
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: "Update failed",
text: extractApiErrorMessage(err),
});
}
}
item.progress = false;
}
static get styles(): CSSResult[] {

View File

@@ -5,6 +5,7 @@ import {
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
@@ -16,13 +17,13 @@ import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
@customElement("dialog-hassio-markdown")
class HassioMarkdownDialog extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public title!: string;
@property() public content!: string;
@property() private _opened = false;
@internalProperty() private _opened = false;
public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title;
@@ -30,6 +31,10 @@ class HassioMarkdownDialog extends LitElement {
this._opened = true;
}
public closeDialog() {
this._opened = false;
}
protected render(): TemplateResult {
if (!this._opened) {
return html``;
@@ -37,7 +42,7 @@ class HassioMarkdownDialog extends LitElement {
return html`
<ha-dialog
open
@closing=${this._closeDialog}
@closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, this.title)}
>
<ha-markdown .content=${this.content || ""}></ha-markdown>
@@ -45,10 +50,6 @@ class HassioMarkdownDialog extends LitElement {
`;
}
private _closeDialog(): void {
this._opened = false;
}
static get styles(): CSSResult[] {
return [
haStyleDialog,

View File

@@ -0,0 +1,333 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button";
import "@material/mwc-tab";
import "@material/mwc-tab-bar";
import { mdiClose } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
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 "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-radio";
import type { HaRadio } from "../../../../src/components/ha-radio";
import "../../../../src/components/ha-related-items";
import "../../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
NetworkInterface,
updateNetworkInterface,
} from "../../../../src/data/hassio/network";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { HassioNetworkDialogParams } from "./show-dialog-network";
@customElement("dialog-hassio-network")
export class DialogHassioNetwork extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _prosessing = false;
@internalProperty() private _params?: HassioNetworkDialogParams;
@internalProperty() private _network!: {
interface: string;
data: NetworkInterface;
}[];
@internalProperty() private _curTabIndex = 0;
@internalProperty() private _device?: {
interface: string;
data: NetworkInterface;
};
@internalProperty() private _dirty = false;
public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
this._params = params;
this._dirty = false;
this._curTabIndex = 0;
this._network = Object.keys(params.network?.interfaces)
.map((device) => ({
interface: device,
data: params.network.interfaces[device],
}))
.sort((a, b) => {
return a.data.primary > b.data.primary ? -1 : 1;
});
this._device = this._network[this._curTabIndex];
this._device.data.nameservers = String(this._device.data.nameservers);
await this.updateComplete;
}
public closeDialog(): void {
this._params = undefined;
this._prosessing = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._params || !this._network) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
.heading=${true}
hideActions
@closed=${this.closeDialog}
>
<div slot="heading">
<ha-header-bar>
<span slot="title">
Network settings
</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
${this._network.length > 1
? html` <mwc-tab-bar
.activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated}
>${this._network.map(
(device) =>
html`<mwc-tab
.id=${device.interface}
.label=${device.interface}
>
</mwc-tab>`
)}
</mwc-tab-bar>`
: ""}
</div>
${cache(this._renderTab())}
</ha-dialog>
`;
}
private _renderTab() {
return html` <div class="form container">
<ha-formfield label="DHCP">
<ha-radio
@change=${this._handleRadioValueChanged}
value="dhcp"
name="method"
?checked=${this._device!.data.method === "dhcp"}
>
</ha-radio>
</ha-formfield>
<ha-formfield label="Static">
<ha-radio
@change=${this._handleRadioValueChanged}
value="static"
name="method"
?checked=${this._device!.data.method === "static"}
>
</ha-radio>
</ha-formfield>
${this._device!.data.method !== "dhcp"
? html` <paper-input
class="flex-auto"
id="ip_address"
label="IP address/Netmask"
.value="${this._device!.data.ip_address}"
@value-changed=${this._handleInputValueChanged}
></paper-input>
<paper-input
class="flex-auto"
id="gateway"
label="Gateway address"
.value="${this._device!.data.gateway}"
@value-changed=${this._handleInputValueChanged}
></paper-input>
<paper-input
class="flex-auto"
id="nameservers"
label="DNS servers"
.value="${this._device!.data.nameservers as string}"
@value-changed=${this._handleInputValueChanged}
></paper-input>
NB!: If you are changing IP or gateway addresses, you might lose
the connection.`
: ""}
</div>
<div class="buttons">
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
<mwc-button @click=${this._updateNetwork} ?disabled=${!this._dirty}>
${this._prosessing
? html`<ha-circular-progress active></ha-circular-progress>`
: "Update"}
</mwc-button>
</div>`;
}
private async _updateNetwork() {
this._prosessing = true;
let options: Partial<NetworkInterface> = {
method: this._device!.data.method,
};
if (options.method !== "dhcp") {
options = {
...options,
address: this._device!.data.ip_address,
gateway: this._device!.data.gateway,
dns: String(this._device!.data.nameservers).split(","),
};
}
try {
await updateNetworkInterface(this.hass, this._device!.interface, options);
} catch (err) {
showAlertDialog(this, {
title: "Failed to change network settings",
text: extractApiErrorMessage(err),
});
this._prosessing = false;
return;
}
this._params?.loadData();
this.closeDialog();
}
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
if (this._dirty) {
const confirm = await showConfirmationDialog(this, {
text:
"You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
confirmText: "yes",
dismissText: "no",
});
if (!confirm) {
this.requestUpdate("_device");
return;
}
}
this._curTabIndex = ev.detail.index;
this._device = this._network[ev.detail.index];
this._device.data.nameservers = String(this._device.data.nameservers);
}
private _handleRadioValueChanged(ev: CustomEvent): void {
const value = (ev.target as HaRadio).value as "dhcp" | "static";
if (!value || !this._device || this._device!.data.method === value) {
return;
}
this._dirty = true;
this._device!.data.method = value;
this.requestUpdate("_device");
}
private _handleInputValueChanged(ev: CustomEvent): void {
const value: string | null | undefined = (ev.target as PaperInputElement)
.value;
const id = (ev.target as PaperInputElement).id;
if (!value || !this._device || this._device.data[id] === value) {
return;
}
this._dirty = true;
this._device.data[id] = value;
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
}
mwc-tab-bar {
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
ha-dialog {
--dialog-content-position: static;
--dialog-content-padding: 0;
--dialog-z-index: 6;
}
@media all and (min-width: 451px) and (min-height: 501px) {
.container {
width: 400px;
}
}
.content {
display: block;
padding: 20px 24px;
}
/* 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);
}
}
mwc-button.warning {
--mdc-theme-primary: var(--error-color);
}
:host([rtl]) app-toolbar {
direction: rtl;
text-align: right;
}
.container {
padding: 20px 24px;
}
.form {
margin-bottom: 53px;
}
.buttons {
position: absolute;
bottom: 0;
width: 100%;
box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
display: flex;
justify-content: space-between;
padding: 8px;
padding-bottom: max(env(safe-area-inset-bottom), 8px);
background-color: var(--mdc-theme-surface, #fff);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-network": DialogHassioNetwork;
}
}

View File

@@ -0,0 +1,22 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { NetworkInfo } from "../../../../src/data/hassio/network";
import "./dialog-hassio-network";
export interface HassioNetworkDialogParams {
network: NetworkInfo;
loadData: () => Promise<void>;
}
export const showNetworkDialog = (
element: HTMLElement,
dialogParams: HassioNetworkDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-network",
dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-network" */ "./dialog-hassio-network"
),
dialogParams,
});
};

View File

@@ -5,24 +5,26 @@ import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "../../../../src/components/ha-circular-progress";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import {
fetchHassioAddonsInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
@@ -39,11 +41,11 @@ class HassioRepositoriesDialog extends LitElement {
@query("#repository_input") private _optionInput?: PaperInputElement;
@property() private _opened = false;
@internalProperty() private _opened = false;
@property() private _prosessing = false;
@internalProperty() private _prosessing = false;
@property() private _error?: string;
@internalProperty() private _error?: string;
public async showDialog(_dialogParams: any): Promise<void> {
this._dialogParams = _dialogParams;
@@ -189,7 +191,7 @@ class HassioRepositoriesDialog extends LitElement {
input.value = "";
} catch (err) {
this._error = err.message;
this._error = extractApiErrorMessage(err);
}
this._prosessing = false;
}
@@ -221,7 +223,7 @@ class HassioRepositoriesDialog extends LitElement {
await this._dialogParams!.loadData();
} catch (err) {
this._error = err.message;
this._error = extractApiErrorMessage(err);
}
}
}

View File

@@ -7,6 +7,7 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
@@ -14,10 +15,12 @@ import {
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../../../../src/polymer-types";
import { haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
@@ -68,21 +71,21 @@ interface FolderItem {
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property() private _snapshot?: HassioSnapshotDetail;
@internalProperty() private _snapshot?: HassioSnapshotDetail;
@property() private _folders!: FolderItem[];
@internalProperty() private _folders!: FolderItem[];
@property() private _addons!: AddonItem[];
@internalProperty() private _addons!: AddonItem[];
@property() private _dialogParams?: HassioSnapshotDialogParams;
@internalProperty() private _dialogParams?: HassioSnapshotDialogParams;
@property() private _snapshotPassword!: string;
@internalProperty() private _snapshotPassword!: string;
@property() private _restoreHass: boolean | null | undefined = true;
@internalProperty() private _restoreHass: boolean | null | undefined = true;
public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
@@ -265,8 +268,12 @@ class HassioSnapshotDialog extends LitElement {
this._snapshotPassword = ev.detail.value;
}
private _partialRestoreClicked() {
if (!confirm("Are you sure you want to restore this snapshot?")) {
private async _partialRestoreClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?",
}))
) {
return;
}
@@ -311,8 +318,13 @@ class HassioSnapshotDialog extends LitElement {
);
}
private _fullRestoreClicked() {
if (!confirm("Are you sure you want to restore this snapshot?")) {
private async _fullRestoreClicked() {
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this snapshot?",
}))
) {
return;
}
@@ -337,8 +349,12 @@ class HassioSnapshotDialog extends LitElement {
);
}
private _deleteClicked() {
if (!confirm("Are you sure you want to delete this snapshot?")) {
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?",
}))
) {
return;
}
@@ -364,7 +380,7 @@ class HassioSnapshotDialog extends LitElement {
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
);
} catch (err) {
alert(`Error: ${err.message}`);
alert(`Error: ${extractApiErrorMessage(err)}`);
return;
}

View File

@@ -3,6 +3,7 @@ import {
HassioAddonDetails,
restartHassioAddon,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
showAlertDialog,
showConfirmationDialog,
@@ -26,7 +27,7 @@ export const suggestAddonRestart = async (
} catch (err) {
showAlertDialog(element, {
title: "Failed to restart",
text: err.body.message,
text: extractApiErrorMessage(err),
});
}
}

View File

@@ -1,111 +1,35 @@
import { PolymerElement } from "@polymer/polymer";
import { customElement, property, PropertyValues } from "lit-element";
import {
html,
PropertyValues,
customElement,
LitElement,
property,
} from "lit-element";
import "./hassio-router";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { HomeAssistant, Route } from "../../src/types";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event";
import { navigate } from "../../src/common/navigate";
import { fetchHassioAddonInfo } from "../../src/data/hassio/addon";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
} from "../../src/data/hassio/host";
import {
createHassioSession,
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
fetchHassioInfo,
HassioHomeAssistantInfo,
HassioInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import {
AlertDialogParams,
showAlertDialog,
} from "../../src/dialogs/generic/show-dialog-box";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import {
HassRouterPage,
RouterOptions,
} from "../../src/layouts/hass-router-page";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import "../../src/resources/ha-style";
import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel";
import { atLeastVersion } from "../../src/common/config/version";
@customElement("hassio-main")
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property() public hass!: HomeAssistant;
export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public panel!: HassioPanelInfo;
@property() public narrow!: boolean;
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
initialLoad: () => this._fetchData(),
showLoading: true,
routes: {
dashboard: {
tag: "hassio-panel",
cache: true,
},
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {
tag: "hassio-addon-dashboard",
load: () =>
import(
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
),
},
ingress: {
tag: "hassio-ingress-view",
load: () =>
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
},
},
};
@property() private _supervisorInfo: HassioSupervisorInfo;
@property() private _hostInfo: HassioHostInfo;
@property() private _hassioInfo?: HassioInfo;
@property() private _hassOsInfo?: HassioHassOSInfo;
@property() private _hassInfo: HassioHomeAssistantInfo;
@property() public route?: Route;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
applyThemesOnElement(
this.parentElement,
this.hass.themes,
this.hass.selectedTheme || this.hass.themes.default_theme
);
this._applyTheme();
this.style.setProperty(
"--app-header-background-color",
"var(--sidebar-background-color)"
);
this.style.setProperty(
"--app-header-text-color",
"var(--sidebar-text-color)"
);
this.style.setProperty(
"--app-header-border-bottom",
"1px solid var(--divider-color)"
);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
// Paulus - March 17, 2019
// We went to a single hass-toggle-menu event in HA 0.90. However, the
// supervisor UI can also run under older versions of Home Assistant.
@@ -138,152 +62,61 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
});
});
makeDialogManager(this, document.body);
makeDialogManager(this, this.shadowRoot!);
}
protected updatePageEl(el) {
// the tabs page does its own routing so needs full route.
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass) {
return;
}
if (oldHass.themes !== this.hass.themes) {
this._applyTheme();
}
}
if ("setProperties" in el) {
// As long as we have Polymer pages
(el as PolymerElement).setProperties({
hass: this.hass,
narrow: this.narrow,
supervisorInfo: this._supervisorInfo,
hassioInfo: this._hassioInfo,
hostInfo: this._hostInfo,
hassInfo: this._hassInfo,
hassOsInfo: this._hassOsInfo,
route,
});
protected render() {
return html`
<hassio-router
.hass=${this.hass}
.route=${this.route}
.panel=${this.panel}
.narrow=${this.narrow}
></hassio-router>
`;
}
private _applyTheme() {
let themeName: string;
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName =
this.hass.selectedTheme?.theme ||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme);
options = this.hass.selectedTheme;
if (themeName === "default" && options?.dark === undefined) {
options = {
...this.hass.selectedTheme,
dark: this.hass.themes.darkMode,
};
}
} else {
el.hass = this.hass;
el.narrow = this.narrow;
el.supervisorInfo = this._supervisorInfo;
el.hassioInfo = this._hassioInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
el.route = route;
}
}
private async _fetchData() {
if (this.panel.config && this.panel.config.ingress) {
await this._redirectIngress(this.panel.config.ingress);
return;
themeName =
((this.hass.selectedTheme as unknown) as string) ||
this.hass.themes.default_theme;
}
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hassioInfo = hassioInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
}
}
private async _redirectIngress(addonSlug: string) {
// When we trigger a navigation, we sleep to make sure we don't
// show the hassio dashboard before navigating away.
const awaitAlert = async (
alertParams: AlertDialogParams,
action: () => void
) => {
await new Promise((resolve) => {
alertParams.confirm = resolve;
showAlertDialog(this, alertParams);
});
action();
await new Promise((resolve) => setTimeout(resolve, 1000));
};
const createSessionPromise = createHassioSession(this.hass).then(
() => true,
() => false
applyThemesOnElement(
this.parentElement,
this.hass.themes,
themeName,
options
);
let addon;
try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err) {
await awaitAlert(
{
text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor",
},
() => history.back()
);
return;
}
if (!addon.ingress_url) {
await awaitAlert(
{
text: "Add-on does not support Ingress",
title: addon.name,
},
() => history.back()
);
return;
}
if (addon.state !== "started") {
await awaitAlert(
{
text: "Add-on is not running. Please start it first",
title: addon.name,
},
() => navigate(this, `/hassio/addon/${addon.slug}/info`, true)
);
return;
}
if (!(await createSessionPromise)) {
await awaitAlert(
{
text: "Unable to create an Ingress session",
title: addon.name,
},
() => history.back()
);
return;
}
location.assign(addon.ingress_url);
// await a promise that doesn't resolve, so we show the loading screen
// while we load the next page.
await new Promise(() => undefined);
}
private _apiCalled(ev) {
if (!ev.detail.success) {
return;
}
let tries = 1;
const tryUpdate = () => {
this._fetchData().catch(() => {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
}
}

View File

@@ -4,6 +4,8 @@ import {
LitElement,
property,
TemplateResult,
css,
CSSResult,
} from "lit-element";
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
@@ -33,6 +35,9 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult {
if (!this.supervisorInfo) {
return html``;
}
return html`
<hassio-panel-router
.route=${this.route}
@@ -46,6 +51,16 @@ class HassioPanel extends LitElement {
></hassio-panel-router>
`;
}
static get styles(): CSSResult {
return css`
:host {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
--app-header-border-bottom: 1px solid var(--divider-color);
}
`;
}
}
declare global {

150
hassio/src/hassio-router.ts Normal file
View File

@@ -0,0 +1,150 @@
import {
customElement,
property,
internalProperty,
PropertyValues,
} from "lit-element";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
} from "../../src/data/hassio/host";
import {
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
fetchHassioInfo,
HassioHomeAssistantInfo,
HassioInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import {
HassRouterPage,
RouterOptions,
} from "../../src/layouts/hass-router-page";
import "../../src/resources/ha-style";
import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel";
@customElement("hassio-router")
class HassioRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public panel!: HassioPanelInfo;
@property() public narrow!: boolean;
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
initialLoad: () => this._fetchData(),
showLoading: true,
routes: {
dashboard: {
tag: "hassio-panel",
cache: true,
},
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {
tag: "hassio-addon-dashboard",
load: () =>
import(
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
),
},
ingress: {
tag: "hassio-ingress-view",
load: () =>
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
},
},
};
@internalProperty() private _supervisorInfo: HassioSupervisorInfo;
@internalProperty() private _hostInfo: HassioHostInfo;
@internalProperty() private _hassioInfo?: HassioInfo;
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
@internalProperty() private _hassInfo: HassioHomeAssistantInfo;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
protected updatePageEl(el) {
// the tabs page does its own routing so needs full route.
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
el.hass = this.hass;
el.narrow = this.narrow;
el.supervisorInfo = this._supervisorInfo;
el.hassioInfo = this._hassioInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
el.route = route;
if (el.localName === "hassio-ingress-view") {
el.ingressPanel = this.panel.config && this.panel.config.ingress;
}
}
private async _fetchData() {
if (this.panel.config && this.panel.config.ingress) {
this._redirectIngress(this.panel.config.ingress);
return;
}
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hassioInfo = hassioInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
}
}
private _redirectIngress(addonSlug: string) {
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
}
private _apiCalled(ev) {
if (!ev.detail.success) {
return;
}
let tries = 1;
const tryUpdate = () => {
this._fetchData().catch(() => {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-router": HassioRouter;
}
}

View File

@@ -5,6 +5,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -16,29 +17,56 @@ import { createHassioSession } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { navigate } from "../../../src/common/navigate";
import { mdiMenu } from "@mdi/js";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("hassio-ingress-view")
class HassioIngressView extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public route!: Route;
@property() private _addon?: HassioAddonDetails;
@property() public ingressPanel = false;
@internalProperty() private _addon?: HassioAddonDetails;
@property({ type: Boolean })
public narrow = false;
protected render(): TemplateResult {
if (!this._addon) {
return html` <hass-loading-screen></hass-loading-screen> `;
}
return html`
<hass-subpage .header=${this._addon.name} hassio>
<iframe src=${this._addon.ingress_url}></iframe>
</hass-subpage>
`;
const iframe = html`<iframe src=${this._addon.ingress_url!}></iframe>`;
if (!this.ingressPanel) {
return html`<hass-subpage
.header=${this._addon.name}
.narrow=${this.narrow}
>
${iframe}
</hass-subpage>`;
}
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
? html`<div class="header">
<mwc-icon-button
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
@click=${this._toggleMenu}
>
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
</mwc-icon-button>
<div class="main-title">${this._addon.name}</div>
</div>
${iframe}`
: iframe}`;
}
protected updated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
super.updated(changedProps);
if (!changedProps.has("route")) {
return;
@@ -55,27 +83,56 @@ class HassioIngressView extends LitElement {
}
private async _fetchData(addonSlug: string) {
const createSessionPromise = createHassioSession(this.hass).then(
() => true,
() => false
);
let addon;
try {
const [addon] = await Promise.all([
fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
throw new Error("Failed to fetch add-on info");
}),
createHassioSession(this.hass).catch(() => {
throw new Error("Failed to create an ingress session");
}),
]);
if (!addon.ingress) {
throw new Error("This add-on does not support ingress");
}
this._addon = addon;
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err) {
// eslint-disable-next-line
console.error(err);
alert(err.message || "Unknown error starting ingress.");
await showAlertDialog(this, {
text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor",
});
history.back();
return;
}
if (!addon.ingress_url) {
await showAlertDialog(this, {
text: "Add-on does not support Ingress",
title: addon.name,
});
history.back();
return;
}
if (addon.state !== "started") {
await showAlertDialog(this, {
text: "Add-on is not running. Please start it first",
title: addon.name,
});
navigate(this, `/hassio/addon/${addon.slug}/info`, true);
return;
}
if (!(await createSessionPromise)) {
await showAlertDialog(this, {
text: "Unable to create an Ingress session",
title: addon.name,
});
history.back();
return;
}
this._addon = addon;
}
private _toggleMenu(): void {
fireEvent(this, "hass-toggle-menu");
}
static get styles(): CSSResult {
@@ -86,6 +143,41 @@ class HassioIngressView extends LitElement {
height: 100%;
border: 0;
}
.header + iframe {
height: calc(100% - 40px);
}
.header {
display: flex;
align-items: center;
font-size: 16px;
height: 40px;
padding: 0 16px;
pointer-events: none;
background-color: var(--app-header-background-color);
font-weight: 400;
color: var(--app-header-text-color, white);
border-bottom: var(--app-header-border-bottom, none);
box-sizing: border-box;
--mdc-icon-size: 20px;
}
.main-title {
margin: 0 0 0 24px;
line-height: 20px;
flex-grow: 1;
}
mwc-icon-button {
pointer-events: auto;
}
hass-subpage {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
--app-header-border-bottom: 1px solid var(--divider-color);
}
`;
}
}

View File

@@ -13,14 +13,17 @@ import {
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
@@ -56,19 +59,19 @@ class HassioSnapshots extends LitElement {
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@property() private _snapshotName = "";
@internalProperty() private _snapshotName = "";
@property() private _snapshotPassword = "";
@internalProperty() private _snapshotPassword = "";
@property() private _snapshotHasPassword = false;
@internalProperty() private _snapshotHasPassword = false;
@property() private _snapshotType: HassioSnapshot["type"] = "full";
@internalProperty() private _snapshotType: HassioSnapshot["type"] = "full";
@property() private _snapshots?: HassioSnapshot[] = [];
@internalProperty() private _snapshots?: HassioSnapshot[] = [];
@property() private _addonList: CheckboxItem[] = [];
@internalProperty() private _addonList: CheckboxItem[] = [];
@property() private _folderList: CheckboxItem[] = [
@internalProperty() private _folderList: CheckboxItem[] = [
{
slug: "homeassistant",
name: "Home Assistant configuration",
@@ -79,9 +82,7 @@ class HassioSnapshots extends LitElement {
{ slug: "addons/local", name: "Local add-ons", checked: true },
];
@property() private _creatingSnapshot = false;
@property() private _error = "";
@internalProperty() private _error = "";
public async refreshData() {
await reloadHassioSnapshots(this.hass);
@@ -191,12 +192,9 @@ class HassioSnapshots extends LitElement {
: undefined}
</div>
<div class="card-actions">
<mwc-button
.disabled=${this._creatingSnapshot}
@click=${this._createSnapshot}
>
<ha-progress-button @click=${this._createSnapshot}>
Create
</mwc-button>
</ha-progress-button>
</div>
</ha-card>
</div>
@@ -229,7 +227,7 @@ class HassioSnapshots extends LitElement {
.icon=${snapshot.type === "full"
? mdiPackageVariantClosed
: mdiPackageVariant}
.icon-class="snapshot"
icon-class="snapshot"
></hassio-card-content>
</div>
</ha-card>
@@ -292,17 +290,20 @@ class HassioSnapshots extends LitElement {
this._snapshots = await fetchHassioSnapshots(this.hass);
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
} catch (err) {
this._error = err.message;
this._error = extractApiErrorMessage(err);
}
}
private async _createSnapshot() {
private async _createSnapshot(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
this._error = "";
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
this._error = "Please enter a password.";
button.progress = false;
return;
}
this._creatingSnapshot = true;
await this.updateComplete;
const name =
@@ -342,10 +343,9 @@ class HassioSnapshots extends LitElement {
this._updateSnapshots();
fireEvent(this, "hass-api-called", { success: true, response: null });
} catch (err) {
this._error = err.message;
} finally {
this._creatingSnapshot = false;
this._error = extractApiErrorMessage(err);
}
button.progress = false;
}
private _computeDetails(snapshot: HassioSnapshot) {

View File

@@ -1,17 +1,32 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { safeDump } from "js-yaml";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/buttons/ha-call-api-button";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import {
extractApiErrorMessage,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
configSyncOS,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo as HassioHostInfoType,
@@ -19,6 +34,10 @@ import {
shutdownHost,
updateOS,
} from "../../../src/data/hassio/host";
import {
fetchNetworkInfo,
NetworkInfo,
} from "../../../src/data/hassio/network";
import { HassioInfo } from "../../../src/data/hassio/supervisor";
import {
showAlertDialog,
@@ -28,176 +47,192 @@ import {
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info")
class HassioHostInfo extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public hostInfo!: HassioHostInfoType;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
@property() private _errors?: string;
@internalProperty() public _networkInfo?: NetworkInfo;
public render(): TemplateResult | void {
protected render(): TemplateResult | void {
const primaryIpAddress = this.hostInfo.features.includes("network")
? this._primaryIpAddress(this._networkInfo!)
: "";
return html`
<ha-card>
<ha-card header="Host System">
<div class="card-content">
<h2>Host system</h2>
<table class="info">
<tbody>
<tr>
<td>Hostname</td>
<td>${this.hostInfo.hostname}</td>
</tr>
<tr>
<td>System</td>
<td>${this.hostInfo.operating_system}</td>
</tr>
${!this.hostInfo.features.includes("hassos")
? html`<tr>
<td>Docker version</td>
<td>${this.hassioInfo.docker}</td>
</tr>`
: ""}
${this.hostInfo.deployment
? html`
<tr>
<td>Deployment</td>
<td>${this.hostInfo.deployment}</td>
</tr>
`
: ""}
</tbody>
</table>
<mwc-button raised @click=${this._showHardware} class="info">
Hardware
</mwc-button>
${this.hostInfo.features.includes("hostname")
? html`
? html`<ha-settings-row>
<span slot="heading">
Hostname
</span>
<span slot="description">
${this.hostInfo.hostname}
</span>
<mwc-button
raised
title="Change the hostname"
label="Change"
@click=${this._changeHostnameClicked}
class="info"
>
Change hostname
</mwc-button>
`
</ha-settings-row>`
: ""}
${this._errors
? html` <div class="errors">Error: ${this._errors}</div> `
${this.hostInfo.features.includes("network") &&
atLeastVersion(this.hass.config.version, 0, 115)
? html` <ha-settings-row>
<span slot="heading">
IP address
</span>
<span slot="description">
${primaryIpAddress}
</span>
<mwc-button
title="Change the network"
label="Change"
@click=${this._changeNetworkClicked}
>
</mwc-button>
</ha-settings-row>`
: ""}
<ha-settings-row>
<span slot="heading">
Operating system
</span>
<span slot="description">
${this.hostInfo.operating_system}
</span>
${this.hostInfo.version !== this.hostInfo.version_latest &&
this.hostInfo.features.includes("hassos")
? html`
<ha-progress-button
title="Update the host OS"
@click=${this._osUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
${!this.hostInfo.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
Docker version
</span>
<span slot="description">
${this.hassioInfo.docker}
</span>
</ha-settings-row>`
: ""}
${this.hostInfo.deployment
? html`<ha-settings-row>
<span slot="heading">
Deployment
</span>
<span slot="description">
${this.hostInfo.deployment}
</span>
</ha-settings-row>`
: ""}
</div>
<div class="card-actions">
${this.hostInfo.features.includes("reboot")
? html`
<mwc-button class="warning" @click=${this._rebootHost}
>Reboot</mwc-button
<ha-progress-button
title="Reboot the host OS"
class="warning"
@click=${this._hostReboot}
>
Reboot
</ha-progress-button>
`
: ""}
${this.hostInfo.features.includes("shutdown")
? html`
<mwc-button class="warning" @click=${this._shutdownHost}
>Shutdown</mwc-button
>
`
: ""}
${this.hostInfo.features.includes("hassos")
? html`
<ha-call-api-button
<ha-progress-button
title="Shutdown the host OS"
class="warning"
.hass=${this.hass}
path="hassio/os/config/sync"
title="Load HassOS configs or updates from USB"
>Import from USB</ha-call-api-button
@click=${this._hostShutdown}
>
Shutdown
</ha-progress-button>
`
: ""}
${this.hostInfo.version !== this.hostInfo.version_latest
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
: ""}
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item title="Show a list of hardware">
Hardware
</mwc-list-item>
${this.hostInfo.features.includes("hassos")
? html`<mwc-list-item
title="Load HassOS configs or updates from USB"
>
Import from USB
</mwc-list-item>`
: ""}
</ha-button-menu>
</div>
</ha-card>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
width: 100%;
}
.card-content {
color: var(--primary-text-color);
box-sizing: border-box;
height: calc(100% - 47px);
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--error-color);
margin-top: 16px;
}
mwc-button.info {
max-width: calc(50% - 12px);
}
table.info {
margin-bottom: 10px;
}
.warning {
--mdc-theme-primary: var(--error-color);
}
`,
];
}
protected firstUpdated(): void {
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
this._loadData();
}
private _apiCalled(ev): void {
if (ev.detail.success) {
this._errors = undefined;
return;
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
if (!network_info) {
return "";
}
return Object.keys(network_info?.interfaces)
.map((device) => network_info.interfaces[device])
.find((device) => device.primary)?.ip_address;
});
const response = ev.detail.response;
this._errors =
typeof response.body === "object"
? response.body.message || "Unknown error"
: response.body;
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
await this._showHardware();
break;
case 1:
await this._importFromUSB();
break;
}
}
private async _showHardware(): Promise<void> {
try {
const content = this._objectToMarkdown(
await fetchHassioHardwareInfo(this.hass)
);
const content = await fetchHassioHardwareInfo(this.hass);
showHassioMarkdownDialog(this, {
title: "Hardware",
content,
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
});
} catch (err) {
showHassioMarkdownDialog(this, {
title: "Hardware",
content: "Error getting hardware info",
showAlertDialog(this, {
title: "Failed to get Hardware list",
text: extractApiErrorMessage(err),
});
}
}
private async _rebootHost(): Promise<void> {
private async _hostReboot(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Reboot",
text: "Are you sure you want to reboot the host?",
@@ -206,20 +241,28 @@ class HassioHostInfo extends LitElement {
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await rebootHost(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to reboot",
text: err.body.message,
});
// Ignore connection errors, these are all expected
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: "Failed to reboot",
text: extractApiErrorMessage(err),
});
}
}
button.progress = false;
}
private async _shutdownHost(): Promise<void> {
private async _hostShutdown(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Shutdown",
text: "Are you sure you want to shutdown the host?",
@@ -228,20 +271,28 @@ class HassioHostInfo extends LitElement {
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await shutdownHost(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to shutdown",
text: err.body.message,
});
// Ignore connection errors, these are all expected
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: "Failed to shutdown",
text: extractApiErrorMessage(err),
});
}
}
button.progress = false;
}
private async _updateOS(): Promise<void> {
private async _osUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Update",
text: "Are you sure you want to update the OS?",
@@ -250,6 +301,7 @@ class HassioHostInfo extends LitElement {
});
if (!confirmed) {
button.progress = false;
return;
}
@@ -258,30 +310,17 @@ class HassioHostInfo extends LitElement {
} catch (err) {
showAlertDialog(this, {
title: "Failed to update",
text: err.body.message,
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private _objectToMarkdown(obj, indent = ""): string {
let data = "";
Object.keys(obj).forEach((key) => {
if (typeof obj[key] !== "object") {
data += `${indent}- ${key}: ${obj[key]}\n`;
} else {
data += `${indent}- ${key}:\n`;
if (Array.isArray(obj[key])) {
if (obj[key].length) {
data +=
`${indent} - ` + obj[key].join(`\n${indent} - `) + "\n";
}
} else {
data += this._objectToMarkdown(obj[key], ` ${indent}`);
}
}
private async _changeNetworkClicked(): Promise<void> {
showNetworkDialog(this, {
network: this._networkInfo!,
loadData: () => this._loadData(),
});
return data;
}
private async _changeHostnameClicked(): Promise<void> {
@@ -300,11 +339,83 @@ class HassioHostInfo extends LitElement {
} catch (err) {
showAlertDialog(this, {
title: "Setting hostname failed",
text: err.body.message,
text: extractApiErrorMessage(err),
});
}
}
}
private async _importFromUSB(): Promise<void> {
try {
await configSyncOS(this.hass);
this.hostInfo = await fetchHassioHostInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to import from USB",
text: extractApiErrorMessage(err),
});
}
}
private async _loadData(): Promise<void> {
this._networkInfo = await fetchNetworkInfo(this.hass);
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
.card-actions {
height: 48px;
border-top: none;
display: flex;
justify-content: space-between;
align-items: center;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row[three-line] {
height: 74px;
}
ha-settings-row > span[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
.warning {
--mdc-theme-primary: var(--error-color);
}
ha-button-menu {
color: var(--secondary-text-color);
--mdc-menu-min-width: 200px;
}
@media (min-width: 563px) {
paper-listbox {
max-height: 150px;
overflow: auto;
}
}
paper-item {
cursor: pointer;
min-height: 35px;
}
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
`,
];
}
}
declare global {

View File

@@ -1,4 +1,3 @@
import "@material/mwc-button";
import {
css,
CSSResult,
@@ -8,94 +7,262 @@ import {
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch";
import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
import {
HassioSupervisorInfo as HassioSupervisorInfoType,
reloadSupervisor,
setSupervisorOption,
SupervisorOptions,
updateSupervisor,
fetchHassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
@customElement("hassio-supervisor-info")
class HassioSupervisorInfo extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public supervisorInfo!: HassioSupervisorInfoType;
@property() private _errors?: string;
@property() public hostInfo!: HassioHostInfoType;
public render(): TemplateResult | void {
protected render(): TemplateResult | void {
return html`
<ha-card>
<ha-card header="Supervisor">
<div class="card-content">
<h2>Supervisor</h2>
<table class="info">
<tbody>
<tr>
<td>Version</td>
<td>${this.supervisorInfo.version}</td>
</tr>
<tr>
<td>Latest version</td>
<td>${this.supervisorInfo.version_latest}</td>
</tr>
${this.supervisorInfo.channel !== "stable"
? html`
<tr>
<td>Channel</td>
<td>${this.supervisorInfo.channel}</td>
</tr>
`
: ""}
</tbody>
</table>
${this._errors
? html` <div class="errors">Error: ${this._errors}</div> `
: ""}
<ha-settings-row>
<span slot="heading">
Version
</span>
<span slot="description">
${this.supervisorInfo.version}
</span>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Newest version
</span>
<span slot="description">
${this.supervisorInfo.version_latest}
</span>
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
? html`
<ha-progress-button
title="Update the supervisor"
@click=${this._supervisorUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Channel
</span>
<span slot="description">
${this.supervisorInfo.channel}
</span>
${this.supervisorInfo.channel === "beta"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get stable updates for Home Assistant, supervisor and host"
>
Leave beta channel
</ha-progress-button>
`
: this.supervisorInfo.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
Join beta channel
</ha-progress-button>
`
: ""}
</ha-settings-row>
${this.supervisorInfo?.supported
? html` <ha-settings-row three-line>
<span slot="heading">
Share diagnostics
</span>
<div slot="description" class="diagnostics-description">
Share crash reports and diagnostic information.
<button
class="link"
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
>
Learn more
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisorInfo.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
You are running an unsupported installation.
<a
href="https://github.com/home-assistant/architecture/blob/master/adr/${this.hostInfo.features.includes(
"hassos"
)
? "0015-home-assistant-os.md"
: "0014-home-assistant-supervised.md"}"
target="_blank"
rel="noreferrer"
title="Learn more about how you can make your system compliant"
>
Learn More
</a>
</div>`}
</div>
<div class="card-actions">
<ha-call-api-button .hass=${this.hass} path="hassio/supervisor/reload"
>Reload</ha-call-api-button
<ha-progress-button
@click=${this._supervisorReload}
title="Reload parts of the supervisor."
>
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
? html`
<ha-call-api-button
.hass=${this.hass}
path="hassio/supervisor/update"
>Update</ha-call-api-button
>
`
: ""}
${this.supervisorInfo.channel === "beta"
? html`
<ha-call-api-button
.hass=${this.hass}
path="hassio/supervisor/options"
.data=${{ channel: "stable" }}
>Leave beta channel</ha-call-api-button
>
`
: ""}
${this.supervisorInfo.channel === "stable"
? html`
<mwc-button
@click=${this._joinBeta}
class="warning"
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>Join beta channel</mwc-button
>
`
: ""}
Reload
</ha-progress-button>
</div>
</ha-card>
`;
}
private async _toggleBeta(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
if (this.supervisorInfo.channel === "stable") {
const confirmed = await showConfirmationDialog(this, {
title: "WARNING",
text: html` Beta releases are for testers and early adopters and can
contain unstable code changes.
<br />
<b>
Make sure you have backups of your data before you activate this
feature.
</b>
<br /><br />
This includes beta releases for:
<li>Home Assistant Core</li>
<li>Home Assistant Supervisor</li>
<li>Home Assistant Operating System</li>
<br />
Do you want to join the beta channel?`,
confirmText: "join beta",
dismissText: "no",
});
if (!confirmed) {
button.progress = false;
return;
}
}
try {
const data: Partial<SupervisorOptions> = {
channel: this.supervisorInfo.channel === "stable" ? "beta" : "stable",
};
await setSupervisorOption(this.hass, data);
await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to set supervisor option",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _supervisorReload(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to reload the supervisor",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _supervisorUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Update supervisor",
text: `Are you sure you want to upgrade supervisor to version ${this.supervisorInfo.version_latest}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await updateSupervisor(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to update the supervisor",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _diagnosticsInformationDialog(): Promise<void> {
await showAlertDialog(this, {
title: "Help Improve Home Assistant",
text: html`Would you want to automatically share crash reports and
diagnostic information when the supervisor encounters unexpected errors?
<br /><br />
This will allow us to fix the problems, the information is only
accessible to the Home Assistant Core team and will not be shared with
others.
<br /><br />
The data does not include any private/sensitive information and you can
disable this in settings at any time you want.`,
});
}
private async _toggleDiagnostics(): Promise<void> {
try {
const data: SupervisorOptions = {
diagnostics: !this.supervisorInfo?.diagnostics,
};
await setSupervisorOption(this.hass, data);
} catch (err) {
showAlertDialog(this, {
title: "Failed to set supervisor option",
text: extractApiErrorMessage(err),
});
}
}
static get styles(): CSSResult[] {
return [
haStyle,
@@ -103,83 +270,35 @@ class HassioSupervisorInfo extends LitElement {
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
.card-actions {
height: 48px;
border-top: none;
display: flex;
justify-content: space-between;
align-items: center;
}
button.link {
color: var(--primary-color);
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
.card-content {
color: var(--primary-text-color);
box-sizing: border-box;
height: calc(100% - 47px);
ha-settings-row[three-line] {
height: 74px;
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--error-color);
margin-top: 16px;
ha-settings-row > div[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
`,
];
}
protected firstUpdated(): void {
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
private _apiCalled(ev): void {
if (ev.detail.success) {
this._errors = undefined;
return;
}
const response = ev.detail.response;
this._errors =
typeof response.body === "object"
? response.body.message || "Unknown error"
: response.body;
}
private async _joinBeta() {
const confirmed = await showConfirmationDialog(this, {
title: "WARNING",
text: html` Beta releases are for testers and early adopters and can
contain unstable code changes.
<br />
<b>
Make sure you have backups of your data before you activate this
feature.
</b>
<br /><br />
This includes beta releases for:
<li>Home Assistant Core</li>
<li>Home Assistant Supervisor</li>
<li>Home Assistant Operating System</li>
<br />
Do you want to join the beta channel?`,
confirmText: "join beta",
dismissText: "no",
});
if (!confirmed) {
return;
}
try {
const data: SupervisorOptions = { channel: "beta" };
await setSupervisorOption(this.hass, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._errors = `Error joining beta channel, ${err.body?.message || err}`;
}
}
}
declare global {

View File

@@ -7,13 +7,16 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/loading-screen";
import "../../../src/layouts/hass-loading-screen";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import "../components/hassio-ansi-to-html";
@@ -55,18 +58,18 @@ const logProviders: LogProvider[] = [
class HassioSupervisorLog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() private _error?: string;
@internalProperty() private _error?: string;
@property() private _selectedLogProvider = "supervisor";
@internalProperty() private _selectedLogProvider = "supervisor";
@property() private _content?: string;
@internalProperty() private _content?: string;
public async connectedCallback(): Promise<void> {
super.connectedCallback();
await this._loadData();
}
public render(): TemplateResult | void {
protected render(): TemplateResult | void {
return html`
<ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -98,21 +101,52 @@ class HassioSupervisorLog extends LitElement {
? html`<hassio-ansi-to-html
.content=${this._content}
></hassio-ansi-to-html>`
: html`<loading-screen></loading-screen>`}
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
</div>
<div class="card-actions">
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
<ha-progress-button @click=${this._refresh}>
Refresh
</ha-progress-button>
</div>
</ha-card>
`;
}
private async _setLogProvider(ev): Promise<void> {
const provider = ev.detail.item.getAttribute("provider");
this._selectedLogProvider = provider;
this._loadData();
}
private async _refresh(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
await this._loadData();
button.progress = false;
}
private async _loadData(): Promise<void> {
this._error = undefined;
try {
this._content = await fetchHassioLogs(
this.hass,
this._selectedLogProvider
);
} catch (err) {
this._error = `Failed to get supervisor logs, ${extractApiErrorMessage(
err
)}`;
}
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
margin-top: 8px;
width: 100%;
}
pre {
@@ -126,38 +160,9 @@ class HassioSupervisorLog extends LitElement {
color: var(--error-color);
margin-bottom: 16px;
}
.card-content {
padding-top: 0px;
}
`,
];
}
private async _setLogProvider(ev): Promise<void> {
const provider = ev.detail.item.getAttribute("provider");
this._selectedLogProvider = provider;
await this._loadData();
}
private async _loadData(): Promise<void> {
this._error = undefined;
this._content = undefined;
try {
this._content = await fetchHassioLogs(
this.hass,
this._selectedLogProvider
);
} catch (err) {
this._error = `Failed to get supervisor logs, ${
err.body?.message || err
}`;
}
}
private async _refresh(): Promise<void> {
await this._loadData();
}
}
declare global {

View File

@@ -12,8 +12,8 @@ import {
HassioHostInfo,
} from "../../../src/data/hassio/host";
import {
HassioSupervisorInfo,
HassioInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
@@ -40,7 +40,7 @@ class HassioSystem extends LitElement {
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
public render(): TemplateResult | void {
protected render(): TemplateResult | void {
return html`
<hass-tabs-subpage
.hass=${this.hass}
@@ -52,10 +52,10 @@ class HassioSystem extends LitElement {
>
<span slot="header">System</span>
<div class="content">
<h1>Information</h1>
<div class="card-group">
<hassio-supervisor-info
.hass=${this.hass}
.hostInfo=${this.hostInfo}
.supervisorInfo=${this.supervisorInfo}
></hassio-supervisor-info>
<hassio-host-info
@@ -65,7 +65,6 @@ class HassioSystem extends LitElement {
.hassOsInfo=${this.hassOsInfo}
></hassio-host-info>
</div>
<h1>System log</h1>
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
</div>
</hass-tabs-subpage>

View File

@@ -13,7 +13,7 @@
"lint:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --check",
"format:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --write",
"lint:types": "tsc",
"lint:lit": "lit-analyzer '**/src/**/*.ts'",
"lint:lit": "lit-analyzer '**/src/**/*.ts' --format markdown --outFile result.md",
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
"format": "npm run format:eslint && npm run format:prettier",
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
@@ -23,22 +23,29 @@
"license": "Apache-2.0",
"dependencies": {
"@formatjs/intl-pluralrules": "^1.5.8",
"@fullcalendar/core": "^5.0.0-beta.2",
"@fullcalendar/daygrid": "^5.0.0-beta.2",
"@material/chips": "7.0.0-canary.d92d8c93e.0",
"@material/circular-progress": "7.0.0-canary.d92d8c93e.0",
"@material/mwc-button": "^0.15.0",
"@material/mwc-checkbox": "^0.15.0",
"@material/mwc-dialog": "^0.15.0",
"@material/mwc-fab": "^0.15.0",
"@material/mwc-formfield": "^0.15.0",
"@material/mwc-icon-button": "^0.15.0",
"@material/mwc-list": "^0.15.0",
"@material/mwc-menu": "^0.15.0",
"@material/mwc-ripple": "^0.15.0",
"@material/mwc-switch": "^0.15.0",
"@mdi/js": "4.9.95",
"@mdi/svg": "4.9.95",
"@fullcalendar/common": "5.1.0",
"@fullcalendar/core": "5.1.0",
"@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0",
"@material/chips": "=8.0.0-canary.096a7a066.0",
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0",
"@material/mwc-button": "^0.18.0",
"@material/mwc-checkbox": "^0.18.0",
"@material/mwc-dialog": "^0.18.0",
"@material/mwc-fab": "^0.18.0",
"@material/mwc-formfield": "^0.18.0",
"@material/mwc-icon-button": "^0.18.0",
"@material/mwc-list": "^0.18.0",
"@material/mwc-menu": "^0.18.0",
"@material/mwc-radio": "^0.18.0",
"@material/mwc-ripple": "^0.18.0",
"@material/mwc-switch": "^0.18.0",
"@material/mwc-tab": "^0.18.0",
"@material/mwc-tab-bar": "^0.18.0",
"@material/top-app-bar": "=8.0.0-canary.096a7a066.0",
"@mdi/js": "5.5.55",
"@mdi/svg": "5.5.55",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-route": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
@@ -71,6 +78,8 @@
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.0",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/sortablejs": "^1.10.6",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vue/web-component-wrapper": "^1.2.0",
@@ -80,6 +89,7 @@
"codemirror": "^5.49.0",
"comlink": "^4.3.0",
"cpx": "^1.5.0",
"cropperjs": "^1.5.7",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"es6-object-assign": "^1.1.0",
@@ -96,15 +106,17 @@
"lit-element": "^2.3.1",
"lit-html": "^1.2.1",
"lit-virtualizer": "^0.4.2",
"marked": "^0.6.1",
"marked": "^1.1.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
"node-vibrant": "^3.1.5",
"proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1",
"regenerator-runtime": "^0.13.2",
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"superstruct": "^0.6.1",
"sortablejs": "^1.10.2",
"superstruct": "^0.10.12",
"unfetch": "^4.1.0",
"vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1",
@@ -132,13 +144,14 @@
"@rollup/plugin-replace": "^2.3.2",
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12",
"@types/codemirror": "^0.0.78",
"@types/codemirror": "^0.0.97",
"@types/hls.js": "^0.12.3",
"@types/js-yaml": "^3.12.1",
"@types/leaflet": "^1.4.3",
"@types/leaflet-draw": "^1.0.1",
"@types/marked": "^1.1.0",
"@types/memoize-one": "4.1.0",
"@types/mocha": "^5.2.6",
"@types/mocha": "^7.0.2",
"@types/resize-observer-browser": "^0.1.3",
"@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^2.28.0",
@@ -149,7 +162,7 @@
"eslint": "^6.8.0",
"eslint-config-airbnb-typescript": "^7.2.1",
"eslint-config-prettier": "^6.10.1",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-import-resolver-webpack": "^0.12.2",
"eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-lit": "^1.2.0",
@@ -167,12 +180,12 @@
"html-minifier": "^4.0.0",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"lit-analyzer": "^1.1.10",
"lit-analyzer": "^1.2.0",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
"map-stream": "^0.0.7",
"merge-stream": "^1.0.1",
"mocha": "^6.0.2",
"mocha": "^7.2.0",
"object-hash": "^2.0.3",
"open": "^7.0.4",
"prettier": "^2.0.4",
@@ -188,8 +201,8 @@
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^3.0.6",
"ts-lit-plugin": "^1.1.10",
"ts-mocha": "^6.0.0",
"ts-lit-plugin": "^1.2.0",
"ts-mocha": "^7.0.0",
"typescript": "^3.8.3",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
@@ -207,19 +220,10 @@
"@polymer/polymer": "3.1.0",
"lit-html": "1.2.1",
"lit-element": "2.3.1",
"@material/animation": "7.0.0-canary.d92d8c93e.0",
"@material/base": "7.0.0-canary.d92d8c93e.0",
"@material/checkbox": "7.0.0-canary.d92d8c93e.0",
"@material/density": "7.0.0-canary.d92d8c93e.0",
"@material/dom": "7.0.0-canary.d92d8c93e.0",
"@material/elevation": "7.0.0-canary.d92d8c93e.0",
"@material/feature-targeting": "7.0.0-canary.d92d8c93e.0",
"@material/ripple": "7.0.0-canary.d92d8c93e.0",
"@material/rtl": "7.0.0-canary.d92d8c93e.0",
"@material/shape": "7.0.0-canary.d92d8c93e.0",
"@material/theme": "7.0.0-canary.d92d8c93e.0",
"@material/touch-target": "7.0.0-canary.d92d8c93e.0",
"@material/typography": "7.0.0-canary.d92d8c93e.0"
"@material/animation": "8.0.0-canary.096a7a066.0",
"@material/base": "8.0.0-canary.096a7a066.0",
"@material/feature-targeting": "8.0.0-canary.096a7a066.0",
"@material/theme": "8.0.0-canary.096a7a066.0"
},
"main": "src/home-assistant.js",
"husky": {

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20200701.0",
version="20200912.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@@ -5,6 +5,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -28,13 +29,13 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@property() public oauth2State?: string;
@property() private _state: State = "loading";
@internalProperty() private _state: State = "loading";
@property() private _stepData: any = {};
@internalProperty() private _stepData: any = {};
@property() private _step?: DataEntryFlowStep;
@internalProperty() private _step?: DataEntryFlowStep;
@property() private _errorMessage?: string;
@internalProperty() private _errorMessage?: string;
protected render() {
return html`

View File

@@ -4,6 +4,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
} from "lit-element";
import {
@@ -15,6 +16,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow";
import { extractSearchParamsObject } from "../common/url/search-params";
import punycode from "punycode";
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
@@ -25,9 +27,9 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@property() public oauth2State?: string;
@property() private _authProvider?: AuthProvider;
@internalProperty() private _authProvider?: AuthProvider;
@property() private _authProviders?: AuthProvider[];
@internalProperty() private _authProviders?: AuthProvider[];
constructor() {
super();
@@ -74,7 +76,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
${this.localize(
"ui.panel.page-authorize.authorizing_client",
"clientId",
this.clientId
this.clientId ? punycode.toASCII(this.clientId) : this.clientId
)}
</p>
${loggingInWith}

View File

@@ -1,131 +0,0 @@
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateName } from "../common/entity/compute_state_name";
import "../components/state-history-charts";
import "../data/ha-state-history-data";
import { EventsMixin } from "../mixins/events-mixin";
/*
* @appliesMixin EventsMixin
*/
class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style>
paper-card:not([dialog]) .content {
padding: 0 16px 16px;
}
paper-card[dialog] {
padding-top: 16px;
background-color: transparent;
}
paper-card {
width: 100%;
/* prevent new stacking context, chart tooltip needs to overflow */
position: static;
}
.header {
@apply --paper-font-headline;
line-height: 40px;
color: var(--primary-text-color);
padding: 20px 16px 12px;
@apply --paper-font-common-nowrap;
}
paper-card[dialog] .header {
display: none;
}
</style>
<ha-state-history-data
hass="[[hass]]"
filter-type="recent-entity"
entity-id="[[computeHistoryEntities(stateObj)]]"
data="{{stateHistory}}"
is-loading="{{stateHistoryLoading}}"
cache-config="[[cacheConfig]]"
></ha-state-history-data>
<paper-card
dialog$="[[inDialog]]"
on-click="cardTapped"
elevation="[[computeElevation(inDialog)]]"
>
<div class="header">[[computeTitle(stateObj)]]</div>
<div class="content">
<state-history-charts
hass="[[hass]]"
history-data="[[stateHistory]]"
is-loading-data="[[stateHistoryLoading]]"
up-to-now
no-single
>
</state-history-charts>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
stateObj: {
type: Object,
observer: "stateObjObserver",
},
inDialog: {
type: Boolean,
value: false,
},
stateHistory: Object,
stateHistoryLoading: Boolean,
cacheConfig: {
type: Object,
value: {
refresh: 0,
cacheKey: null,
hoursToShow: 24,
},
},
};
}
stateObjObserver(stateObj) {
if (!stateObj) return;
if (
this.cacheConfig.cacheKey !== stateObj.entity_id ||
this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) ||
this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)
) {
this.cacheConfig = {
refresh: stateObj.attributes.refresh || 0,
cacheKey: stateObj.entity_id,
hoursToShow: stateObj.attributes.hours_to_show || 24,
};
}
}
computeTitle(stateObj) {
return computeStateName(stateObj);
}
computeContentClass(inDialog) {
return inDialog ? "" : "content";
}
computeHistoryEntities(stateObj) {
return stateObj.attributes.entity_id;
}
computeElevation(inDialog) {
return inDialog ? 0 : 1;
}
cardTapped(ev) {
const mq = window.matchMedia("(min-width: 610px) and (min-height: 550px)");
if (mq.matches) {
ev.stopPropagation();
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
}
}
}
customElements.define("ha-history_graph-card", HaHistoryGraphCard);

View File

@@ -1,4 +1,8 @@
/* eslint-disable no-undef, no-console */
import {
CastStateEventData,
SessionStateEventData,
} from "chromecast-caf-receiver/cast.framework";
import { Auth } from "home-assistant-js-websocket";
import { castApiAvailable } from "./cast_framework";
import { CAST_APP_ID, CAST_DEV, CAST_NS } from "./const";
@@ -40,16 +44,13 @@ export class CastManager {
const context = this.castContext;
context.setOptions({
receiverApplicationId: CAST_APP_ID,
// @ts-ignore
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
});
context.addEventListener(
// @ts-ignore
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
(ev) => this._sessionStateChanged(ev)
);
context.addEventListener(
// @ts-ignore
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
(ev) => this._castStateChanged(ev)
);
@@ -118,7 +119,7 @@ export class CastManager {
}
}
private _sessionStateChanged(ev) {
private _sessionStateChanged(ev: SessionStateEventData) {
if (__DEV__) {
console.log("Cast session state changed", ev.sessionState);
}
@@ -141,7 +142,7 @@ export class CastManager {
}
}
private _castStateChanged(ev) {
private _castStateChanged(ev: CastStateEventData) {
if (__DEV__) {
console.log("Cast state changed", ev.castState);
}

View File

@@ -0,0 +1,113 @@
const expand_hex = (hex: string): string => {
let result = "";
for (const val of hex) {
result += val + val;
}
return result;
};
const rgb_hex = (component: number): string => {
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
// Conversion between HEX and RGB
export const hex2rgb = (hex: string): [number, number, number] => {
hex = hex.replace("#", "");
if (hex.length === 3 || hex.length === 4) {
hex = expand_hex(hex);
}
return [
parseInt(hex.substring(0, 2), 16),
parseInt(hex.substring(2, 4), 16),
parseInt(hex.substring(4, 6), 16),
];
};
export const rgb2hex = (rgb: [number, number, number]): string => {
return `#${rgb_hex(rgb[0])}${rgb_hex(rgb[1])}${rgb_hex(rgb[2])}`;
};
// Conversion between LAB, XYZ and RGB from https://github.com/gka/chroma.js
// Copyright (c) 2011-2019, Gregor Aisch
// Constants for XYZ and LAB conversion
const Xn = 0.95047;
const Yn = 1;
const Zn = 1.08883;
const t0 = 0.137931034; // 4 / 29
const t1 = 0.206896552; // 6 / 29
const t2 = 0.12841855; // 3 * t1 * t1
const t3 = 0.008856452; // t1 * t1 * t1
const rgb_xyz = (r: number) => {
r /= 255;
if (r <= 0.04045) {
return r / 12.92;
}
return ((r + 0.055) / 1.055) ** 2.4;
};
const xyz_lab = (t: number) => {
if (t > t3) {
return t ** (1 / 3);
}
return t / t2 + t0;
};
const xyz_rgb = (r: number) => {
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * r ** (1 / 2.4) - 0.055);
};
const lab_xyz = (t: number) => {
return t > t1 ? t * t * t : t2 * (t - t0);
};
// Conversions between RGB and LAB
const rgb2xyz = (rgb: [number, number, number]): [number, number, number] => {
let [r, g, b] = rgb;
r = rgb_xyz(r);
g = rgb_xyz(g);
b = rgb_xyz(b);
const x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
const y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.072175 * b) / Yn);
const z = xyz_lab((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / Zn);
return [x, y, z];
};
export const rgb2lab = (
rgb: [number, number, number]
): [number, number, number] => {
const [x, y, z] = rgb2xyz(rgb);
const l = 116 * y - 16;
return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z)];
};
export const lab2rgb = (
lab: [number, number, number]
): [number, number, number] => {
const [l, a, b] = lab;
let y = (l + 16) / 116;
let x = isNaN(a) ? y : y + a / 500;
let z = isNaN(b) ? y : y - b / 200;
y = Yn * lab_xyz(y);
x = Xn * lab_xyz(x);
z = Zn * lab_xyz(z);
const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB
const g = xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
const b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
return [r, g, b_];
};
export const lab2hex = (lab: [number, number, number]): string => {
const rgb = lab2rgb(lab);
return rgb2hex(rgb);
};

16
src/common/color/lab.ts Normal file
View File

@@ -0,0 +1,16 @@
// From https://github.com/gka/chroma.js
// Copyright (c) 2011-2019, Gregor Aisch
export const labDarken = (
lab: [number, number, number],
amount = 1
): [number, number, number] => {
return [lab[0] - 18 * amount, lab[1], lab[2]];
};
export const labBrighten = (
lab: [number, number, number],
amount = 1
): [number, number, number] => {
return labDarken(lab, -amount);
};

24
src/common/color/rgb.ts Normal file
View File

@@ -0,0 +1,24 @@
const luminosity = (rgb: [number, number, number]): number => {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
const lum: [number, number, number] = [0, 0, 0];
for (let i = 0; i < rgb.length; i++) {
const chan = rgb[i] / 255;
lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
}
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
};
export const rgbContrast = (
color1: [number, number, number],
color2: [number, number, number]
) => {
const lum1 = luminosity(color1);
const lum2 = luminosity(color2);
if (lum1 > lum2) {
return (lum1 + 0.05) / (lum2 + 0.05);
}
return (lum2 + 0.05) / (lum1 + 0.05);
};

View File

@@ -0,0 +1,9 @@
import { HomeAssistant } from "../../types";
/** Return an array of domains with the service. */
export const componentsWithService = (
hass: HomeAssistant,
service: string
): Array<string> =>
hass &&
Object.keys(hass.services).filter((key) => service in hass.services[key]);

View File

@@ -0,0 +1,9 @@
import { HomeAssistant } from "../../types";
/** Return if a service is loaded. */
export const isServiceLoaded = (
hass: HomeAssistant,
domain: string,
service: string
): boolean =>
hass && domain in hass.services && service in hass.services[domain];

View File

@@ -22,7 +22,6 @@ export const DOMAINS_WITH_CARD = [
"timer",
"vacuum",
"water_heater",
"weblink",
];
/** Domains with separate more info dialog. */
@@ -36,7 +35,6 @@ export const DOMAINS_WITH_MORE_INFO = [
"cover",
"fan",
"group",
"history_graph",
"humidifier",
"input_datetime",
"light",
@@ -46,7 +44,6 @@ export const DOMAINS_WITH_MORE_INFO = [
"script",
"sun",
"timer",
"updater",
"vacuum",
"water_heater",
"weather",
@@ -58,16 +55,10 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_select",
"input_text",
"scene",
"weblink",
];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = [
"camera",
"configurator",
"history_graph",
"scene",
];
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"];
/** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"];

View File

@@ -20,31 +20,29 @@ export default function relativeTime(
let delta = (compareTime.getTime() - dateObj.getTime()) / 1000;
const tense = delta >= 0 ? "past" : "future";
delta = Math.abs(delta);
let roundedDelta = Math.round(delta);
let timeDesc;
if (roundedDelta === 0) {
return localize("ui.components.relative_time.just_now");
}
let unit = "week";
for (let i = 0; i < tests.length; i++) {
if (delta < tests[i]) {
delta = Math.floor(delta);
timeDesc = localize(
`ui.components.relative_time.duration.${langKey[i]}`,
"count",
delta
);
if (roundedDelta < tests[i]) {
unit = langKey[i];
break;
}
delta /= tests[i];
roundedDelta = Math.round(delta);
}
if (timeDesc === undefined) {
delta = Math.floor(delta);
timeDesc = localize(
"ui.components.relative_time.duration.week",
"count",
delta
);
}
const timeDesc = localize(
`ui.components.relative_time.duration.${unit}`,
"count",
roundedDelta
);
return options.includeTense === false
? timeDesc

View File

@@ -0,0 +1,155 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyDeclaration, UpdatingElement } from "lit-element";
import type { ClassElement } from "../../types";
type Callback = (oldValue: any, newValue: any) => void;
class Storage {
constructor() {
window.addEventListener("storage", (ev: StorageEvent) => {
if (ev.key && this.hasKey(ev.key)) {
this._storage[ev.key] = ev.newValue
? JSON.parse(ev.newValue)
: ev.newValue;
if (this._listeners[ev.key]) {
this._listeners[ev.key].forEach((listener) =>
listener(
ev.oldValue ? JSON.parse(ev.oldValue) : ev.oldValue,
this._storage[ev.key!]
)
);
}
}
});
}
private _storage: { [storageKey: string]: any } = {};
private _listeners: {
[storageKey: string]: Callback[];
} = {};
public addFromStorage(storageKey: any): void {
if (!this._storage[storageKey]) {
const data = window.localStorage.getItem(storageKey);
if (data) {
this._storage[storageKey] = JSON.parse(data);
}
}
}
public subscribeChanges(
storageKey: string,
callback: Callback
): UnsubscribeFunc {
if (this._listeners[storageKey]) {
this._listeners[storageKey].push(callback);
} else {
this._listeners[storageKey] = [callback];
}
return () => {
this.unsubscribeChanges(storageKey, callback);
};
}
public unsubscribeChanges(storageKey: string, callback: Callback) {
if (!(storageKey in this._listeners)) {
return;
}
const index = this._listeners[storageKey].indexOf(callback);
if (index !== -1) {
this._listeners[storageKey].splice(index, 1);
}
}
public hasKey(storageKey: string): any {
return storageKey in this._storage;
}
public getValue(storageKey: string): any {
return this._storage[storageKey];
}
public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value;
try {
window.localStorage.setItem(storageKey, JSON.stringify(value));
} catch (err) {
// Safari in private mode doesn't allow localstorage
}
}
}
const storage = new Storage();
export const LocalStorage = (
storageKey?: string,
property?: boolean,
propertyOptions?: PropertyDeclaration
): any => {
return (clsElement: ClassElement) => {
const key = String(clsElement.key);
storageKey = storageKey || String(clsElement.key);
const initVal = clsElement.initializer
? clsElement.initializer()
: undefined;
storage.addFromStorage(storageKey);
const subscribe = (el: UpdatingElement): UnsubscribeFunc =>
storage.subscribeChanges(storageKey!, (oldValue) => {
el.requestUpdate(clsElement.key, oldValue);
});
const getValue = (): any => {
return storage.hasKey(storageKey!)
? storage.getValue(storageKey!)
: initVal;
};
const setValue = (el: UpdatingElement, value: any) => {
let oldValue: unknown | undefined;
if (property) {
oldValue = getValue();
}
storage.setValue(storageKey!, value);
if (property) {
el.requestUpdate(clsElement.key, oldValue);
}
};
return {
kind: "method",
placement: "prototype",
key: clsElement.key,
descriptor: {
set(this: UpdatingElement, value: unknown) {
setValue(this, value);
},
get() {
return getValue();
},
enumerable: true,
configurable: true,
},
finisher(cls: typeof UpdatingElement) {
if (property) {
const connectedCallback = cls.prototype.connectedCallback;
const disconnectedCallback = cls.prototype.disconnectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
this[`__unbsubLocalStorage${key}`] = subscribe(this);
};
cls.prototype.disconnectedCallback = function () {
disconnectedCallback.call(this);
this[`__unbsubLocalStorage${key}`]();
};
cls.createProperty(clsElement.key, {
noAccessor: true,
...propertyOptions,
});
}
},
};
};
};

View File

@@ -1,26 +1,20 @@
import { derivedStyles } from "../../resources/styles";
import { derivedStyles, darkStyles } from "../../resources/styles";
import { HomeAssistant, Theme } from "../../types";
import {
hex2rgb,
rgb2hex,
rgb2lab,
lab2rgb,
lab2hex,
} from "../color/convert-color";
import { rgbContrast } from "../color/rgb";
import { labDarken, labBrighten } from "../color/lab";
interface ProcessedTheme {
keys: { [key: string]: "" };
styles: { [key: string]: string };
}
const hexToRgb = (hex: string): string | null => {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const checkHex = hex.replace(shorthandRegex, (_m, r, g, b) => {
return r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(checkHex);
return result
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
result[3],
16
)}`
: null;
};
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
/**
@@ -33,17 +27,56 @@ let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
export const applyThemesOnElement = (
element,
themes: HomeAssistant["themes"],
selectedTheme?: string
selectedTheme?: string,
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
) => {
const newTheme = selectedTheme
? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
: undefined;
let cacheKey = selectedTheme;
let themeRules: Partial<Theme> = {};
if (!element._themes && !newTheme) {
if (selectedTheme === "default" && themeOptions) {
if (themeOptions.dark) {
cacheKey = `${cacheKey}__dark`;
themeRules = darkStyles;
}
if (themeOptions.primaryColor) {
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
const rgbPrimaryColor = hex2rgb(themeOptions.primaryColor);
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
themeRules["primary-color"] = themeOptions.primaryColor;
const rgbLigthPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
themeRules["light-primary-color"] = rgb2hex(rgbLigthPrimaryColor);
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
themeRules["text-primary-color"] =
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
themeRules["text-light-primary-color"] =
rgbContrast(rgbLigthPrimaryColor, [33, 33, 33]) < 6
? "#fff"
: "#212121";
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
}
if (themeOptions.accentColor) {
cacheKey = `${cacheKey}__accent_${themeOptions.accentColor}`;
themeRules["accent-color"] = themeOptions.accentColor;
const rgbAccentColor = hex2rgb(themeOptions.accentColor);
themeRules["text-accent-color"] =
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
}
}
if (selectedTheme && themes.themes[selectedTheme]) {
themeRules = themes.themes[selectedTheme];
}
if (!element._themes && !Object.keys(themeRules).length) {
// No styles to reset, and no styles to set
return;
}
const newTheme =
themeRules && cacheKey
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
: undefined;
// Add previous set keys to reset them, and new theme
const styles = { ...element._themes, ...newTheme?.styles };
element._themes = newTheme?.keys;
@@ -58,42 +91,45 @@ export const applyThemesOnElement = (
};
const processTheme = (
themeName: string,
themes: HomeAssistant["themes"]
cacheKey: string,
theme: Partial<Theme>
): ProcessedTheme | undefined => {
if (!themes.themes[themeName]) {
if (!theme || !Object.keys(theme).length) {
return undefined;
}
const theme: Theme = {
const combinedTheme: Partial<Theme> = {
...derivedStyles,
...themes.themes[themeName],
...theme,
};
const styles = {};
const keys = {};
for (const key of Object.keys(theme)) {
for (const key of Object.keys(combinedTheme)) {
const prefixedKey = `--${key}`;
const value = theme[key];
const value = String(combinedTheme[key]!);
styles[prefixedKey] = value;
keys[prefixedKey] = "";
// Try to create a rgb value for this key if it is a hex color
if (!value.startsWith("#")) {
// Not a hex color
// Try to create a rgb value for this key if it is not a var
if (value.startsWith("#")) {
// Can't convert non hex value
continue;
}
const rgbKey = `rgb-${key}`;
if (theme[rgbKey] !== undefined) {
if (combinedTheme[rgbKey] !== undefined) {
// Theme has it's own rgb value
continue;
}
const rgbValue = hexToRgb(value);
if (rgbValue !== null) {
try {
const rgbValue = hex2rgb(value).join(",");
const prefixedRgbKey = `--${rgbKey}`;
styles[prefixedRgbKey] = rgbValue;
keys[prefixedRgbKey] = "";
} catch (e) {
continue;
}
}
PROCESSED_THEMES[themeName] = { styles, keys };
PROCESSED_THEMES[cacheKey] = { styles, keys };
return { styles, keys };
};

View File

@@ -1,4 +1,4 @@
import type { Map } from "leaflet";
import type { Map, TileLayer } from "leaflet";
// Sets up a Leaflet map on the provided DOM element
export type LeafletModuleType = typeof import("leaflet");
@@ -6,9 +6,9 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async (
mapElement: HTMLElement,
darkMode = false,
darkMode?: boolean,
draw = false
): Promise<[Map, LeafletModuleType]> => {
): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element");
}
@@ -28,15 +28,28 @@ export const setupLeafletMap = async (
style.setAttribute("rel", "stylesheet");
mapElement.parentNode.appendChild(style);
map.setView([52.3731339, 4.8903147], 13);
createTileLayer(Leaflet, darkMode).addTo(map);
return [map, Leaflet];
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
return [map, Leaflet, tileLayer];
};
export const createTileLayer = (
export const replaceTileLayer = (
leaflet: LeafletModuleType,
map: Map,
tileLayer: TileLayer,
darkMode: boolean
): TileLayer => {
map.removeLayer(tileLayer);
tileLayer = createTileLayer(leaflet, darkMode);
tileLayer.addTo(map);
return tileLayer;
};
const createTileLayer = (
leaflet: LeafletModuleType,
darkMode: boolean
) => {
): TileLayer => {
return leaflet.tileLayer(
`https://{s}.basemaps.cartocdn.com/${
darkMode ? "dark_all" : "light_all"

View File

@@ -0,0 +1,28 @@
/** Return an icon representing a battery state. */
import { HassEntity } from "home-assistant-js-websocket";
export const batteryIcon = (
batteryState: HassEntity,
batteryChargingState?: HassEntity
) => {
const battery = Number(batteryState.state);
const battery_charging =
batteryChargingState && batteryChargingState.state === "on";
if (isNaN(battery)) {
return "hass:battery-unknown";
}
let icon = "hass:battery";
const batteryRound = Math.round(battery / 10) * 10;
if (battery_charging && battery > 10) {
icon += `-charging-${batteryRound}`;
} else if (battery_charging) {
icon += "-outline";
} else if (battery <= 5) {
icon += "-alert";
} else if (battery > 5 && battery < 95) {
icon += `-${batteryRound}`;
}
return icon;
};

View File

@@ -3,49 +3,51 @@ import { HassEntity } from "home-assistant-js-websocket";
/** Return an icon representing a binary sensor state. */
export const binarySensorIcon = (state: HassEntity) => {
const activated = state.state && state.state === "off";
const is_off = state.state && state.state === "off";
switch (state.attributes.device_class) {
case "battery":
return activated ? "hass:battery" : "hass:battery-outline";
return is_off ? "hass:battery" : "hass:battery-outline";
case "battery_charging":
return is_off ? "hass:battery" : "hass:battery-charging";
case "cold":
return activated ? "hass:thermometer" : "hass:snowflake";
return is_off ? "hass:thermometer" : "hass:snowflake";
case "connectivity":
return activated ? "hass:server-network-off" : "hass:server-network";
return is_off ? "hass:server-network-off" : "hass:server-network";
case "door":
return activated ? "hass:door-closed" : "hass:door-open";
return is_off ? "hass:door-closed" : "hass:door-open";
case "garage_door":
return activated ? "hass:garage" : "hass:garage-open";
return is_off ? "hass:garage" : "hass:garage-open";
case "gas":
case "power":
case "problem":
case "safety":
case "smoke":
return activated ? "hass:shield-check" : "hass:alert";
return is_off ? "hass:shield-check" : "hass:alert";
case "heat":
return activated ? "hass:thermometer" : "hass:fire";
return is_off ? "hass:thermometer" : "hass:fire";
case "light":
return activated ? "hass:brightness-5" : "hass:brightness-7";
return is_off ? "hass:brightness-5" : "hass:brightness-7";
case "lock":
return activated ? "hass:lock" : "hass:lock-open";
return is_off ? "hass:lock" : "hass:lock-open";
case "moisture":
return activated ? "hass:water-off" : "hass:water";
return is_off ? "hass:water-off" : "hass:water";
case "motion":
return activated ? "hass:walk" : "hass:run";
return is_off ? "hass:walk" : "hass:run";
case "occupancy":
return activated ? "hass:home-outline" : "hass:home";
return is_off ? "hass:home-outline" : "hass:home";
case "opening":
return activated ? "hass:square" : "hass:square-outline";
return is_off ? "hass:square" : "hass:square-outline";
case "plug":
return activated ? "hass:power-plug-off" : "hass:power-plug";
return is_off ? "hass:power-plug-off" : "hass:power-plug";
case "presence":
return activated ? "hass:home-outline" : "hass:home";
return is_off ? "hass:home-outline" : "hass:home";
case "sound":
return activated ? "hass:music-note-off" : "hass:music-note";
return is_off ? "hass:music-note-off" : "hass:music-note";
case "vibration":
return activated ? "hass:crop-portrait" : "hass:vibrate";
return is_off ? "hass:crop-portrait" : "hass:vibrate";
case "window":
return activated ? "hass:window-closed" : "hass:window-open";
return is_off ? "hass:window-closed" : "hass:window-open";
default:
return activated ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
return is_off ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
}
};

View File

@@ -13,14 +13,13 @@ const fixedIcons = {
calendar: "hass:calendar",
camera: "hass:video",
climate: "hass:thermostat",
configurator: "hass:settings",
configurator: "hass:cog",
conversation: "hass:text-to-speech",
counter: "hass:counter",
device_tracker: "hass:account",
fan: "hass:fan",
google_assistant: "hass:google-assistant",
group: "hass:google-circles-communities",
history_graph: "hass:chart-line",
homeassistant: "hass:home-assistant",
homekit: "hass:home-automation",
humidifier: "hass:air-humidifier",
@@ -29,7 +28,7 @@ const fixedIcons = {
input_datetime: "hass:calendar-clock",
input_number: "hass:ray-vertex",
input_select: "hass:format-list-bulleted",
input_text: "hass:textbox",
input_text: "hass:form-textbox",
light: "hass:lightbulb",
mailbox: "hass:mailbox",
notify: "hass:comment-alert",
@@ -44,12 +43,11 @@ const fixedIcons = {
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
switch: "hass:flash",
timer: "hass:timer",
timer: "hass:timer-outline",
updater: "hass:cloud-upload",
vacuum: "hass:robot-vacuum",
water_heater: "hass:thermometer",
weather: "hass:weather-cloudy",
weblink: "hass:open-in-new",
zone: "hass:map-marker-radius",
};

View File

@@ -2,14 +2,19 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNIT_C, UNIT_F } from "../const";
import { domainIcon } from "./domain_icon";
import { batteryIcon } from "./battery_icon";
const fixedDeviceClassIcons = {
current: "hass:current-ac",
energy: "hass:flash",
humidity: "hass:water-percent",
illuminance: "hass:brightness-5",
temperature: "hass:thermometer",
pressure: "hass:gauge",
power: "hass:flash",
power_factor: "hass:angle-acute",
signal_strength: "hass:wifi",
voltage: "hass:sine-wave",
};
export const sensorIcon = (state: HassEntity) => {
@@ -19,29 +24,7 @@ export const sensorIcon = (state: HassEntity) => {
return fixedDeviceClassIcons[dclass];
}
if (dclass === "battery") {
const battery = Number(state.state);
if (isNaN(battery)) {
return "hass:battery-unknown";
}
const batteryRound = Math.round(battery / 10) * 10;
if (batteryRound >= 100) {
return "hass:battery";
}
if (batteryRound <= 0) {
return "hass:battery-alert";
}
// Will return one of the following icons: (listed so extractor picks up)
// hass:battery-10
// hass:battery-20
// hass:battery-30
// hass:battery-40
// hass:battery-50
// hass:battery-60
// hass:battery-70
// hass:battery-80
// hass:battery-90
// We obscure 'hass' in iconname so this name does not get picked up
return `${"hass"}:battery-${batteryRound}`;
return batteryIcon(state);
}
const unit = state.attributes.unit_of_measurement;

View File

@@ -3,9 +3,10 @@ import { HomeAssistant } from "../../types";
import { DOMAINS_WITH_CARD } from "../const";
import { canToggleState } from "./can_toggle_state";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE } from "../../data/entity";
export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => {
if (stateObj.state === "unavailable") {
if (stateObj.state === UNAVAILABLE) {
return "display";
}

View File

@@ -4,6 +4,6 @@ export const supportsFeature = (
stateObj: HassEntity,
feature: number
): boolean => {
// eslint-disable-next-line:no-bitwise
// eslint-disable-next-line no-bitwise
return (stateObj.attributes.supported_features! & feature) !== 0;
};

View File

@@ -1,7 +1,12 @@
import { HassEntity } from "home-assistant-js-websocket";
import durationToSeconds from "../datetime/duration_to_seconds";
export const timerTimeRemaining = (stateObj: HassEntity) => {
export const timerTimeRemaining = (
stateObj: HassEntity
): undefined | number => {
if (!stateObj.attributes.remaining) {
return undefined;
}
let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
if (stateObj.state === "active") {

View File

@@ -2,11 +2,3 @@ const validEntityId = /^(\w+)\.(\w+)$/;
export const isValidEntityId = (entityId: string) =>
validEntityId.test(entityId);
export const createValidEntityId = (input: string) =>
input
.toLowerCase()
.replace(/\s|'|\./g, "_") // replace spaces, points and quotes with underscore
.replace(/\W/g, "") // remove not allowed chars
.replace(/_{2,}/g, "_") // replace multiple underscores with 1
.replace(/_$/, ""); // remove underscores at the end

View File

@@ -0,0 +1,14 @@
import {
ListItem,
RequestSelectedDetail,
} from "@material/mwc-list/mwc-list-item";
export const shouldHandleRequestSelectedEvent = (
ev: CustomEvent<RequestSelectedDetail>
): boolean => {
if (!ev.detail.selected || ev.detail.source !== "property") {
return false;
}
(ev.currentTarget as ListItem).selected = false;
return true;
};

View File

@@ -26,6 +26,9 @@ class SearchInput extends LitElement {
@property({ type: Boolean })
public autofocus = false;
@property({ type: String })
public label?: string;
public focus() {
this.shadowRoot!.querySelector("paper-input")!.focus();
}
@@ -43,7 +46,7 @@ class SearchInput extends LitElement {
<paper-input
class=${classMap({ "no-underline": this.noUnderline })}
.autofocus=${this.autofocus}
label="Search"
.label=${this.label || "Search"}
.value=${this.filter}
@value-changed=${this._filterInputChanged}
.noLabelFloat=${this.noLabelFloat}

View File

@@ -1,19 +1,19 @@
// https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1
export const slugify = (value: string) => {
export const slugify = (value: string, delimiter = "_") => {
const a =
"àáäâãåăæąçćčđďèéěėëêęğǵḧìíïîįłḿǹńňñòóöôœøṕŕřßşśšșťțùúüûǘůűūųẃẍÿýźžż·/_,:;";
const b =
"aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz------";
const b = `aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}`;
const p = new RegExp(a.split("").join("|"), "g");
return value
.toString()
.toLowerCase()
.replace(/\s+/g, "-") // Replace spaces with -
.replace(/\s+/g, delimiter) // Replace spaces with delimiter
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, "-and-") // Replace & with 'and'
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
.replace(/[^\w-]+/g, "") // Remove all non-word characters
.replace(/--+/g, "-") // Replace multiple - with single -
.replace(/^-+/, "") // Trim - from start of text
.replace(/-+$/, ""); // Trim - from end of text
.replace(/-/, delimiter) // Replace - with delimiter
.replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single delimiter
.replace(new RegExp(`/^${delimiter}+/`), "") // Trim delimiter from start of text
.replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
};

View File

@@ -19,7 +19,13 @@ export const iconColorCSS = css`
ha-icon[data-domain="sun"][data-state="above_horizon"],
ha-icon[data-domain="switch"][data-state="on"],
ha-icon[data-domain="timer"][data-state="active"],
ha-icon[data-domain="vacuum"][data-state="cleaning"] {
ha-icon[data-domain="vacuum"][data-state="cleaning"],
ha-icon[data-domain="group"][data-state="on"],
ha-icon[data-domain="group"][data-state="home"],
ha-icon[data-domain="group"][data-state="open"],
ha-icon[data-domain="group"][data-state="locked"],
ha-icon[data-domain="group"][data-state="problem"]
{
color: var(--paper-item-icon-active-color, #fdd835);
}

View File

@@ -9,5 +9,9 @@ export function computeRTL(hass: HomeAssistant) {
}
export function computeRTLDirection(hass: HomeAssistant) {
return computeRTL(hass) ? "rtl" : "ltr";
return emitRTLDirection(computeRTL(hass));
}
export function emitRTLDirection(rtl: boolean) {
return rtl ? "rtl" : "ltr";
}

View File

@@ -0,0 +1,50 @@
// From: underscore.js https://github.com/jashkenas/underscore/blob/master/underscore.js
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `false for leading`. To disable execution on the trailing edge, ditto.
export const throttle = <T extends Function>(
func: T,
wait: number,
leading = true,
trailing = true
): T => {
let timeout: number | undefined;
let previous = 0;
let context: any;
let args: any;
const later = () => {
previous = leading === false ? 0 : Date.now();
timeout = undefined;
func.apply(context, args);
if (!timeout) {
context = null;
args = null;
}
};
// @ts-ignore
return function (...argmnts) {
// @ts-ignore
// @typescript-eslint/no-this-alias
context = this;
args = argmnts;
const now = Date.now();
if (!previous && leading === false) {
previous = now;
}
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
previous = now;
func.apply(context, args);
} else if (!timeout && trailing !== false) {
timeout = window.setTimeout(later, remaining);
}
};
};

View File

@@ -54,8 +54,8 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
callService() {
this.progress = true;
// eslint-disable-next-line @typescript-eslint/no-this-alias
var el = this;
var eventData = {
const el = this;
const eventData = {
domain: this.domain,
service: this.service,
serviceData: this.serviceData,

View File

@@ -1,110 +0,0 @@
import "@material/mwc-button";
import "../ha-circular-progress";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HaProgressButton extends PolymerElement {
static get template() {
return html`
<style>
:host {
outline: none;
}
.container {
position: relative;
display: inline-block;
}
mwc-button {
transition: all 1s;
}
.success mwc-button {
--mdc-theme-primary: white;
background-color: var(--success-color);
transition: none;
}
.error mwc-button {
--mdc-theme-primary: white;
background-color: var(--error-color);
transition: none;
}
.progress {
@apply --layout;
@apply --layout-center-center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
<div class="container" id="container">
<mwc-button
id="button"
disabled="[[computeDisabled(disabled, progress)]]"
on-click="buttonTapped"
>
<slot></slot>
</mwc-button>
<template is="dom-if" if="[[progress]]">
<div class="progress">
<ha-circular-progress active size="small"></ha-circular-progress>
</div>
</template>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
progress: {
type: Boolean,
value: false,
},
disabled: {
type: Boolean,
value: false,
},
};
}
tempClass(className) {
var classList = this.$.container.classList;
classList.add(className);
setTimeout(() => {
classList.remove(className);
}, 1000);
}
ready() {
super.ready();
this.addEventListener("click", (ev) => this.buttonTapped(ev));
}
buttonTapped(ev) {
if (this.progress) ev.stopPropagation();
}
actionSuccess() {
this.tempClass("success");
}
actionError() {
this.tempClass("error");
}
computeDisabled(disabled, progress) {
return disabled || progress;
}
}
customElements.define("ha-progress-button", HaProgressButton);

View File

@@ -0,0 +1,114 @@
import "@material/mwc-button";
import type { Button } from "@material/mwc-button";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
query,
} from "lit-element";
import "../ha-circular-progress";
@customElement("ha-progress-button")
class HaProgressButton extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public progress = false;
@property({ type: Boolean }) public raised = false;
@query("mwc-button") private _button?: Button;
public render(): TemplateResult {
return html`
<mwc-button
?raised=${this.raised}
.disabled=${this.disabled || this.progress}
@click=${this._buttonTapped}
>
<slot></slot>
</mwc-button>
${this.progress
? html`<div class="progress">
<ha-circular-progress size="small" active></ha-circular-progress>
</div>`
: ""}
`;
}
public actionSuccess(): void {
this._tempClass("success");
}
public actionError(): void {
this._tempClass("error");
}
private _tempClass(className: string): void {
this._button!.classList.add(className);
setTimeout(() => {
this._button!.classList.remove(className);
}, 1000);
}
private _buttonTapped(ev: Event): void {
if (this.progress) {
ev.stopPropagation();
}
}
static get styles(): CSSResult {
return css`
:host {
outline: none;
display: inline-block;
position: relative;
}
mwc-button {
transition: all 1s;
}
mwc-button.success {
--mdc-theme-primary: white;
background-color: var(--success-color);
transition: none;
}
mwc-button[raised].success {
--mdc-theme-primary: var(--success-color);
--mdc-theme-on-primary: white;
}
mwc-button.error {
--mdc-theme-primary: white;
background-color: var(--error-color);
transition: none;
}
mwc-button[raised].error {
--mdc-theme-primary: var(--error-color);
--mdc-theme-on-primary: white;
}
.progress {
bottom: 0;
margin-top: 4px;
position: absolute;
text-align: center;
top: 0;
width: 100%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-progress-button": HaProgressButton;
}
}

View File

@@ -3,19 +3,21 @@ import {
css,
CSSResult,
customElement,
eventOptions,
html,
internalProperty,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
eventOptions,
internalProperty,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { ifDefined } from "lit-html/directives/if-defined";
import { styleMap } from "lit-html/directives/style-map";
import { scroll } from "lit-virtualizer";
import memoizeOne from "memoize-one";
import { restoreScroll } from "../../common/decorators/restore-scroll";
import { fireEvent } from "../../common/dom/fire_event";
import "../../common/search/search-input";
import { debounce } from "../../common/util/debounce";
@@ -24,8 +26,6 @@ import "../ha-checkbox";
import type { HaCheckbox } from "../ha-checkbox";
import "../ha-icon";
import { filterData, sortData } from "./sort-filter";
import memoizeOne from "memoize-one";
import { restoreScroll } from "../../common/decorators/restore-scroll";
declare global {
// for fire event
@@ -69,6 +69,8 @@ export interface DataTableColumnData extends DataTableSortColumnData {
width?: string;
maxWidth?: string;
grows?: boolean;
forceLTR?: boolean;
hidden?: boolean;
}
export interface DataTableRowData {
@@ -97,6 +99,8 @@ export class HaDataTable extends LitElement {
@property({ type: String }) public noDataText?: string;
@property({ type: String }) public searchLabel?: string;
@property({ type: String }) public filter = "";
@internalProperty() private _filterable = false;
@@ -201,6 +205,7 @@ export class HaDataTable extends LitElement {
<div class="table-header">
<search-input
@value-changed=${this._handleSearchChange}
.label=${this.searchLabel}
></search-input>
</div>
`
@@ -210,13 +215,15 @@ export class HaDataTable extends LitElement {
class="mdc-data-table__table ${classMap({
"auto-height": this.autoHeight,
})}"
role="table"
aria-rowcount=${this._filteredData.length}
style=${styleMap({
height: this.autoHeight
? `${(this._filteredData.length || 1) * 53 + 57}px`
: `calc(100% - ${this._header?.clientHeight}px)`,
})}
>
<div class="mdc-data-table__header-row">
<div class="mdc-data-table__header-row" role="row">
${this.selectable
? html`
<div
@@ -236,8 +243,10 @@ export class HaDataTable extends LitElement {
</div>
`
: ""}
${Object.entries(this.columns).map((columnEntry) => {
const [key, column] = columnEntry;
${Object.entries(this.columns).map(([key, column]) => {
if (column.hidden) {
return "";
}
const sorted = key === this._sortColumn;
const classes = {
"mdc-data-table__header-cell--numeric": Boolean(
@@ -284,8 +293,8 @@ export class HaDataTable extends LitElement {
${!this._filteredData.length
? html`
<div class="mdc-data-table__content">
<div class="mdc-data-table__row">
<div class="mdc-data-table__cell grows center">
<div class="mdc-data-table__row" role="row">
<div class="mdc-data-table__cell grows center" role="cell">
${this.noDataText || "No data"}
</div>
</div>
@@ -300,12 +309,14 @@ export class HaDataTable extends LitElement {
items: !this.hasFab
? this._filteredData
: [...this._filteredData, ...[{ empty: true }]],
renderItem: (row: DataTableRowData) => {
renderItem: (row: DataTableRowData, index) => {
if (row.empty) {
return html` <div class="mdc-data-table__row"></div> `;
}
return html`
<div
aria-rowindex=${index}
role="row"
.rowId="${row[this.id]}"
@click=${this._handleRowClick}
class="mdc-data-table__row ${classMap({
@@ -324,6 +335,7 @@ export class HaDataTable extends LitElement {
? html`
<div
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
role="cell"
>
<ha-checkbox
class="mdc-data-table__row-checkbox"
@@ -337,39 +349,45 @@ export class HaDataTable extends LitElement {
</div>
`
: ""}
${Object.entries(this.columns).map((columnEntry) => {
const [key, column] = columnEntry;
return html`
<div
class="mdc-data-table__cell ${classMap({
"mdc-data-table__cell--numeric": Boolean(
column.type === "numeric"
),
"mdc-data-table__cell--icon": Boolean(
column.type === "icon"
),
"mdc-data-table__cell--icon-button": Boolean(
column.type === "icon-button"
),
grows: Boolean(column.grows),
})}"
style=${column.width
? styleMap({
[column.grows
? "minWidth"
: "width"]: column.width,
maxWidth: column.maxWidth
? column.maxWidth
: "",
})
: ""}
>
${column.template
? column.template(row[key], row)
: row[key]}
</div>
`;
})}
${Object.entries(this.columns).map(
([key, column]) => {
if (column.hidden) {
return "";
}
return html`
<div
role="cell"
class="mdc-data-table__cell ${classMap({
"mdc-data-table__cell--numeric": Boolean(
column.type === "numeric"
),
"mdc-data-table__cell--icon": Boolean(
column.type === "icon"
),
"mdc-data-table__cell--icon-button": Boolean(
column.type === "icon-button"
),
grows: Boolean(column.grows),
forceLTR: Boolean(column.forceLTR),
})}"
style=${column.width
? styleMap({
[column.grows
? "minWidth"
: "width"]: column.width,
maxWidth: column.maxWidth
? column.maxWidth
: "",
})
: ""}
>
${column.template
? column.template(row[key], row)
: row[key]}
</div>
`;
}
)}
</div>
`;
},
@@ -536,7 +554,7 @@ export class HaDataTable extends LitElement {
border-radius: 4px;
border-width: 1px;
border-style: solid;
border-color: rgba(var(--rgb-primary-text-color), 0.12);
border-color: var(--divider-color);
display: inline-flex;
flex-direction: column;
box-sizing: border-box;
@@ -554,7 +572,7 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__row ~ .mdc-data-table__row {
border-top: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
border-top: 1px solid var(--divider-color);
}
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
@@ -573,7 +591,7 @@ export class HaDataTable extends LitElement {
height: 56px;
display: flex;
width: 100%;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
border-bottom: 1px solid var(--divider-color);
overflow-x: auto;
}
@@ -604,10 +622,8 @@ export class HaDataTable extends LitElement {
padding-right: 0;
width: 56px;
}
[dir="rtl"] .mdc-data-table__header-cell--checkbox,
.mdc-data-table__header-cell--checkbox[dir="rtl"],
[dir="rtl"] .mdc-data-table__cell--checkbox,
.mdc-data-table__cell--checkbox[dir="rtl"] {
:host([dir="rtl"]) .mdc-data-table__header-cell--checkbox,
:host([dir="rtl"]) .mdc-data-table__cell--checkbox {
/* @noflip */
padding-left: 0;
/* @noflip */
@@ -641,8 +657,7 @@ export class HaDataTable extends LitElement {
.mdc-data-table__cell--numeric {
text-align: right;
}
[dir="rtl"] .mdc-data-table__cell--numeric,
.mdc-data-table__cell--numeric[dir="rtl"] {
:host([dir="rtl"]) .mdc-data-table__cell--numeric {
/* @noflip */
text-align: left;
}
@@ -660,18 +675,33 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell.mdc-data-table__header-cell--icon {
text-align: center;
}
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover,
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(.not-sorted) {
text-align: left;
}
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover,
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(.not-sorted) {
text-align: right;
}
.mdc-data-table__cell--icon:first-child ha-icon {
margin-left: 8px;
}
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon {
margin-left: auto;
margin-right: 8px;
}
.mdc-data-table__cell--icon:first-child state-badge {
margin-right: -8px;
}
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child state-badge {
margin-right: auto;
margin-left: -8px;
}
.mdc-data-table__header-cell--icon-button,
.mdc-data-table__cell--icon-button {
@@ -689,12 +719,22 @@ export class HaDataTable extends LitElement {
width: 64px;
padding-left: 16px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:first-child,
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:first-child {
padding-left: auto;
padding-right: 16px;
}
.mdc-data-table__header-cell--icon-button:last-child,
.mdc-data-table__cell--icon-button:last-child {
width: 64px;
padding-right: 16px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child,
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child {
padding-right: auto;
padding-left: 16px;
}
.mdc-data-table__cell--icon-button a {
color: var(--secondary-text-color);
@@ -712,8 +752,7 @@ export class HaDataTable extends LitElement {
text-transform: inherit;
text-align: left;
}
[dir="rtl"] .mdc-data-table__header-cell,
.mdc-data-table__header-cell[dir="rtl"] {
:host([dir="rtl"]) .mdc-data-table__header-cell {
/* @noflip */
text-align: right;
}
@@ -725,11 +764,15 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) {
text-align: left;
}
[dir="rtl"] .mdc-data-table__header-cell--numeric,
.mdc-data-table__header-cell--numeric[dir="rtl"] {
:host([dir="rtl"]) .mdc-data-table__header-cell--numeric {
/* @noflip */
text-align: left;
}
:host([dir="rtl"]) .mdc-data-table__header-cell--numeric.sortable:hover,
:host([dir="rtl"])
.mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) {
text-align: right;
}
/* custom from here */
@@ -750,6 +793,10 @@ export class HaDataTable extends LitElement {
position: relative;
left: 0px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell span {
left: auto;
right: 0px;
}
.mdc-data-table__header-cell.sortable {
cursor: pointer;
@@ -757,6 +804,9 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell > * {
transition: left 0.2s ease;
}
:host([dir="rtl"]) .mdc-data-table__header-cell > * {
transition: right 0.2s ease;
}
.mdc-data-table__header-cell ha-icon {
top: -3px;
position: absolute;
@@ -764,16 +814,37 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell.not-sorted ha-icon {
left: -20px;
}
:host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-icon {
right: -20px;
}
.mdc-data-table__header-cell.sortable:not(.not-sorted) span,
.mdc-data-table__header-cell.sortable.not-sorted:hover span {
left: 24px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable:not(.not-sorted)
span,
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable.not-sorted:hover
span {
left: auto;
right: 24px;
}
.mdc-data-table__header-cell.sortable:not(.not-sorted) ha-icon,
.mdc-data-table__header-cell.sortable:hover.not-sorted ha-icon {
left: 12px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable:not(.not-sorted)
ha-icon,
:host([dir="rtl"])
.mdc-data-table__header-cell.sortable:hover.not-sorted
ha-icon {
left: auto;
right: 12px;
}
.table-header {
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
border-bottom: 1px solid var(--divider-color);
padding: 0 16px;
}
search-input {
@@ -802,6 +873,9 @@ export class HaDataTable extends LitElement {
flex-grow: 1;
flex-shrink: 1;
}
.forceLTR {
direction: ltr;
}
`;
}
}

View File

@@ -1,11 +1,11 @@
// To use comlink under ES5
import "proxy-polyfill";
import { expose } from "comlink";
import "proxy-polyfill";
import type {
DataTableSortColumnData,
DataTableRowData,
SortingDirection,
DataTableSortColumnData,
SortableColumnContainer,
SortingDirection,
} from "./ha-data-table";
const filterData = (
@@ -19,7 +19,7 @@ const filterData = (
const [key, column] = columnEntry;
if (column.filterable) {
if (
(column.filterKey ? row[key][column.filterKey] : row[key])
String(column.filterKey ? row[key][column.filterKey] : row[key])
.toUpperCase()
.includes(filter)
) {

View File

@@ -135,7 +135,7 @@ class DateRangePickerElement extends WrappedElement {
}
.daterangepicker td.in-range {
background-color: var(--light-primary-color);
color: var(--primary-text-color);
color: var(--text-light-primary-color, var(--primary-text-color));
}
.daterangepicker td.active,
.daterangepicker td.active:hover {

View File

@@ -13,6 +13,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -87,7 +88,7 @@ const rowRenderer = (
@customElement("ha-area-devices-picker")
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@@ -124,13 +125,13 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean })
private _opened?: boolean;
@property() private _areaPicker = true;
@internalProperty() private _areaPicker = true;
@property() private _devices?: DeviceRegistryEntry[];
@internalProperty() private _devices?: DeviceRegistryEntry[];
@property() private _areas?: AreaRegistryEntry[];
@internalProperty() private _areas?: AreaRegistryEntry[];
@property() private _entities?: EntityRegistryEntry[];
@internalProperty() private _entities?: EntityRegistryEntry[];
private _selectedDevices: string[] = [];

View File

@@ -8,6 +8,7 @@ import {
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../common/dom/fire_event";
@@ -24,7 +25,7 @@ const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
export abstract class HaDeviceAutomationPicker<
T extends DeviceAutomation
> extends LitElement {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@@ -36,11 +37,11 @@ export abstract class HaDeviceAutomationPicker<
protected UNKNOWN_AUTOMATION_TEXT = "Unknown automation";
@property() private _automations: T[] = [];
@internalProperty() private _automations: T[] = [];
// Trigger an empty render so we start with a clean DOM.
// paper-listbox does not like changing things around.
@property() private _renderEmpty = false;
@internalProperty() private _renderEmpty = false;
private _localizeDeviceAutomation: (
hass: HomeAssistant,
@@ -116,11 +117,7 @@ export abstract class HaDeviceAutomationPicker<
>
${this.NO_AUTOMATION_TEXT}
</paper-item>
<paper-item
key=${UNKNOWN_AUTOMATION_KEY}
.automation=${this.value}
hidden
>
<paper-item key=${UNKNOWN_AUTOMATION_KEY} hidden>
${this.UNKNOWN_AUTOMATION_TEXT}
</paper-item>
${this._automations.map(
@@ -174,18 +171,17 @@ export abstract class HaDeviceAutomationPicker<
}
private _automationChanged(ev) {
this._setValue(ev.detail.item.automation);
if (ev.detail.item.automation) {
this._setValue(ev.detail.item.automation);
}
}
private _setValue(automation: T) {
if (this.value && deviceAutomationsEqual(automation, this.value)) {
return;
}
this.value = automation;
setTimeout(() => {
fireEvent(this, "change");
fireEvent(this, "value-changed", { value: automation });
}, 0);
fireEvent(this, "change");
fireEvent(this, "value-changed", { value: automation });
}
static get styles(): CSSResult {

View File

@@ -66,7 +66,7 @@ const rowRenderer = (root: HTMLElement, _owner, model: { item: Device }) => {
@customElement("ha-device-picker")
export class HaDevicePicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;

View File

@@ -12,7 +12,7 @@ import "./ha-device-picker";
@customElement("ha-devices-picker")
class HaDevicesPicker extends LitElement {
@property() public hass?: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public value?: string[];

View File

@@ -23,10 +23,10 @@ export const HaIronFocusablesHelper = {
* @return {!Array<!HTMLElement>}
*/
getTabbableNodes: function (node) {
var result = [];
const result = [];
// If there is at least one element with tabindex > 0, we need to sort
// the final array by tabindex.
var needsSortByTabIndex = this._collectTabbableNodes(node, result);
const needsSortByTabIndex = this._collectTabbableNodes(node, result);
if (needsSortByTabIndex) {
return IronFocusablesHelper._sortByTabIndex(result);
}
@@ -50,9 +50,9 @@ export const HaIronFocusablesHelper = {
) {
return false;
}
var element = /** @type {!HTMLElement} */ (node);
var tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
var needsSort = tabIndex > 0;
const element = /** @type {!HTMLElement} */ (node);
const tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
let needsSort = tabIndex > 0;
if (tabIndex >= 0) {
result.push(element);
}
@@ -70,7 +70,7 @@ export const HaIronFocusablesHelper = {
// <input id="B" slot="b" tabindex="1">
// </div>
// TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
var children;
let children;
if (element.localName === "content" || element.localName === "slot") {
children = dom(element).getDistributedNodes();
} else {
@@ -80,7 +80,7 @@ export const HaIronFocusablesHelper = {
children = dom(element.shadowRoot || element.root || element).children;
// /////////////////////////
}
for (var i = 0; i < children.length; i++) {
for (let i = 0; i < children.length; i++) {
// Ensure method is always invoked to collect tabbable children.
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
}

View File

@@ -0,0 +1,18 @@
import { batteryIcon } from "../../common/entity/battery_icon";
import "../ha-icon";
import { customElement, html, property, LitElement } from "lit-element";
@customElement("ha-battery-icon")
export class HaBatteryIcon extends LitElement {
@property() public batteryStateObj;
@property() public batteryChargingStateObj;
protected render() {
return html`
<ha-icon
.icon=${batteryIcon(this.batteryStateObj, this.batteryChargingStateObj)}
></ha-icon>
`;
}
}

View File

@@ -1,12 +1,12 @@
/* eslint-plugin-disable lit */
import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior";
import "../ha-icon-button";
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
import { timeOut } from "@polymer/polymer/lib/utils/async";
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { formatTime } from "../../common/datetime/format_time";
import "../ha-icon-button";
// eslint-disable-next-line no-unused-vars
/* global Chart moment Color */
@@ -355,7 +355,7 @@ class HaChartBase extends mixinBehaviors(
return value;
}
const date = new Date(values[index].value);
return formatTime(date);
return formatTime(date, this.hass.language);
}
drawChart() {
@@ -420,7 +420,7 @@ class HaChartBase extends mixinBehaviors(
},
};
options = Chart.helpers.merge(options, this.data.options);
options.scales.xAxes[0].ticks.callback = this._formatTickValue;
options.scales.xAxes[0].ticks.callback = this._formatTickValue.bind(this);
if (this.data.type === "timeline") {
this.set("isTimeline", true);
if (this.data.colors !== undefined) {

View File

@@ -15,7 +15,7 @@ import type { HaEntityPickerEntityFilterFunc } from "./ha-entity-picker";
@customElement("ha-entities-picker")
class HaEntitiesPickerLight extends LitElement {
@property() public hass?: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public value?: string[];

View File

@@ -0,0 +1,178 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "./state-badge";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-item {
margin: -10px;
padding: 0;
}
</style>
<paper-item></paper-item>
`;
}
root.querySelector("paper-item")!.textContent = model.item;
};
@customElement("ha-entity-attribute-picker")
class HaEntityAttributePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId?: string;
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean, attribute: "allow-custom-value" })
public allowCustomValue;
@property() public label?: string;
@property() public value?: string;
@property({ type: Boolean }) private _opened = false;
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
protected shouldUpdate(changedProps: PropertyValues) {
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items = state
? Object.keys(state.attributes)
: [];
}
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<vaadin-combo-box-light
.value=${this._value}
.allowCustomValue=${this.allowCustomValue}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
<paper-input
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize(
"ui.components.entity.entity-attribute-picker.attribute"
)}
.value=${this._value}
.disabled=${this.disabled || !this.entityId}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.entity.entity-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</ha-icon-button>
`
: ""}
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.entity.entity-attribute-picker.show_attributes"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</ha-icon-button>
</paper-input>
</vaadin-combo-box-light>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue("");
}
private get _value() {
return this.value || "";
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
const newValue = ev.detail.value;
if (newValue !== this._value) {
this._setValue(newValue);
}
}
private _setValue(value: string) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResult {
return css`
paper-input > ha-icon-button {
--mdc-icon-button-size: 24px;
padding: 0px 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-entity-attribute-picker": HaEntityAttributePicker;
}
}

View File

@@ -1,4 +1,3 @@
import "../ha-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
@@ -7,6 +6,7 @@ import { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
@@ -20,6 +20,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "./state-badge";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@@ -51,7 +52,8 @@ const rowRenderer = (
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
};
class HaEntityPicker extends LitElement {
@customElement("ha-entity-picker")
export class HaEntityPicker extends LitElement {
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled?: boolean;
@@ -59,7 +61,7 @@ class HaEntityPicker extends LitElement {
@property({ type: Boolean, attribute: "allow-custom-entity" })
public allowCustomEntity;
@property() public hass?: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public label?: string;
@@ -95,6 +97,8 @@ class HaEntityPicker extends LitElement {
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
private _initedStates = false;
private _getStates = memoizeOne(
(
_opened: boolean,
@@ -148,11 +152,18 @@ class HaEntityPicker extends LitElement {
);
protected shouldUpdate(changedProps: PropertyValues) {
if (
changedProps.has("value") ||
changedProps.has("label") ||
changedProps.has("disabled")
) {
return true;
}
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
const states = this._getStates(
this._opened,
this.hass,
@@ -162,6 +173,7 @@ class HaEntityPicker extends LitElement {
this.includeDeviceClasses
);
(this._comboBox as any).items = states;
this._initedStates = true;
}
}
@@ -169,7 +181,6 @@ class HaEntityPicker extends LitElement {
if (!this.hass) {
return html``;
}
return html`
<vaadin-combo-box-light
item-value-path="entity_id"
@@ -267,8 +278,6 @@ class HaEntityPicker extends LitElement {
}
}
customElements.define("ha-entity-picker", HaEntityPicker);
declare global {
interface HTMLElementTagNameMap {
"ha-entity-picker": HaEntityPicker;

View File

@@ -6,6 +6,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -28,7 +29,7 @@ export class HaEntityToggle extends LitElement {
@property() public stateObj?: HassEntity;
@property() private _isOn = false;
@internalProperty() private _isOn = false;
protected render(): TemplateResult {
if (!this.stateObj) {

View File

@@ -6,6 +6,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -19,10 +20,11 @@ import { stateIcon } from "../../common/entity/state_icon";
import { timerTimeRemaining } from "../../common/entity/timer_time_remaining";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
@customElement("ha-state-label-badge")
export class HaStateLabelBadge extends LitElement {
@property() public hass?: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public state?: HassEntity;
@@ -32,7 +34,7 @@ export class HaStateLabelBadge extends LitElement {
@property() public image?: string;
@property() private _timerTimeRemaining?: number;
@internalProperty() private _timerTimeRemaining?: number;
private _connected?: boolean;
@@ -80,7 +82,8 @@ export class HaStateLabelBadge extends LitElement {
? ""
: this.image
? this.image
: state.attributes.entity_picture}"
: state.attributes.entity_picture_local ||
state.attributes.entity_picture}"
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
.description="${this.name ? this.name : computeStateName(state)}"
></ha-label-badge>
@@ -107,7 +110,7 @@ export class HaStateLabelBadge extends LitElement {
return null;
case "sensor":
default:
return state.state === "unknown"
return state.state === UNKNOWN
? "-"
: state.attributes.unit_of_measurement
? state.state
@@ -120,7 +123,7 @@ export class HaStateLabelBadge extends LitElement {
}
private _computeIcon(domain: string, state: HassEntity) {
if (state.state === "unavailable") {
if (state.state === UNAVAILABLE) {
return null;
}
switch (domain) {
@@ -155,7 +158,9 @@ export class HaStateLabelBadge extends LitElement {
? domainIcon(domain)
: "hass:brightness-3";
case "timer":
return state.state === "active" ? "hass:timer" : "hass:timer-off";
return state.state === "active"
? "hass:timer-outline"
: "hass:timer-off-outline";
default:
return null;
}
@@ -163,7 +168,7 @@ export class HaStateLabelBadge extends LitElement {
private _computeLabel(domain, state, _timerTimeRemaining) {
if (
state.state === "unavailable" ||
state.state === UNAVAILABLE ||
["device_tracker", "alarm_control_panel", "person"].includes(domain)
) {
// Localize the state with a special state_badge namespace, which has variations of

View File

@@ -6,6 +6,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -31,7 +32,7 @@ export class StateBadge extends LitElement {
@property({ type: Boolean, reflect: true, attribute: "icon" })
private _showIcon = true;
@property() private _iconStyle: { [name: string]: string } = {};
@internalProperty() private _iconStyle: { [name: string]: string } = {};
protected render(): TemplateResult {
const stateObj = this.stateObj;
@@ -72,10 +73,10 @@ export class StateBadge extends LitElement {
if (stateObj) {
// hide icon if we have entity picture
if (
(stateObj.attributes.entity_picture && !this.overrideIcon) ||
((stateObj.attributes.entity_picture_local || stateObj.attributes.entity_picture) && !this.overrideIcon) ||
this.overrideImage
) {
let imageUrl = this.overrideImage || stateObj.attributes.entity_picture;
let imageUrl = this.overrideImage || stateObj.attributes.entity_picture_local || stateObj.attributes.entity_picture;
if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl);
}

View File

@@ -12,6 +12,7 @@ import {
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
@@ -61,7 +62,7 @@ const rowRenderer = (
@customElement("ha-area-picker")
export class HaAreaPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@@ -72,7 +73,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean, attribute: "no-add" })
public noAdd?: boolean;
@property() private _opened?: boolean;
@internalProperty() private _opened?: boolean;
public hassSubscribe(): UnsubscribeFunc[] {
return [

View File

@@ -17,7 +17,7 @@ let jsYamlPromise: Promise<typeof import("js-yaml")>;
class HaAttributes extends LitElement {
@property() public stateObj?: HassEntity;
@property() public extraFilters?: string;
@property({ attribute: "extra-filters" }) public extraFilters?: string;
protected render(): TemplateResult {
if (!this.stateObj) {

67
src/components/ha-bar.ts Normal file
View File

@@ -0,0 +1,67 @@
import {
css,
CSSResult,
customElement,
LitElement,
property,
svg,
TemplateResult,
} from "lit-element";
import {
getValueInPercentage,
normalize,
roundWithOneDecimal,
} from "../util/calculate";
@customElement("ha-bar")
export class HaBar extends LitElement {
@property({ type: Number }) public min = 0;
@property({ type: Number }) public max = 100;
@property({ type: Number }) public value!: number;
protected render(): TemplateResult {
const valuePrecentage = roundWithOneDecimal(
getValueInPercentage(
normalize(this.value, this.min, this.max),
this.min,
this.max
)
);
return svg`
<svg>
<g>
<rect></rect>
<rect width="${valuePrecentage}%"></rect>
</g>
</svg>
`;
}
static get styles(): CSSResult {
return css`
rect:first-child {
width: 100%;
fill: var(--ha-bar-background-color, var(--secondary-background-color));
}
rect:last-child {
fill: var(--ha-bar-primary-color, var(--primary-color));
rx: var(--ha-bar-border-radius, 4px);
}
svg {
border-radius: var(--ha-bar-border-radius, 4px);
height: 12px;
width: 100%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-bar": HaBar;
}
}

View File

@@ -10,7 +10,6 @@ import {
} from "lit-element";
import "@material/mwc-button";
import "@material/mwc-menu";
import "@material/mwc-list/mwc-list-item";
import type { Menu, Corner } from "@material/mwc-menu";
import "./ha-icon-button";
@@ -19,14 +18,30 @@ import "./ha-icon-button";
export class HaButtonMenu extends LitElement {
@property() public corner: Corner = "TOP_START";
@property({ type: Boolean }) public multi = false;
@property({ type: Boolean }) public activatable = false;
@query("mwc-menu") private _menu?: Menu;
public get items() {
return this._menu?.items;
}
public get selected() {
return this._menu?.selected;
}
protected render(): TemplateResult {
return html`
<div @click=${this._handleClick}>
<slot name="trigger"></slot>
</div>
<mwc-menu .corner=${this.corner}>
<mwc-menu
.corner=${this.corner}
.multi=${this.multi}
.activatable=${this.activatable}
>
<slot></slot>
</mwc-menu>
`;

View File

@@ -1,21 +1,20 @@
import "@material/mwc-icon-button/mwc-icon-button";
import {
css,
CSSResult,
customElement,
html,
TemplateResult,
property,
LitElement,
CSSResult,
css,
property,
TemplateResult,
} from "lit-element";
import "./ha-icon-button";
import { fireEvent } from "../common/dom/fire_event";
import type { ToggleButton } from "../types";
import "./ha-svg-icon";
@customElement("ha-button-toggle-group")
export class HaButtonToggleGroup extends LitElement {
@property() public buttons!: ToggleButton[];
@property({ attribute: false }) public buttons!: ToggleButton[];
@property() public active?: string;
@@ -23,21 +22,23 @@ export class HaButtonToggleGroup extends LitElement {
return html`
<div>
${this.buttons.map(
(button) => html` <ha-icon-button
.label=${button.label}
.icon=${button.icon}
.value=${button.value}
?active=${this.active === button.value}
@click=${this._handleClick}
>
</ha-icon-button>`
(button) => html`
<mwc-icon-button
.label=${button.label}
.value=${button.value}
?active=${this.active === button.value}
@click=${this._handleClick}
>
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
</mwc-icon-button>
`
)}
</div>
`;
}
private _handleClick(ev): void {
this.active = ev.target.value;
this.active = ev.currentTarget.value;
fireEvent(this, "value-changed", { value: this.active });
}
@@ -48,12 +49,13 @@ export class HaButtonToggleGroup extends LitElement {
--mdc-icon-button-size: var(--button-toggle-size, 36px);
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
}
ha-icon-button {
mwc-icon-button {
border: 1px solid var(--primary-color);
border-right-width: 0px;
position: relative;
cursor: pointer;
}
ha-icon-button::before {
mwc-icon-button::before {
top: 0;
left: 0;
width: 100%;
@@ -65,22 +67,26 @@ export class HaButtonToggleGroup extends LitElement {
content: "";
transition: opacity 15ms linear, background-color 15ms linear;
}
ha-icon-button[active]::before {
mwc-icon-button[active]::before {
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
}
ha-icon-button:first-child {
mwc-icon-button:first-child {
border-radius: 4px 0 0 4px;
}
ha-icon-button:last-child {
mwc-icon-button:last-child {
border-radius: 0 4px 4px 0;
border-right-width: 1px;
}
mwc-icon-button:only-child {
border-radius: 4px;
border-right-width: 1px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-button-toggle-button": HaButtonToggleGroup;
"ha-button-toggle-group": HaButtonToggleGroup;
}
}

View File

@@ -3,6 +3,7 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
@@ -17,37 +18,28 @@ import {
fetchStreamUrl,
} from "../data/camera";
import { CameraEntity, HomeAssistant } from "../types";
type HLSModule = typeof import("hls.js");
import "./ha-hls-player";
@customElement("ha-camera-stream")
class HaCameraStream extends LitElement {
@property() public hass?: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public stateObj?: CameraEntity;
@property({ attribute: false }) public stateObj?: CameraEntity;
@property({ type: Boolean }) public showControls = false;
@property({ type: Boolean, attribute: "controls" })
public controls = false;
@property() private _attached = false;
@property({ type: Boolean, attribute: "muted" })
public muted = false;
// We keep track if we should force MJPEG with a string
// that way it automatically resets if we change entity.
@property() private _forceMJPEG: string | undefined = undefined;
@internalProperty() private _forceMJPEG?: string;
private _hlsPolyfillInstance?: Hls;
public connectedCallback() {
super.connectedCallback();
this._attached = true;
}
public disconnectedCallback() {
super.disconnectedCallback();
this._attached = false;
}
@internalProperty() private _url?: string;
protected render(): TemplateResult {
if (!this.stateObj || !this._attached) {
if (!this.stateObj) {
return html``;
}
@@ -64,51 +56,25 @@ class HaCameraStream extends LitElement {
)} camera.`}
/>
`
: html`
<video
: this._url
? html`
<ha-hls-player
autoplay
muted
playsinline
?controls=${this.showControls}
@loadeddata=${this._elementResized}
></video>
`}
.muted=${this.muted}
.controls=${this.controls}
.hass=${this.hass}
.url=${this._url}
></ha-hls-player>
`
: ""}
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
const stateObjChanged = changedProps.has("stateObj");
const attachedChanged = changedProps.has("_attached");
const oldState = changedProps.get("stateObj") as this["stateObj"];
const oldEntityId = oldState ? oldState.entity_id : undefined;
const curEntityId = this.stateObj ? this.stateObj.entity_id : undefined;
if (
(!stateObjChanged && !attachedChanged) ||
(stateObjChanged && oldEntityId === curEntityId)
) {
return;
}
// If we are no longer attached, destroy polyfill.
if (attachedChanged && !this._attached) {
this._destroyPolyfill();
return;
}
// Nothing to do if we are render MJPEG.
if (this._shouldRenderMJPEG) {
return;
}
// Tear down existing polyfill, if available
this._destroyPolyfill();
if (curEntityId) {
this._startHls();
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("stateObj") && !this._shouldRenderMJPEG) {
this._forceMJPEG = undefined;
this._getStreamUrl();
}
}
@@ -120,96 +86,35 @@ class HaCameraStream extends LitElement {
);
}
private get _videoEl(): HTMLVideoElement {
return this.shadowRoot!.querySelector("video")!;
}
private async _startHls(): Promise<void> {
// eslint-disable-next-line
const Hls = ((await import(
/* webpackChunkName: "hls.js" */ "hls.js"
)) as any).default as HLSModule;
let hlsSupported = Hls.isSupported();
const videoEl = this._videoEl;
if (!hlsSupported) {
hlsSupported =
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
}
if (!hlsSupported) {
this._forceMJPEG = this.stateObj!.entity_id;
return;
}
private async _getStreamUrl(): Promise<void> {
try {
const { url } = await fetchStreamUrl(
this.hass!,
this.stateObj!.entity_id
);
if (Hls.isSupported()) {
this._renderHLSPolyfill(videoEl, Hls, url);
} else {
this._renderHLSNative(videoEl, url);
}
return;
this._url = url;
} catch (err) {
// Fails if we were unable to get a stream
// eslint-disable-next-line
console.error(err);
this._forceMJPEG = this.stateObj!.entity_id;
}
}
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
videoEl.src = url;
await new Promise((resolve) =>
videoEl.addEventListener("loadedmetadata", resolve)
);
videoEl.play();
}
private async _renderHLSPolyfill(
videoEl: HTMLVideoElement,
// eslint-disable-next-line
Hls: HLSModule,
url: string
) {
const hls = new Hls({
liveBackBufferLength: 60,
fragLoadingTimeOut: 30000,
manifestLoadingTimeOut: 30000,
levelLoadingTimeOut: 30000,
});
this._hlsPolyfillInstance = hls;
hls.attachMedia(videoEl);
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
hls.loadSource(url);
});
}
private _elementResized() {
fireEvent(this, "iron-resize");
}
private _destroyPolyfill(): void {
if (this._hlsPolyfillInstance) {
this._hlsPolyfillInstance.destroy();
this._hlsPolyfillInstance = undefined;
}
}
static get styles(): CSSResult {
return css`
:host,
img,
video {
img {
display: block;
}
img,
video {
img {
width: 100%;
}
`;

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