Compare commits

...

228 Commits

Author SHA1 Message Date
Paulus Schoutsen
1feb9f6a27 Bump version to 20181024.0 2018-10-24 22:14:06 +02:00
Paulus Schoutsen
337a760e73 Update translations 2018-10-24 22:13:49 +02:00
Thomas Lovén
54cd412107 Fix for some dev pages crashing on iOS. (#1850)
This should probably be reverted when the fixes in
https://bugs.webkit.org/show_bug.cgi?id=174629 has been rolled out.
2018-10-24 22:04:27 +02:00
Ian Richardson
cf2171ece1 Convert hui-vertical-stack-card to TypeScript/LitElement (#1846)
Failed to rebase previous branch and am taking my working changes and applying to a new branch based off of current master.

Updated tslint.json to allow for prefixed `_` to variable names
2018-10-24 12:11:09 +02:00
Paulus Schoutsen
47fb8a5513 Fix mixin names (#1849) 2018-10-24 12:10:58 +02:00
Ian Richardson
06bf134bd4 Remove extending of HassLocalizeMixin as it is not needed (#1847) 2018-10-24 11:42:09 +02:00
Timmo
cf8899fcbe ↕️ fix sorting of addons (#1845) 2018-10-24 09:58:44 +02:00
Charles Garwood
c05b77961e Reduces device registry card height to only show 4 entities before scrolling (#1844) 2018-10-23 17:40:31 +02:00
Paulus Schoutsen
19c365cd12 Update version to 20181023.0 2018-10-23 14:01:51 +02:00
Paulus Schoutsen
29f032087e Update translations 2018-10-23 14:01:22 +02:00
Paulus Schoutsen
d0cb7b9724 TS history data (#1839)
* Convert history data to TS

* Lint

* Extract cached history

* Move around
2018-10-23 13:54:52 +02:00
Thomas Lovén
ad162677a6 Introduce typescript to hui-conditional-card (#1831) 2018-10-23 12:11:06 +02:00
Thomas Lovén
fbbbe7d17d Add HomeAssistant object hass to LovelaceCard interface (#1843) 2018-10-23 10:53:42 +02:00
Ian Richardson
ef0d11c042 connection is being populated for HomeAssistant type, not conn (#1841) 2018-10-23 09:33:04 +02:00
Ian Richardson
fc2608980f Generate icons from `.ts. files (#1842) 2018-10-23 09:32:27 +02:00
Tom Raithel
cc97e82a78 Improve size of device cards and add max-width for entities (#1838)
fixes #1751
2018-10-23 00:12:39 +02:00
Paulus Schoutsen
54e3191de6 Reinstate first call true 2018-10-22 21:40:35 +02:00
Paulus Schoutsen
4f8c8762c7 Fix second visit to history panel (#1835) 2018-10-22 21:37:21 +02:00
Zack Arnett
c190f1986e Merge pull request #1834 from home-assistant/fix-chart-tooltip
Fix chart tooltip
2018-10-22 15:13:25 -04:00
Paulus Schoutsen
b418048bc9 Fix chart tooltip 2018-10-22 21:07:31 +02:00
Paulus Schoutsen
0fdd1c74f2 Always define localize func (#1830) 2018-10-22 20:31:43 +02:00
Thomas Lovén
3b1b2b95e7 Add option to pick number of columns for glance card (#1832) 2018-10-22 20:10:48 +02:00
Paulus Schoutsen
3bb5484b7f Bump version to 20181021.0 2018-10-21 20:32:05 +02:00
Paulus Schoutsen
d93c09b27b Fix reference to this.language (#1825) 2018-10-21 20:31:41 +02:00
Paulus Schoutsen
e7ec18d270 Fix lint 2018-10-21 20:31:24 +02:00
Paulus Schoutsen
3ebe21e135 Update translations 2018-10-21 20:16:09 +02:00
Ian Richardson
aca1ecf1ee Convert hui-markdown-card to TypeScript/LitElement (#1808)
* Convert hui-markdown-card to TypeScript/LitElement

* Addressed review comments

* Addressed review comments

* Addressed review comments
2018-10-21 20:09:11 +02:00
Charles Garwood
e8ef2fdc2c this.language -> this.hass.language (#1816) 2018-10-21 15:12:28 +02:00
Paulus Schoutsen
b129d5fb08 Refresh cloud status if paying sub info comes in (#1822) 2018-10-21 12:28:16 +02:00
Zack Arnett
11f4564465 Merge pull request #1807 from home-assistant/safer-icon-brightness
Safer brightness calculation for icons
2018-10-19 20:58:46 -04:00
Thomas Lovén
c9d140281b fix typo in variable name 2018-10-19 16:35:52 +02:00
PhracturedBlue
fa637a37d5 Fix mailbox issues with this.fire() and this.language undefined (#1809)
* Fix bug with this.fire and this.language

* Proper language variable
2018-10-19 16:15:31 +02:00
Thomas Lovén
1589c3fc51 Better error message 2018-10-19 15:29:05 +02:00
Thomas Lovén
bdc2b31202 Complain and ignore instead of fixing 2018-10-19 12:07:33 +02:00
Thomas Lovén
0970e1e33c Fix overflow of image elements. (#1811)
* Fix overflow of image elements.

* Positioning safeguards
2018-10-19 10:41:08 +02:00
Timmo
028003dffc Set attributes in input form controls (#1805)
* 🔨 add autocapitalize, autocomplete, autocorrect and spellcheck attributes

* 🔨 switch autocapitalize to none

* 🔨 add attributes to dev-state

* 🔨 add attributes to entity-picker
2018-10-19 09:13:45 +02:00
Thomas Lovén
1eb4ac7f34 Safer brightness calculation for icons 2018-10-19 00:01:23 +02:00
Zack Arnett
d97e356376 Merge pull request #1794 from zsarnett/glance-unavailable
Glance Card update to show when entity is unavailable
2018-10-18 11:21:08 -04:00
Zack Arnett
d36352af16 Removing Repeat 2018-10-18 10:26:42 -04:00
Zack Arnett
05ae92d5f8 Merge pull request #1786 from zsarnett/glance-column-change
Changing Glance Column Width default to fill card
2018-10-18 09:52:16 -04:00
Paulus Schoutsen
dce612f944 Trim whitespace 2018-10-18 15:40:37 +02:00
Ian Richardson
3a196203c3 Convert hui-iframe-card to LitElement/TypeScript (#1801)
* Convert hui-iframe-card to LitElement/TypeScript

* style cleanup

* Address review comments

* Addressed review comments
2018-10-18 15:37:01 +02:00
Zack Arnett
33578a6289 Updating from reviews 2018-10-18 09:28:49 -04:00
Zack Arnett
4c3db2119b Update to show when entity is unavailable 2018-10-18 09:14:32 -04:00
Zack Arnett
4a7ff3cd94 Removing Column width variable in interface 2018-10-18 09:06:29 -04:00
Paulus Schoutsen
5578580d78 Verison bump to 20181018.0 2018-10-18 13:39:45 +02:00
Paulus Schoutsen
dc1d8366a5 Fix automation editor (#1804) 2018-10-18 13:39:18 +02:00
Paulus Schoutsen
252f0692c8 Add hass on badge (#1802)
* Add hass on badge

* Add more hass everywhere
2018-10-18 13:21:06 +02:00
Thomas Lovén
9d13925280 Show currently selected language in profile settings. Resolves #1797 (#1800) 2018-10-18 09:55:28 +02:00
Thomas Lovén
5462a71f52 Use this.hass.language since this.language is removed from localize mixin. (#1799) 2018-10-18 09:54:01 +02:00
Josh McCarty
42953a0b62 Use title case for all configuration pages (#1793)
Localize translations will need to be updated separately.
2018-10-18 09:25:35 +02:00
Thomas Lovén
f146a1d80f Lovelace: Allow press-and-hold on picture-elements elements. (#1745)
* Allow press-and-hold on picture-elements elements.
2018-10-17 22:16:17 +02:00
Zack Arnett
a113c71de7 Merge branch 'master' into glance-column-change 2018-10-17 15:06:29 -04:00
Zack Arnett
1f642f436a Adding Theme option to Glance and Button Cards (#1783)
* Adding Theme option to Glance and Button Cards

* Updateing Theme default to `default`

* Prettier Update
2018-10-17 20:20:05 +02:00
Zack Arnett
62d27a17d5 Takes out column width variable 2018-10-17 13:06:57 -04:00
Zack Arnett
35941a58a5 Prettier Fixes 2018-10-17 11:11:29 -04:00
Paulus Schoutsen
2ace2165e0 version bump to 20181017.0 2018-10-17 14:06:59 +02:00
Paulus Schoutsen
1cfcacfa9a Update translations 2018-10-17 14:06:20 +02:00
Paulus Schoutsen
e020fd1154 👋 decorators (#1790) 2018-10-17 13:58:24 +02:00
Paulus Schoutsen
1dcc645fec Guard cloud info being null (#1788) 2018-10-17 13:48:31 +02:00
Thomas Lovén
87fba75860 Fix margins in vertical-stack (#1789) 2018-10-17 13:48:04 +02:00
Paulus Schoutsen
294360d35a Fix babel config 2018-10-17 09:20:41 +02:00
Paulus Schoutsen
a7684d7206 Add some decorators (#1784)
* Add some decorators

* Disable sort keys

* Add babel plugins

* Update typescript to 7.1
2018-10-16 23:30:13 +02:00
Paulus Schoutsen
af81ede100 Fix showing sub info (#1785) 2018-10-16 23:29:40 +02:00
Zack Arnett
698beedaa2 Changing Clance Column width default to fill card 2018-10-16 16:20:28 -04:00
Paulus Schoutsen
a6b4cce7f3 Upgrade MDI icons (#1781) 2018-10-16 20:02:09 +02:00
ehendrix23
ba66ff840f Added domain icon for homekit (#1782)
Added domain icon home-automation for domain homekit. With PR home-assistant/pull/17180 the homekit component will create entries within logbook. This PR is to set the icon displayed then for those logbook entries to be the home-automation icon.
2018-10-16 19:06:19 +02:00
Zack Arnett
c296f33ba1 Update Button Card to TS + Lit (#1778)
* Updating from Reviews - Reset commits and force pushing

* Removing Redundant check on config

* Checking Entity before setting config
2018-10-16 19:04:29 +02:00
Paulus Schoutsen
e7a49192bd Type check as part of lint (#1780)
* Type check as part of lint

* Lint

* Validate service exist for call-service action

* Fix for of
2018-10-16 17:21:05 +02:00
Thomas Lovén
5774d913af Lovelace: Add a label entity row (#1779)
* Add a label entity row

* Style fixes

* Allow blank label text

* Rename to section
2018-10-16 16:50:40 +02:00
Charles Garwood
b068db3f7a Hide Z-Wave "Remove/Replace Failed Node" Buttons unless nodes are failed (#1777)
* Hide failed node buttons if node isn't failed

* Cleanup
2018-10-16 09:05:15 +02:00
Paulus Schoutsen
8e49241e7c Add types to hass object (#1776) 2018-10-16 09:04:10 +02:00
Tom Raithel
b8cee5cc9c Fix Icon spacing in Logbook list (#1774) 2018-10-15 21:02:32 +02:00
Zack Arnett
794808d3a7 Button Card - Lovelace Addition (#1766)
* Initial Commit - Button Card

* Fixing Coloring Review

* Resolving Reviews

* Updating last Reviews
2018-10-15 20:07:13 +02:00
Zack Arnett
48f6d1dfec Adding Alarm Panel to Lovelace (#1758)
* Adding Alarm Panel

* Updating error in Lint

* Review Changes

* Using label-badge for upper right icon

* Resolving Reviews

* Prettier Fixes

* Updating style to fix overlapping state badge

* Adding Alarm Card back to create element

* Resolving reviews and reposition of Icon

* Updating to Localize Labels
2018-10-15 19:14:43 +02:00
Paulus Schoutsen
97e1aae9c0 Introduce TypeScript (#1773) 2018-10-15 19:07:08 +02:00
Paulus Schoutsen
e2511c5ed3 Remove default export fire event (#1772)
* Remove default export fire event

* Update provide_hass.js
2018-10-15 06:17:33 +02:00
Paulus Schoutsen
74bdfc8c2d Use style.setProperty instead of updateStyles 2018-10-14 22:53:33 +02:00
Paulus Schoutsen
fbccf23d36 Clean up localize mixin (#1771) 2018-10-14 22:40:43 +02:00
Paulus Schoutsen
906aaa15a3 20181014.0 2018-10-14 19:05:46 +02:00
Paulus Schoutsen
0ae1f9c754 Update Translations 2018-10-14 19:05:45 +02:00
Adam Mills
f1bd89fd02 Fix checking for syntax if for doesn't exist (#1769) 2018-10-14 19:04:51 +02:00
Paulus Schoutsen
3949b47e51 Introduce object rest spread (#1763) 2018-10-14 19:03:25 +02:00
Bram Kragten
2f6595bca7 add min-width to childeren of horizontal stack (#1761)
See https://css-tricks.com/flexbox-truncated-text/
2018-10-13 16:02:47 +02:00
Paulus Schoutsen
3bcd0ddc46 Migrate Babel 6 -> 7 (#1762)
* Migrate Babel 6 -> 7

* Update babel-eslint
2018-10-13 11:25:03 +02:00
Paulus Schoutsen
ca93c2cfcd Convert glance card to lit (#1760)
* Convert glance card to lit

* Guard for hass before config

* Lint

* better click listening

* Move config check

* Format HTML
2018-10-12 22:18:38 -07:00
Paulus Schoutsen
a633e3c553 version bump to 20181012.0 2018-10-12 14:49:33 +02:00
Paulus Schoutsen
bef2731207 Update translations 2018-10-12 14:49:15 +02:00
Paulus Schoutsen
ee53ee4077 Missing localize (#1757) 2018-10-12 12:50:58 +02:00
Paulus Schoutsen
34bfc12647 Prettier 💎 (#1737)
* Add prettier

* Apply Prettier
2018-10-11 12:22:11 +02:00
Nikolay Vasilchuk
3b425c3e14 Logbook: filter by entity and period (#1728)
* Filter logbook by entity_id

* Filter logbook by period

* Filter logbook styles

* CI Fix

* Review

* Review

* CI Fix
2018-10-11 11:46:16 +02:00
Zack Arnett
69eb007ea2 Adding Gauge Card to Lovelace (#1742)
* Commiting Only needed Files. Adds Gallery Entry

* Adding Attribute current_temperature to gallery entry config

* Fixing code from review and updating gallery

* Updating Gallery to show errors

* Resolving Reviews and updating gallery

* Deleting unused line

* Minor changes

* Address my own comments.
2018-10-11 10:30:56 +02:00
Jerad Meisner
90c3350d40 Fix error when only one state history entry. (#1750) 2018-10-09 11:12:04 +02:00
John Arild Berentsen
a7ddbd72b3 Fix non-working zwave log andriod PWA (#1714)
* Fix non-working andriod PWA

* Forgot clearing dialog setInterval

* Correctly identify pwa or browser interval clearing

* Move isPwa to common

* Stab at making imorted dialog

* Redone refresh

* Remove unused property
2018-10-09 11:10:32 +02:00
Paulus Schoutsen
5a2ee98ae2 Version bump to 20181007.0 2018-10-07 23:15:44 +02:00
Karl Kihlström
ea0b5d5e26 Add sensor-graph-card (#1744)
* Added sensor-graph-card

* Removed Object as type

* Removed unused attributes

* Fixed card config

* Changed svg rendering to lit html svg

* Fixed config conversion

* Changed to _config, _entity, _line as private

* Removed lit-element package

* Renamed to hui-sensor-card

* lit-html 0.6.2 changes

* Added logic for graph config option
2018-10-07 23:13:10 +02:00
Paulus Schoutsen
af2cb1be1a Update Lit to 0.6.2 (#1748) 2018-10-07 21:32:50 +02:00
Jerad Meisner
c30e7ac683 Add time created to persistent notifications. (#1733)
* Add time created to persistent notifications.

* Add tooltip to show actual date.

* Fix style rules.

* Fix duplicate ids.
2018-10-07 18:59:54 +02:00
Paulus Schoutsen
7fb5ac11fd Update translations 2018-10-07 18:52:30 +02:00
Paulus Schoutsen
b2dc0ac819 Proper fix for mjs to hassio too 2018-10-07 18:52:01 +02:00
Paulus Schoutsen
dbdf873ba4 transpile mjs (#1746) 2018-10-07 14:11:33 +02:00
Paulus Schoutsen
1b70b6e88c Introduce Lit Element (#1738) 2018-10-07 11:07:02 +02:00
Paulus Schoutsen
c90e13d35e Inline domain icon (#1739) 2018-10-05 21:29:50 +02:00
Paul Davis
442375f76e fix dockerfile for new setups (#1740) 2018-10-05 21:23:31 +02:00
Paulus Schoutsen
81d493e1d6 Version bump to 20181005.0 2018-10-05 17:46:49 +02:00
Paulus Schoutsen
151f16af47 Update translations 2018-10-05 17:46:34 +02:00
Adam Mills
606a220603 [WIP] Handle dict syntax in state trigger "for" (#1725)
* Handle dict syntax in state trigger "for"

* padStart polyfill
2018-10-05 11:33:27 +02:00
Thomas Lovén
362e758c40 Lovelace: Allow glance card to assume theme colors (#1732)
* Allow glance card to assume theme colors

* Better configuration options

* Added example to gallery

* Fixing problems from review
2018-10-05 10:26:31 +02:00
Paulus Schoutsen
2eb3a55f59 Remove last used from long lived access token list. (#1727) 2018-10-05 10:23:29 +02:00
Paulus Schoutsen
6720c03cbc Fix language reference (#1735) 2018-10-04 13:24:07 +02:00
Jerad Meisner
a76386b53b Fix console errors in LL when entities are unavailable. (#1734) 2018-10-04 09:44:01 +02:00
Thomas Lovén
0243632357 Remove margin from conditional if not shown (#1730) 2018-10-04 09:36:14 +02:00
Paulus Schoutsen
bb24b55a67 Update translations 2018-10-03 15:28:12 +02:00
Paulus Schoutsen
f47fd8eec4 Bump version to 20181002.0 2018-10-02 14:17:58 +02:00
Paulus Schoutsen
f1f9f13d82 Update translations 2018-10-02 14:17:44 +02:00
Paulus Schoutsen
d2dd82c0ec Use new LL command (#1702) 2018-10-02 14:16:52 +02:00
Paulus Schoutsen
e1738b625d Fix link color for Hass.io update panel (#1721) 2018-10-02 14:16:35 +02:00
William Scanlon
7aa37183b6 Convert climate water heaters to new water_heaters component (#1661)
* Water heater support

* Attempt to fix lint errors.

* Fixed another lint issue
2018-10-02 14:16:19 +02:00
Jason Hu
c91b28a850 L10N config-entries (#1718)
* L10N config-entries

* Lint

* Address review comment

* Add back parentheses
2018-10-02 13:27:18 +02:00
Adam Mills
70225c1a18 Expose state trigger For configuration in editor (#1723) 2018-10-02 13:26:56 +02:00
Jason Hu
3d9d7d899d Fix zero degree in weather card (#1720)
* Fix zero degree display in weather card

* Fix zero degree display in weather more-info dialog
2018-10-01 13:30:48 +02:00
Otto Winter
f0619c7d13 Add pressure sensor device class (#1713) 2018-10-01 12:32:03 +02:00
Jason Hu
305fa84d38 Apply user language preference on datetime formatting (#1719) 2018-10-01 12:08:21 +02:00
Jason Hu
edf0e2bedb Add l10n suppor for profile panel (#1717)
* L10N support for profile panel and mfa module

* L10N support for mfa setup flow

* Lint

* Lint

* Lint
2018-10-01 12:02:41 +02:00
Anders Melchiorsen
8be5561d19 Remove turn_off from brightness slider (#1715) 2018-10-01 10:56:08 +02:00
Paulus Schoutsen
f11ca53282 Version bump to 20180927.0 2018-09-27 23:03:17 +02:00
Paulus Schoutsen
2c25d6cc0a Update translations 2018-09-27 23:03:02 +02:00
Paulus Schoutsen
db6ab4d8ec Update Z-Wave icon (#1711) 2018-09-27 23:02:22 +02:00
Jerad Meisner
3961eff372 Extend paper-slider to fix rounding issue. (#1709) 2018-09-27 10:06:56 +02:00
Charles Garwood
458a7827f9 Fix for content appearing behind header in hassio and config panels (#1708)
* Call iron-resize on route change

* Add EventsMixin to hassio-main
2018-09-26 20:37:03 +02:00
Paulus Schoutsen
68d1c77a79 Bump version to 20180926.0 2018-09-26 11:10:46 +02:00
Paulus Schoutsen
aa97e30d51 Add card for entities without devices (#1706)
* Add card for entities without devices

* Better empty check
2018-09-26 11:09:33 +02:00
Paulus Schoutsen
9027d7d391 Update translations 2018-09-26 10:57:56 +02:00
Paulus Schoutsen
974fd5de0f Allow description when creating entry (#1704)
* Allow description when creating entry

* Lint
2018-09-25 16:32:45 +02:00
Paulus Schoutsen
f9d28fbf83 Add alert icon" (#1703) 2018-09-25 14:42:23 +02:00
Paulus Schoutsen
b944089087 Version bump 20180924.0 2018-09-24 11:46:26 +02:00
Paulus Schoutsen
7b6cf28459 Update translations 2018-09-24 11:46:10 +02:00
Paulus Schoutsen
be91688efb Add link to release notes (#1694) 2018-09-24 11:05:23 +02:00
Paulus Schoutsen
a5d47231aa Fix text color for system panel (#1695) 2018-09-24 11:05:16 +02:00
Paulus Schoutsen
01e833a399 Fix ha-paper-slider (#1700) 2018-09-24 11:05:03 +02:00
Charles Garwood
c363ba8056 Fix more-info graph when expanded (#1696) 2018-09-24 10:01:40 +02:00
schumpeter2
7cec39ba6c Fix more-info dialog call for groups of entities having a common domain (#1698) 2018-09-24 09:58:25 +02:00
PhracturedBlue
3f15cbd2bd Add support for multiple separate mailboxes (#1660) 2018-09-21 11:56:30 +02:00
Paulus Schoutsen
3235d33463 Add firmware 2018-09-21 10:41:25 +02:00
Paulus Schoutsen
140597c7f8 Minor CSS Fixes 2018-09-21 10:00:03 +02:00
Paulus Schoutsen
e1407a7d73 Use human readable description if possible (#1688)
* Use human readable description if possible

* lint
2018-09-21 09:20:07 +02:00
Paulus Schoutsen
03525c010f Allow toggling cloud integrations (#1690) 2018-09-21 09:02:24 +02:00
Paulus Schoutsen
8dc202af92 Version bump to 20180920.0 2018-09-20 10:53:41 +02:00
Paulus Schoutsen
369977f8f3 Update translations 2018-09-20 10:52:56 +02:00
randellhodges
7f8c092dfc Normalize more-info bottom padding (#1682) 2018-09-20 10:20:12 +02:00
randellhodges
d517cad6e6 fixed weather-lightning icon (#1684) 2018-09-20 10:19:04 +02:00
Paulus Schoutsen
62a68890d3 Show sub info (#1685)
* Show sub info

* Fix observer
2018-09-20 10:18:39 +02:00
Paulus Schoutsen
3d8a8cc77b Fix minifier (#1683) 2018-09-20 00:08:25 +02:00
Paulus Schoutsen
55dc35a8fc Update version to 20180919.0 2018-09-19 15:16:35 +02:00
Paulus Schoutsen
82e49a5e44 Update translations 2018-09-19 15:16:14 +02:00
Paulus Schoutsen
17ac6f96a0 Update deps (#1678)
* Update deps

* Lint

* Fix lint
2018-09-19 15:15:16 +02:00
Pascal Vizeli
085db3e0a6 Fix URL for nabucasa page (#1677) 2018-09-19 14:59:59 +02:00
Paulus Schoutsen
348bebc417 Update material design sidebar (#1676) 2018-09-19 14:59:48 +02:00
Paulus Schoutsen
15d21cc673 Fix miniy fail (#1674) 2018-09-19 11:32:24 +02:00
Paulus Schoutsen
7e0ff14f28 Merge overview into integrations (#1672)
* Merge overview into integrations

* Lint
2018-09-19 11:11:00 +02:00
Charles Garwood
67d09e8b3d Add loaded components popup to dev-info (#1666)
* Add loaded components popup to dev-info

* Change dialog handling
2018-09-18 14:54:37 +02:00
randellhodges
ce3b53a920 Image aspect ratio (#1665)
* Allow user to specify an aspect ratio for various images

* added a comment on what is supported

* fixed typo

* Fixed lint and test errors
2018-09-17 21:16:00 +02:00
Paulus Schoutsen
a32809e14b Update padding checkbox 2018-09-17 17:23:56 +02:00
Paulus Schoutsen
1d8c515da2 Bump to 20180917.0 2018-09-17 14:17:08 +02:00
Paulus Schoutsen
81e0f1a025 Update translations 2018-09-17 14:16:47 +02:00
Paulus Schoutsen
c593e2789c Add basic overview page (#1668)
* Add basic overview page

* Add empty state

* Show hub devices

* Add more info to config entries page

* Lint
2018-09-17 14:11:07 +02:00
cdce8p
650d2d7a47 Lovelace Custom ui fallback (#1670)
* Custom ui elements fallback to default instead of error

* Lint

* Changed fallback to timeout
2018-09-17 14:06:17 +02:00
Paulus Schoutsen
2665c86683 Show entities under configured integrations (#1663) 2018-09-17 10:00:21 +02:00
Jerad Meisner
8b262f3424 Added entity row for media players. (#1495)
* Added entity row for media players.

* Use artist:track/series:episode for music/tvshow.

* Add controls

* Comments

* Fixes

* Fixes for off states. Added gallery demo.

* Resolve conflicts. Change to use template extension points.

* Fixes
2018-09-17 09:58:43 +02:00
Jerad Meisner
5187f3b84f Get persistent_notifications for lovelace from websocket. (#1649)
* Get persistent_notifications for lovelace from websocket.

* Only fetch notifications on event.

* Use collection for notifications.
2018-09-17 09:53:14 +02:00
Charles Garwood
443e083a79 Add Z-Wave Entity Information/more-info button (#1664)
* Add Entity Info button

* Cleanup
2018-09-17 09:43:13 +02:00
Paulus Schoutsen
6c262c20ce Handle defaults, required and optional fields (#1662)
* Handle defaults, required and optional fields

* Lint
2018-09-17 09:42:43 +02:00
Paulus Schoutsen
cfbf2903c1 Version bump to 20180916.0 2018-09-16 21:20:49 +02:00
Paulus Schoutsen
19b8ff7d9f Update translations 2018-09-16 21:20:24 +02:00
Paulus Schoutsen
ec6ffd2115 Update text 2018-09-16 21:19:58 +02:00
Paulus Schoutsen
433b1e2979 Fix translation 2018-09-12 15:30:33 +02:00
Paulus Schoutsen
bd3d079dfb Version bump to 20180912.0 2018-09-12 13:13:49 +02:00
Paulus Schoutsen
fe776191b7 Update translations 2018-09-12 13:13:30 +02:00
Paulus Schoutsen
c546d8787d Add last used to token on profile page (#1659) 2018-09-12 13:12:26 +02:00
Jason Hu
a672b84b88 Disable delete icon if token is current used one (#1658) 2018-09-12 09:50:12 +02:00
Paulus Schoutsen
e3a137c675 Version bump to 20180911.0 2018-09-11 21:35:28 +02:00
Paulus Schoutsen
10aa99abdc Update translations 2018-09-11 21:35:08 +02:00
Paulus Schoutsen
34567d451f Add UI for tokens (#1656)
* Add UI for tokens

* Update strings

* Update text

* Update text
2018-09-11 21:29:40 +02:00
PhracturedBlue
494e3dc62c Fix authorization and display issues in mailbox view (#1610)
* Mailbox: Fix authorization issues.  Remove backdrop

* Fix linting issues

* Use HA Dialog system.  Fix authorization

* Add back missing backdrop

* Linting errors

* Use callApi.  Add error checking and spinner

* linting error

* more linting errors

* minor requested fixes

* Use let/const.  Fix lint issues

* Remove blob test that can never fail

* More let vs var fixes

* More minor requested fixes

* Rework code to use fetchWithAuth

* Async tweaks

* Lint

* Fix onboarding

* Add credentials for onboarding

* Lint
2018-09-11 11:33:57 +02:00
Paulus Schoutsen
0997274f29 Update external auth (#1655)
* Update external auth

* Lint
2018-09-11 10:24:01 +02:00
cdce8p
76161329b6 Add lovelace template extension points (#1653) 2018-09-10 23:15:29 +02:00
John Arild Berentsen
8505750958 Load ozw-log in new window, and add tail-like button. (#1652)
* Load log in new window, and add tail

* avoid duplicate code
2018-09-10 21:53:16 +02:00
Paulus Schoutsen
4077105db1 Version bump to 20180910.0 2018-09-10 13:28:51 +02:00
Paulus Schoutsen
3f31d83a55 Update translations 2018-09-10 13:28:33 +02:00
Paulus Schoutsen
d729e3c567 Update HAWS to 3.1.2 2018-09-10 13:25:50 +02:00
Paulus Schoutsen
9af75f9a43 Prevent changing domain entity ID (#1650) 2018-09-10 13:14:21 +02:00
Alessandro Staniscia
d32d334a2e Review Docker management (#1113)
* Review Docker management ( linked with #934 )

*  fix comment by @balloob

* Explicit remove of  package-lock.json

* moved on feature branch, merge docker scripts, added documetation

* Used alphine as requested by @balloob on https://github.com/home-assistant/home-assistant-polymer/pull/947 and followed the @mcspr comment https://github.com/home-assistant/home-assistant-polymer/issues/934

* Remove package-lock from gitignore, we don't use npm

* Update for new build instructions
2018-09-10 11:58:18 +02:00
Charles Garwood
94006a843c ZWave Panel Updates (#1647)
* Add padding to zwave log text

* Replace entity dropdown with actual entity_id

* Add Node Information button for more-info popup, and remove less-functional Node Info card.

* Fix indentation

* Address review comments

* Fix lint/mixin

* Update comment
2018-09-10 10:16:10 +02:00
Jason Hu
4790590327 Try to resolve workbox warning (#1648) 2018-09-10 10:15:20 +02:00
Charles Garwood
7cf7763e21 Fix body stream already read error (#1646) 2018-09-08 21:06:07 +02:00
Paulus Schoutsen
0d7979a72f Add revoke token to (external) auth (#1639)
* Add revoke token to external auth

* Lint

* Update to HAWS 3.1.1

* Fix constant
2018-09-07 20:37:06 +02:00
Paulus Schoutsen
300425e698 Redirect to onboarding from auth page (#1640)
* Redirect to onboarding from auth page

* Remove left over trial code
2018-09-07 20:13:00 +02:00
Stephen Vanterpool
59010baf89 Fix the way calls are made over the javascript bridge (#1644)
* Fix the way calls are made over the javascript bridge

* Update external_auth.js
2018-09-07 20:12:52 +02:00
Paulus Schoutsen
47fcb122a2 Don't delete system generated user (#1638) 2018-09-07 19:41:06 +02:00
Paulus Schoutsen
bbb50b1397 Better handle auth (#1637)
* Better handle auth

* Lint
2018-09-07 19:40:56 +02:00
Paulus Schoutsen
ae8724d699 Compress using zopfli (#1636) 2018-09-05 11:41:03 +02:00
Paulus Schoutsen
2169f6979d Remove link to alexa web 2018-09-04 15:02:35 +02:00
Paulus Schoutsen
9cc577e9c7 Add external auth (#1621)
* Add external auth

* Lint

* Warn when external auth not present
2018-09-03 09:00:39 -07:00
Paulus Schoutsen
6ead58f62f Version bump to 20180903.0 2018-09-03 13:17:04 +02:00
Paulus Schoutsen
ec3118227c Update translations 2018-09-03 13:16:45 +02:00
Paulus Schoutsen
0d3d9bc78a Upgrade MDI icons (#1630) 2018-09-03 13:07:58 +02:00
Jason Hu
e16b3db0d4 Ask "save to login" after hassConnected (#1631) 2018-09-03 13:07:34 +02:00
Timmo
cdab874b5b Autocapitalization of username field (#1627)
* 🔨 fix capitalization of username field

* 🔨 change words to on
2018-09-03 13:06:15 +02:00
Paulus Schoutsen
bf40995b16 Show an error when invalid client id or redirect uri (#1620) 2018-09-02 10:29:38 -07:00
Paulus Schoutsen
68b3a4fbb7 Version bump to 20180831.0 2018-08-31 12:45:59 +02:00
Paulus Schoutsen
c38bfa1101 Update translations 2018-08-31 12:45:42 +02:00
Jerad Meisner
af7a85eeb7 Force line chart for climate state history. (#1617)
* Force line chart for climate state history.

* Simplify climate check
2018-08-31 12:44:07 +02:00
Paulus Schoutsen
2bd5dc21a8 Fix refresh user (#1618)
* Fix refresh user

* Lint
2018-08-31 12:28:32 +02:00
Paulus Schoutsen
18a151c8e8 Fix Safari Profile page (#1619) 2018-08-31 11:17:57 +02:00
Paulus Schoutsen
da19a1a9c6 Fix header for glance cards 2018-08-31 11:15:06 +02:00
Paulus Schoutsen
45cdb5a3e4 Use new version of HAWS (#1612)
* Use new version of HAWS

* Fix init page

* Lint

* Fix tests

* Update gitignore

* Clear old tokens, use new key to store
2018-08-31 09:45:58 +02:00
Paulus Schoutsen
ab19dbc35e Version bump to 20180829.1 2018-08-29 22:49:34 +02:00
Paulus Schoutsen
6a443734a1 Update translations 2018-08-29 22:49:21 +02:00
Jason Hu
f0251d3056 Fix for login flow switch (#1609)
* Fix for login flow switch

* Switch flow shall clear step data
2018-08-29 22:48:32 +02:00
Paulus Schoutsen
31127ccf29 Version bump to 20180829.0 2018-08-29 10:23:27 +02:00
Paulus Schoutsen
b97e055b39 Update translations 2018-08-29 10:22:57 +02:00
Jason Hu
f4ce1ee0fa Add some translation for login flow (#1608)
* Add some translation for login flow

* Fix typo
2018-08-29 10:18:55 +02:00
PhracturedBlue
2a29311ca5 Lovelace: Don't show badge entities in unused list (#1607) 2018-08-28 20:34:12 +02:00
Paulus Schoutsen
8cfd7ee170 Version bump to 20180827.0 2018-08-27 22:20:31 +02:00
Paulus Schoutsen
59a8354a7f Update translations 2018-08-27 22:20:17 +02:00
Paulus Schoutsen
f443942e03 Authorize onboarding (#1603)
* Tweak onboarding + authorize

* Authorize/Onboarding pimp

* More tweaks

* Comments
2018-08-27 22:10:15 +02:00
Alok Saboo
772208ba22 Fixed typo (#1604) 2018-08-27 21:50:14 +02:00
Paulus Schoutsen
e46a1be5d7 Center svg in markdown 2018-08-26 22:00:57 +02:00
556 changed files with 31082 additions and 14176 deletions

View File

@@ -1,5 +1,5 @@
{
"extends": "airbnb-base",
"extends": ["airbnb-base", "prettier"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true,
@@ -67,13 +67,11 @@
"react/no-find-dom-node": 2,
"react/no-is-mounted": 2,
"react/jsx-no-comment-textnodes": 2,
"react/jsx-curly-spacing": 2,
"react/jsx-no-undef": 2,
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"no-restricted-syntax": [0, "ForOfStatement"]
"no-restricted-syntax": [0, "ForOfStatement"],
"prettier/prettier": "error"
},
"plugins": [
"react"
]
"plugins": ["react", "prettier"]
}

View File

@@ -9,6 +9,7 @@
"parser": "babel-eslint",
"rules": {
"import/no-unresolved": 2,
"linebreak-style": 0
"linebreak-style": 0,
"implicit-arrow-linebreak": 0
}
}

3
.gitignore vendored
View File

@@ -21,6 +21,9 @@ lib
bin
dist
# vscode
.vscode
# Secrets
.lokalise_token
yarn-error.log

View File

@@ -1,25 +1,31 @@
FROM node:8.2.1-alpine
FROM node:8.11.1-alpine
# install yarn
ENV PATH /root/.yarn/bin:$PATH
## Install/force base tools
RUN apk update \
&& apk add curl bash binutils tar git python3 \
&& apk add make g++ curl bash binutils tar git python2 python3 \
&& rm -rf /var/cache/apk/* \
&& /bin/bash \
&& touch ~/.bashrc \
&& curl -o- -L https://yarnpkg.com/install.sh | bash
&& touch ~/.bashrc
## Install yarn
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
## Setup the project
RUN mkdir -p /frontend
WORKDIR /frontend
ENV NODE_ENV production
COPY package.json yarn.lock ./
COPY package.json ./
RUN yarn
COPY bower.json ./
RUN ./node_modules/.bin/bower install --allow-root
RUN yarn install --frozen-lockfile
COPY . .
CMD [ "/bin/bash", "./script/build_frontend" ]
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
RUN chmod +x /usr/bin/docker_entrypoint.sh
CMD [ "docker_entrypoint.sh" ]

View File

@@ -16,6 +16,18 @@ This is the repository for the official [Home Assistant](https://home-assistant.
- Gallery: `cd gallery && script/develop_gallery`
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
## Frontend development
### Classic environment
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
### Docker environment
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
* `sh ./script/docker_run.sh build` Build all the project with one command
* `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the *classic environment*) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.

35
config/babel.js Normal file
View File

@@ -0,0 +1,35 @@
module.exports.babelLoaderConfig = ({ latestBuild }) => {
if (latestBuild === undefined) {
throw Error("latestBuild not defined for babel loader config");
}
return {
test: /\.m?js$|\.ts$/,
use: {
loader: "babel-loader",
options: {
presets: [
!latestBuild && [
require("@babel/preset-env").default,
{ modules: false },
],
require("@babel/preset-typescript").default,
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
[
"@babel/transform-react-jsx",
{
pragma: "h",
},
],
],
},
},
};
};

View File

@@ -1,12 +1,12 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import JsYaml from 'js-yaml';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import JsYaml from "js-yaml";
import HomeAssistant from '../data/hass.js';
import demoConfig from '../data/demo_config.js';
import demoResources from '../data/demo_resources.js';
import demoStates from '../data/demo_states.js';
import createCardElement from '../../../src/panels/lovelace/common/create-card-element.js';
import HomeAssistant from "../data/hass.js";
import demoConfig from "../data/demo_config.js";
import demoResources from "../data/demo_resources.js";
import demoStates from "../data/demo_states.js";
import createCardElement from "../../../src/panels/lovelace/common/create-card-element.js";
class DemoCard extends PolymerElement {
static get template() {
@@ -50,11 +50,11 @@ class DemoCard extends PolymerElement {
return {
hass: {
type: Object,
observer: '_hassChanged',
observer: "_hassChanged",
},
config: {
type: Object,
observer: '_configChanged'
observer: "_configChanged",
},
showConfig: Boolean,
};
@@ -74,7 +74,7 @@ class DemoCard extends PolymerElement {
const hass = new HomeAssistant(demoStates);
hass.config = demoConfig;
hass.resources = demoResources;
hass.language = 'en';
hass.language = "en";
hass.states = demoStates;
el.hass = hass;
}
@@ -92,4 +92,4 @@ class DemoCard extends PolymerElement {
}
}
customElements.define('demo-card', DemoCard);
customElements.define("demo-card", DemoCard);

View File

@@ -1,9 +1,9 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/paper-toggle-button/paper-toggle-button.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/paper-toggle-button/paper-toggle-button.js";
import './demo-card.js';
import "./demo-card.js";
class DemoCards extends PolymerElement {
static get template() {
@@ -50,9 +50,9 @@ class DemoCards extends PolymerElement {
_showConfig: {
type: Boolean,
value: false,
}
},
};
}
}
customElements.define('demo-cards', DemoCards);
customElements.define("demo-cards", DemoCards);

View File

@@ -1,10 +1,9 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '../../../src/state-summary/state-card-content.js';
import '../../../src/dialogs/more-info/controls/more-info-content.js';
import '../../../src/components/ha-card.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import "../../../src/state-summary/state-card-content.js";
import "../../../src/dialogs/more-info/controls/more-info-content.js";
import "../../../src/components/ha-card.js";
class DemoMoreInfo extends PolymerElement {
static get template() {
@@ -68,8 +67,8 @@ class DemoMoreInfo extends PolymerElement {
showConfig: Boolean,
_stateObj: {
type: Object,
computed: '_getState(entityId, hass.states)'
}
computed: "_getState(entityId, hass.states)",
},
};
}
@@ -82,7 +81,7 @@ class DemoMoreInfo extends PolymerElement {
// (it sucks, we will remove in the future)
const tmp = {};
Object.keys(stateObj).forEach((key) => {
if (key[0] !== '_') {
if (key[0] !== "_") {
tmp[key] = stateObj[key];
}
});
@@ -90,4 +89,4 @@ class DemoMoreInfo extends PolymerElement {
}
}
customElements.define('demo-more-info', DemoMoreInfo);
customElements.define("demo-more-info", DemoMoreInfo);

View File

@@ -1,9 +1,9 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/paper-toggle-button/paper-toggle-button.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/paper-toggle-button/paper-toggle-button.js";
import './demo-more-info.js';
import "./demo-more-info.js";
class DemoMoreInfos extends PolymerElement {
static get template() {
@@ -50,9 +50,9 @@ class DemoMoreInfos extends PolymerElement {
_showConfig: {
type: Boolean,
value: false,
}
},
};
}
}
customElements.define('demo-more-infos', DemoMoreInfos);
customElements.define("demo-more-infos", DemoMoreInfos);

View File

@@ -4,172 +4,106 @@ export default {
latitude: 51.5287352,
longitude: -0.381773,
unit_system: {
length: 'km',
mass: 'kg',
temperature: '°C',
volume: 'L'
}
length: "km",
mass: "kg",
temperature: "°C",
volume: "L",
},
},
services: {
configurator: [
'configure'
],
tts: [
'demo_say',
'clear_cache'
],
configurator: ["configure"],
tts: ["demo_say", "clear_cache"],
cover: [
'open_cover',
'close_cover',
'open_cover_tilt',
'close_cover_tilt',
'set_cover_tilt_position',
'set_cover_position',
'stop_cover_tilt',
'stop_cover'
],
group: [
'set',
'reload',
'remove',
'set_visibility'
"open_cover",
"close_cover",
"open_cover_tilt",
"close_cover_tilt",
"set_cover_tilt_position",
"set_cover_position",
"stop_cover_tilt",
"stop_cover",
],
group: ["set", "reload", "remove", "set_visibility"],
alarm_control_panel: [
'alarm_arm_night',
'alarm_disarm',
'alarm_trigger',
'alarm_arm_home',
'alarm_arm_away',
'alarm_arm_custom_bypass'
],
conversation: [
'process'
],
notify: [
'demo_test_target_name',
'notify'
],
lock: [
'open',
'lock',
'unlock'
"alarm_arm_night",
"alarm_disarm",
"alarm_trigger",
"alarm_arm_home",
"alarm_arm_away",
"alarm_arm_custom_bypass",
],
conversation: ["process"],
notify: ["demo_test_target_name", "notify"],
lock: ["open", "lock", "unlock"],
input_select: [
'select_previous',
'set_options',
'select_next',
'select_option'
],
recorder: [
'purge'
],
persistent_notification: [
'create',
'dismiss'
],
timer: [
'pause',
'cancel',
'finish',
'start'
],
input_boolean: [
'turn_off',
'toggle',
'turn_on'
"select_previous",
"set_options",
"select_next",
"select_option",
],
recorder: ["purge"],
persistent_notification: ["create", "dismiss"],
timer: ["pause", "cancel", "finish", "start"],
input_boolean: ["turn_off", "toggle", "turn_on"],
fan: [
'set_speed',
'turn_on',
'turn_off',
'set_direction',
'oscillate',
'toggle'
"set_speed",
"turn_on",
"turn_off",
"set_direction",
"oscillate",
"toggle",
],
climate: [
'set_humidity',
'set_operation_mode',
'set_aux_heat',
'turn_on',
'set_hold_mode',
'set_away_mode',
'turn_off',
'set_fan_mode',
'set_temperature',
'set_swing_mode'
],
switch: [
'turn_off',
'toggle',
'turn_on'
],
script: [
'turn_off',
'demo',
'reload',
'toggle',
'turn_on'
],
scene: [
'turn_on'
],
system_log: [
'clear',
'write'
],
camera: [
'disable_motion_detection',
'enable_motion_detection',
'snapshot'
],
image_processing: [
'scan'
"set_humidity",
"set_operation_mode",
"set_aux_heat",
"turn_on",
"set_hold_mode",
"set_away_mode",
"turn_off",
"set_fan_mode",
"set_temperature",
"set_swing_mode",
],
switch: ["turn_off", "toggle", "turn_on"],
script: ["turn_off", "demo", "reload", "toggle", "turn_on"],
scene: ["turn_on"],
system_log: ["clear", "write"],
camera: ["disable_motion_detection", "enable_motion_detection", "snapshot"],
image_processing: ["scan"],
media_player: [
'media_previous_track',
'clear_playlist',
'shuffle_set',
'media_seek',
'turn_on',
'media_play_pause',
'media_next_track',
'media_pause',
'volume_down',
'volume_set',
'media_stop',
'toggle',
'media_play',
'play_media',
'volume_mute',
'turn_off',
'select_sound_mode',
'select_source',
'volume_up'
],
input_number: [
'set_value',
'increment',
'decrement'
],
device_tracker: [
'see'
"media_previous_track",
"clear_playlist",
"shuffle_set",
"media_seek",
"turn_on",
"media_play_pause",
"media_next_track",
"media_pause",
"volume_down",
"volume_set",
"media_stop",
"toggle",
"media_play",
"play_media",
"volume_mute",
"turn_off",
"select_sound_mode",
"select_source",
"volume_up",
],
input_number: ["set_value", "increment", "decrement"],
device_tracker: ["see"],
homeassistant: [
'stop',
'check_config',
'reload_core_config',
'turn_on',
'turn_off',
'restart',
'toggle'
"stop",
"check_config",
"reload_core_config",
"turn_on",
"turn_off",
"restart",
"toggle",
],
light: [
'turn_off',
'toggle',
'turn_on'
],
input_text: [
'set_value'
]
}
light: ["turn_off", "toggle", "turn_on"],
input_text: ["set_value"],
},
};

View File

@@ -1,253 +1,264 @@
export default {
en: {
'state.default.off': 'Off',
'state.default.on': 'On',
'state.default.unknown': 'Unknown',
'state.default.unavailable': 'Unavailable',
'state.alarm_control_panel.armed': 'Armed',
'state.alarm_control_panel.disarmed': 'Disarmed',
'state.alarm_control_panel.armed_home': 'Armed home',
'state.alarm_control_panel.armed_away': 'Armed away',
'state.alarm_control_panel.armed_night': 'Armed night',
'state.alarm_control_panel.armed_custom_bypass': 'Armed custom bypass',
'state.alarm_control_panel.pending': 'Pending',
'state.alarm_control_panel.arming': 'Arming',
'state.alarm_control_panel.disarming': 'Disarming',
'state.alarm_control_panel.triggered': 'Triggered',
'state.automation.off': 'Off',
'state.automation.on': 'On',
'state.binary_sensor.default.off': 'Off',
'state.binary_sensor.default.on': 'On',
'state.binary_sensor.battery.off': 'Normal',
'state.binary_sensor.battery.on': 'Low',
'state.binary_sensor.cold.off': 'Normal',
'state.binary_sensor.cold.on': 'Cold',
'state.binary_sensor.connectivity.off': 'Disconnected',
'state.binary_sensor.connectivity.on': 'Connected',
'state.binary_sensor.door.off': 'Closed',
'state.binary_sensor.door.on': 'Open',
'state.binary_sensor.garage_door.off': 'Closed',
'state.binary_sensor.garage_door.on': 'Open',
'state.binary_sensor.gas.off': 'Clear',
'state.binary_sensor.gas.on': 'Detected',
'state.binary_sensor.heat.off': 'Normal',
'state.binary_sensor.heat.on': 'Hot',
'state.binary_sensor.lock.off': 'Locked',
'state.binary_sensor.lock.on': 'Unlocked',
'state.binary_sensor.moisture.off': 'Dry',
'state.binary_sensor.moisture.on': 'Wet',
'state.binary_sensor.motion.off': 'Clear',
'state.binary_sensor.motion.on': 'Detected',
'state.binary_sensor.occupancy.off': 'Clear',
'state.binary_sensor.occupancy.on': 'Detected',
'state.binary_sensor.opening.off': 'Closed',
'state.binary_sensor.opening.on': 'Open',
'state.binary_sensor.presence.off': 'Away',
'state.binary_sensor.presence.on': 'Home',
'state.binary_sensor.problem.off': 'OK',
'state.binary_sensor.problem.on': 'Problem',
'state.binary_sensor.safety.off': 'Safe',
'state.binary_sensor.safety.on': 'Unsafe',
'state.binary_sensor.smoke.off': 'Clear',
'state.binary_sensor.smoke.on': 'Detected',
'state.binary_sensor.sound.off': 'Clear',
'state.binary_sensor.sound.on': 'Detected',
'state.binary_sensor.vibration.off': 'Clear',
'state.binary_sensor.vibration.on': 'Detected',
'state.binary_sensor.window.off': 'Closed',
'state.binary_sensor.window.on': 'Open',
'state.calendar.off': 'Off',
'state.calendar.on': 'On',
'state.camera.recording': 'Recording',
'state.camera.streaming': 'Streaming',
'state.camera.idle': 'Idle',
'state.climate.off': 'Off',
'state.climate.on': 'On',
'state.climate.heat': 'Heat',
'state.climate.cool': 'Cool',
'state.climate.idle': 'Idle',
'state.climate.auto': 'Auto',
'state.climate.dry': 'Dry',
'state.climate.fan_only': 'Fan only',
'state.climate.eco': 'Eco',
'state.climate.electric': 'Electric',
'state.climate.performance': 'Performance',
'state.climate.high_demand': 'High demand',
'state.climate.heat_pump': 'Heat pump',
'state.climate.gas': 'Gas',
'state.configurator.configure': 'Configure',
'state.configurator.configured': 'Configured',
'state.cover.open': 'Open',
'state.cover.opening': 'Opening',
'state.cover.closed': 'Closed',
'state.cover.closing': 'Closing',
'state.cover.stopped': 'Stopped',
'state.device_tracker.home': 'Home',
'state.device_tracker.not_home': 'Away',
'state.fan.off': 'Off',
'state.fan.on': 'On',
'state.group.off': 'Off',
'state.group.on': 'On',
'state.group.home': 'Home',
'state.group.not_home': 'Away',
'state.group.open': 'Open',
'state.group.opening': 'Opening',
'state.group.closed': 'Closed',
'state.group.closing': 'Closing',
'state.group.stopped': 'Stopped',
'state.group.locked': 'Locked',
'state.group.unlocked': 'Unlocked',
'state.group.ok': 'OK',
'state.group.problem': 'Problem',
'state.input_boolean.off': 'Off',
'state.input_boolean.on': 'On',
'state.light.off': 'Off',
'state.light.on': 'On',
'state.lock.locked': 'Locked',
'state.lock.unlocked': 'Unlocked',
'state.media_player.off': 'Off',
'state.media_player.on': 'On',
'state.media_player.playing': 'Playing',
'state.media_player.paused': 'Paused',
'state.media_player.idle': 'Idle',
'state.media_player.standby': 'Standby',
'state.plant.ok': 'OK',
'state.plant.problem': 'Problem',
'state.remote.off': 'Off',
'state.remote.on': 'On',
'state.scene.scening': 'Scening',
'state.script.off': 'Off',
'state.script.on': 'On',
'state.sensor.off': 'Off',
'state.sensor.on': 'On',
'state.sun.above_horizon': 'Above horizon',
'state.sun.below_horizon': 'Below horizon',
'state.switch.off': 'Off',
'state.switch.on': 'On',
'state.weather.clear-night': 'Clear, night',
'state.weather.cloudy': 'Cloudy',
'state.weather.fog': 'Fog',
'state.weather.hail': 'Hail',
'state.weather.lightning': 'Lightning',
'state.weather.lightning-rainy': 'Lightning, rainy',
'state.weather.partlycloudy': 'Partly cloudy',
'state.weather.pouring': 'Pouring',
'state.weather.rainy': 'Rainy',
'state.weather.snowy': 'Snowy',
'state.weather.snowy-rainy': 'Snowy, rainy',
'state.weather.sunny': 'Sunny',
'state.weather.windy': 'Windy',
'state.weather.windy-variant': 'Windy',
'state.zwave.default.initializing': 'Initializing',
'state.zwave.default.dead': 'Dead',
'state.zwave.default.sleeping': 'Sleeping',
'state.zwave.default.ready': 'Ready',
'state.zwave.query_stage.initializing': 'Initializing ({query_stage})',
'state.zwave.query_stage.dead': 'Dead ({query_stage})',
'state_badge.default.unknown': 'Unk',
'state_badge.default.unavailable': 'Unavai',
'state_badge.alarm_control_panel.armed': 'Armed',
'state_badge.alarm_control_panel.disarmed': 'Disarm',
'state_badge.alarm_control_panel.armed_home': 'Armed',
'state_badge.alarm_control_panel.armed_away': 'Armed',
'state_badge.alarm_control_panel.armed_night': 'Armed',
'state_badge.alarm_control_panel.armed_custom_bypass': 'Armed',
'state_badge.alarm_control_panel.pending': 'Pend',
'state_badge.alarm_control_panel.arming': 'Arming',
'state_badge.alarm_control_panel.disarming': 'Disarm',
'state_badge.alarm_control_panel.triggered': 'Trig',
'state_badge.device_tracker.home': 'Home',
'state_badge.device_tracker.not_home': 'Away',
'ui.card.alarm_control_panel.code': 'Code',
'ui.card.alarm_control_panel.clear_code': 'Clear',
'ui.card.alarm_control_panel.disarm': 'Disarm',
'ui.card.alarm_control_panel.arm_home': 'Arm home',
'ui.card.alarm_control_panel.arm_away': 'Arm away',
'ui.card.automation.last_triggered': 'Last triggered',
'ui.card.automation.trigger': 'Trigger',
'ui.card.camera.not_available': 'Image not available',
'ui.card.climate.currently': 'Currently',
'ui.card.climate.on_off': 'On / off',
'ui.card.climate.target_temperature': 'Target temperature',
'ui.card.climate.target_humidity': 'Target humidity',
'ui.card.climate.operation': 'Operation',
'ui.card.climate.fan_mode': 'Fan mode',
'ui.card.climate.swing_mode': 'Swing mode',
'ui.card.climate.away_mode': 'Away mode',
'ui.card.climate.aux_heat': 'Aux heat',
'ui.card.cover.position': 'Position',
'ui.card.cover.tilt_position': 'Tilt position',
'ui.card.fan.speed': 'Speed',
'ui.card.fan.oscillate': 'Oscillate',
'ui.card.fan.direction': 'Direction',
'ui.card.light.brightness': 'Brightness',
'ui.card.light.color_temperature': 'Color temperature',
'ui.card.light.white_value': 'White value',
'ui.card.light.effect': 'Effect',
'ui.card.lock.code': 'Code',
'ui.card.lock.lock': 'Lock',
'ui.card.lock.unlock': 'Unlock',
'ui.card.media_player.source': 'Source',
'ui.card.media_player.sound_mode': 'Sound mode',
'ui.card.media_player.text_to_speak': 'Text to speak',
'ui.card.persistent_notification.dismiss': 'Dismiss',
'ui.card.scene.activate': 'Activate',
'ui.card.script.execute': 'Execute',
'ui.card.weather.attributes.air_pressure': 'Air pressure',
'ui.card.weather.attributes.humidity': 'Humidity',
'ui.card.weather.attributes.temperature': 'Temperature',
'ui.card.weather.attributes.visibility': 'Visibility',
'ui.card.weather.attributes.wind_speed': 'Wind speed',
'ui.card.weather.cardinal_direction.e': 'E',
'ui.card.weather.cardinal_direction.ene': 'ENE',
'ui.card.weather.cardinal_direction.ese': 'ESE',
'ui.card.weather.cardinal_direction.n': 'N',
'ui.card.weather.cardinal_direction.ne': 'NE',
'ui.card.weather.cardinal_direction.nne': 'NNE',
'ui.card.weather.cardinal_direction.nw': 'NW',
'ui.card.weather.cardinal_direction.nnw': 'NNW',
'ui.card.weather.cardinal_direction.s': 'S',
'ui.card.weather.cardinal_direction.se': 'SE',
'ui.card.weather.cardinal_direction.sse': 'SSE',
'ui.card.weather.cardinal_direction.ssw': 'SSW',
'ui.card.weather.cardinal_direction.sw': 'SW',
'ui.card.weather.cardinal_direction.w': 'W',
'ui.card.weather.cardinal_direction.wnw': 'WNW',
'ui.card.weather.cardinal_direction.wsw': 'WSW',
'ui.card.weather.forecast': 'Forecast',
'ui.common.loading': 'Loading',
'ui.common.cancel': 'Cancel',
'ui.components.entity.entity-picker.entity': 'Entity',
'ui.components.relative_time.past': '{time} ago',
'ui.components.relative_time.future': 'In {time}',
'ui.components.relative_time.never': 'Never',
'ui.components.relative_time.duration.second': '{count} {count, plural,\n one {second}\n other {seconds}\n}',
'ui.components.relative_time.duration.minute': '{count} {count, plural,\n one {minute}\n other {minutes}\n}',
'ui.components.relative_time.duration.hour': '{count} {count, plural,\n one {hour}\n other {hours}\n}',
'ui.components.relative_time.duration.day': '{count} {count, plural,\n one {day}\n other {days}\n}',
'ui.components.relative_time.duration.week': '{count} {count, plural,\n one {week}\n other {weeks}\n}',
'ui.components.history_charts.loading_history': 'Loading state history...',
'ui.components.history_charts.no_history_found': 'No state history found.',
'ui.components.service-picker.service': 'Service',
'ui.dialogs.more_info_settings.save': 'Save',
'ui.dialogs.more_info_settings.name': 'Name',
'ui.duration.second': '{count} {count, plural,\n one {second}\n other {seconds}\n}',
'ui.duration.minute': '{count} {count, plural,\n one {minute}\n other {minutes}\n}',
'ui.duration.hour': '{count} {count, plural,\n one {hour}\n other {hours}\n}',
'ui.duration.day': '{count} {count, plural,\n one {day}\n other {days}\n}',
'ui.duration.week': '{count} {count, plural,\n one {week}\n other {weeks}\n}',
'ui.login-form.password': 'Password',
'ui.login-form.remember': 'Remember',
'ui.login-form.log_in': 'Log in',
'ui.notification_toast.entity_turned_on': 'Turned on {entity}.',
'ui.notification_toast.entity_turned_off': 'Turned off {entity}.',
'ui.notification_toast.service_called': 'Service {service} called.',
'ui.notification_toast.service_call_failed': 'Failed to call service {service}.',
'ui.notification_toast.connection_lost': 'Connection lost. Reconnecting…',
'ui.sidebar.developer_tools': 'Developer tools',
'ui.sidebar.log_out': 'Log out',
'attribute.weather.humidity': 'Humidity',
'attribute.weather.visibility': 'Visibility',
'attribute.weather.wind_speed': 'Wind speed',
}
"state.default.off": "Off",
"state.default.on": "On",
"state.default.unknown": "Unknown",
"state.default.unavailable": "Unavailable",
"state.alarm_control_panel.armed": "Armed",
"state.alarm_control_panel.disarmed": "Disarmed",
"state.alarm_control_panel.armed_home": "Armed home",
"state.alarm_control_panel.armed_away": "Armed away",
"state.alarm_control_panel.armed_night": "Armed night",
"state.alarm_control_panel.armed_custom_bypass": "Armed custom bypass",
"state.alarm_control_panel.pending": "Pending",
"state.alarm_control_panel.arming": "Arming",
"state.alarm_control_panel.disarming": "Disarming",
"state.alarm_control_panel.triggered": "Triggered",
"state.automation.off": "Off",
"state.automation.on": "On",
"state.binary_sensor.default.off": "Off",
"state.binary_sensor.default.on": "On",
"state.binary_sensor.battery.off": "Normal",
"state.binary_sensor.battery.on": "Low",
"state.binary_sensor.cold.off": "Normal",
"state.binary_sensor.cold.on": "Cold",
"state.binary_sensor.connectivity.off": "Disconnected",
"state.binary_sensor.connectivity.on": "Connected",
"state.binary_sensor.door.off": "Closed",
"state.binary_sensor.door.on": "Open",
"state.binary_sensor.garage_door.off": "Closed",
"state.binary_sensor.garage_door.on": "Open",
"state.binary_sensor.gas.off": "Clear",
"state.binary_sensor.gas.on": "Detected",
"state.binary_sensor.heat.off": "Normal",
"state.binary_sensor.heat.on": "Hot",
"state.binary_sensor.lock.off": "Locked",
"state.binary_sensor.lock.on": "Unlocked",
"state.binary_sensor.moisture.off": "Dry",
"state.binary_sensor.moisture.on": "Wet",
"state.binary_sensor.motion.off": "Clear",
"state.binary_sensor.motion.on": "Detected",
"state.binary_sensor.occupancy.off": "Clear",
"state.binary_sensor.occupancy.on": "Detected",
"state.binary_sensor.opening.off": "Closed",
"state.binary_sensor.opening.on": "Open",
"state.binary_sensor.presence.off": "Away",
"state.binary_sensor.presence.on": "Home",
"state.binary_sensor.problem.off": "OK",
"state.binary_sensor.problem.on": "Problem",
"state.binary_sensor.safety.off": "Safe",
"state.binary_sensor.safety.on": "Unsafe",
"state.binary_sensor.smoke.off": "Clear",
"state.binary_sensor.smoke.on": "Detected",
"state.binary_sensor.sound.off": "Clear",
"state.binary_sensor.sound.on": "Detected",
"state.binary_sensor.vibration.off": "Clear",
"state.binary_sensor.vibration.on": "Detected",
"state.binary_sensor.window.off": "Closed",
"state.binary_sensor.window.on": "Open",
"state.calendar.off": "Off",
"state.calendar.on": "On",
"state.camera.recording": "Recording",
"state.camera.streaming": "Streaming",
"state.camera.idle": "Idle",
"state.climate.off": "Off",
"state.climate.on": "On",
"state.climate.heat": "Heat",
"state.climate.cool": "Cool",
"state.climate.idle": "Idle",
"state.climate.auto": "Auto",
"state.climate.dry": "Dry",
"state.climate.fan_only": "Fan only",
"state.climate.eco": "Eco",
"state.climate.electric": "Electric",
"state.climate.performance": "Performance",
"state.climate.high_demand": "High demand",
"state.climate.heat_pump": "Heat pump",
"state.climate.gas": "Gas",
"state.configurator.configure": "Configure",
"state.configurator.configured": "Configured",
"state.cover.open": "Open",
"state.cover.opening": "Opening",
"state.cover.closed": "Closed",
"state.cover.closing": "Closing",
"state.cover.stopped": "Stopped",
"state.device_tracker.home": "Home",
"state.device_tracker.not_home": "Away",
"state.fan.off": "Off",
"state.fan.on": "On",
"state.group.off": "Off",
"state.group.on": "On",
"state.group.home": "Home",
"state.group.not_home": "Away",
"state.group.open": "Open",
"state.group.opening": "Opening",
"state.group.closed": "Closed",
"state.group.closing": "Closing",
"state.group.stopped": "Stopped",
"state.group.locked": "Locked",
"state.group.unlocked": "Unlocked",
"state.group.ok": "OK",
"state.group.problem": "Problem",
"state.input_boolean.off": "Off",
"state.input_boolean.on": "On",
"state.light.off": "Off",
"state.light.on": "On",
"state.lock.locked": "Locked",
"state.lock.unlocked": "Unlocked",
"state.media_player.off": "Off",
"state.media_player.on": "On",
"state.media_player.playing": "Playing",
"state.media_player.paused": "Paused",
"state.media_player.idle": "Idle",
"state.media_player.standby": "Standby",
"state.plant.ok": "OK",
"state.plant.problem": "Problem",
"state.remote.off": "Off",
"state.remote.on": "On",
"state.scene.scening": "Scening",
"state.script.off": "Off",
"state.script.on": "On",
"state.sensor.off": "Off",
"state.sensor.on": "On",
"state.sun.above_horizon": "Above horizon",
"state.sun.below_horizon": "Below horizon",
"state.switch.off": "Off",
"state.switch.on": "On",
"state.weather.clear-night": "Clear, night",
"state.weather.cloudy": "Cloudy",
"state.weather.fog": "Fog",
"state.weather.hail": "Hail",
"state.weather.lightning": "Lightning",
"state.weather.lightning-rainy": "Lightning, rainy",
"state.weather.partlycloudy": "Partly cloudy",
"state.weather.pouring": "Pouring",
"state.weather.rainy": "Rainy",
"state.weather.snowy": "Snowy",
"state.weather.snowy-rainy": "Snowy, rainy",
"state.weather.sunny": "Sunny",
"state.weather.windy": "Windy",
"state.weather.windy-variant": "Windy",
"state.zwave.default.initializing": "Initializing",
"state.zwave.default.dead": "Dead",
"state.zwave.default.sleeping": "Sleeping",
"state.zwave.default.ready": "Ready",
"state.zwave.query_stage.initializing": "Initializing ({query_stage})",
"state.zwave.query_stage.dead": "Dead ({query_stage})",
"state_badge.default.unknown": "Unk",
"state_badge.default.unavailable": "Unavai",
"state_badge.alarm_control_panel.armed": "Armed",
"state_badge.alarm_control_panel.disarmed": "Disarm",
"state_badge.alarm_control_panel.armed_home": "Armed",
"state_badge.alarm_control_panel.armed_away": "Armed",
"state_badge.alarm_control_panel.armed_night": "Armed",
"state_badge.alarm_control_panel.armed_custom_bypass": "Armed",
"state_badge.alarm_control_panel.pending": "Pend",
"state_badge.alarm_control_panel.arming": "Arming",
"state_badge.alarm_control_panel.disarming": "Disarm",
"state_badge.alarm_control_panel.triggered": "Trig",
"state_badge.device_tracker.home": "Home",
"state_badge.device_tracker.not_home": "Away",
"ui.card.alarm_control_panel.code": "Code",
"ui.card.alarm_control_panel.clear_code": "Clear",
"ui.card.alarm_control_panel.disarm": "Disarm",
"ui.card.alarm_control_panel.arm_home": "Arm home",
"ui.card.alarm_control_panel.arm_away": "Arm away",
"ui.card.automation.last_triggered": "Last triggered",
"ui.card.automation.trigger": "Trigger",
"ui.card.camera.not_available": "Image not available",
"ui.card.climate.currently": "Currently",
"ui.card.climate.on_off": "On / off",
"ui.card.climate.target_temperature": "Target temperature",
"ui.card.climate.target_humidity": "Target humidity",
"ui.card.climate.operation": "Operation",
"ui.card.climate.fan_mode": "Fan mode",
"ui.card.climate.swing_mode": "Swing mode",
"ui.card.climate.away_mode": "Away mode",
"ui.card.climate.aux_heat": "Aux heat",
"ui.card.cover.position": "Position",
"ui.card.cover.tilt_position": "Tilt position",
"ui.card.fan.speed": "Speed",
"ui.card.fan.oscillate": "Oscillate",
"ui.card.fan.direction": "Direction",
"ui.card.light.brightness": "Brightness",
"ui.card.light.color_temperature": "Color temperature",
"ui.card.light.white_value": "White value",
"ui.card.light.effect": "Effect",
"ui.card.lock.code": "Code",
"ui.card.lock.lock": "Lock",
"ui.card.lock.unlock": "Unlock",
"ui.card.media_player.source": "Source",
"ui.card.media_player.sound_mode": "Sound mode",
"ui.card.media_player.text_to_speak": "Text to speak",
"ui.card.persistent_notification.dismiss": "Dismiss",
"ui.card.scene.activate": "Activate",
"ui.card.script.execute": "Execute",
"ui.card.weather.attributes.air_pressure": "Air pressure",
"ui.card.weather.attributes.humidity": "Humidity",
"ui.card.weather.attributes.temperature": "Temperature",
"ui.card.weather.attributes.visibility": "Visibility",
"ui.card.weather.attributes.wind_speed": "Wind speed",
"ui.card.weather.cardinal_direction.e": "E",
"ui.card.weather.cardinal_direction.ene": "ENE",
"ui.card.weather.cardinal_direction.ese": "ESE",
"ui.card.weather.cardinal_direction.n": "N",
"ui.card.weather.cardinal_direction.ne": "NE",
"ui.card.weather.cardinal_direction.nne": "NNE",
"ui.card.weather.cardinal_direction.nw": "NW",
"ui.card.weather.cardinal_direction.nnw": "NNW",
"ui.card.weather.cardinal_direction.s": "S",
"ui.card.weather.cardinal_direction.se": "SE",
"ui.card.weather.cardinal_direction.sse": "SSE",
"ui.card.weather.cardinal_direction.ssw": "SSW",
"ui.card.weather.cardinal_direction.sw": "SW",
"ui.card.weather.cardinal_direction.w": "W",
"ui.card.weather.cardinal_direction.wnw": "WNW",
"ui.card.weather.cardinal_direction.wsw": "WSW",
"ui.card.weather.forecast": "Forecast",
"ui.common.loading": "Loading",
"ui.common.cancel": "Cancel",
"ui.components.entity.entity-picker.entity": "Entity",
"ui.components.relative_time.past": "{time} ago",
"ui.components.relative_time.future": "In {time}",
"ui.components.relative_time.never": "Never",
"ui.components.relative_time.duration.second":
"{count} {count, plural,\n one {second}\n other {seconds}\n}",
"ui.components.relative_time.duration.minute":
"{count} {count, plural,\n one {minute}\n other {minutes}\n}",
"ui.components.relative_time.duration.hour":
"{count} {count, plural,\n one {hour}\n other {hours}\n}",
"ui.components.relative_time.duration.day":
"{count} {count, plural,\n one {day}\n other {days}\n}",
"ui.components.relative_time.duration.week":
"{count} {count, plural,\n one {week}\n other {weeks}\n}",
"ui.components.history_charts.loading_history": "Loading state history...",
"ui.components.history_charts.no_history_found": "No state history found.",
"ui.components.service-picker.service": "Service",
"ui.dialogs.more_info_settings.save": "Save",
"ui.dialogs.more_info_settings.name": "Name",
"ui.duration.second":
"{count} {count, plural,\n one {second}\n other {seconds}\n}",
"ui.duration.minute":
"{count} {count, plural,\n one {minute}\n other {minutes}\n}",
"ui.duration.hour":
"{count} {count, plural,\n one {hour}\n other {hours}\n}",
"ui.duration.day":
"{count} {count, plural,\n one {day}\n other {days}\n}",
"ui.duration.week":
"{count} {count, plural,\n one {week}\n other {weeks}\n}",
"ui.login-form.password": "Password",
"ui.login-form.remember": "Remember",
"ui.login-form.log_in": "Log in",
"ui.notification_toast.entity_turned_on": "Turned on {entity}.",
"ui.notification_toast.entity_turned_off": "Turned off {entity}.",
"ui.notification_toast.service_called": "Service {service} called.",
"ui.notification_toast.service_call_failed":
"Failed to call service {service}.",
"ui.notification_toast.connection_lost": "Connection lost. Reconnecting…",
"ui.sidebar.developer_tools": "Developer tools",
"ui.sidebar.log_out": "Log out",
"attribute.weather.humidity": "Humidity",
"attribute.weather.visibility": "Visibility",
"attribute.weather.wind_speed": "Wind speed",
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
const now = () => new Date().toISOString();
const randomTime = () =>
new Date(new Date().getTime() - (Math.random() * 80 * 60 * 1000)).toISOString();
new Date(new Date().getTime() - Math.random() * 80 * 60 * 1000).toISOString();
/* eslint-disable no-unused-vars */
@@ -18,19 +18,23 @@ export class Entity {
}
async handleService(domain, service, data) {
console.log(`Unmocked service for ${this.entityId}: ${domain}/${service}`, data);
console.log(
`Unmocked service for ${this.entityId}: ${domain}/${service}`,
data
);
}
update(state, attributes = {}) {
this.state = state;
this.lastUpdated = now();
this.lastChanged = state === this.state ? this.lastChanged : this.lastUpdated;
this.lastChanged =
state === this.state ? this.lastChanged : this.lastUpdated;
this.attributes = Object.assign({}, this.baseAttributes, attributes);
console.log('update', this.entityId, this);
console.log("update", this.entityId, this);
this.hass.updateStates({
[this.entityId]: this.toState()
[this.entityId]: this.toState(),
});
}
@@ -47,22 +51,25 @@ export class Entity {
export class LightEntity extends Entity {
async handleService(domain, service, data) {
if (!['homeassistant', this.domain].includes(domain)) return;
if (!["homeassistant", this.domain].includes(domain)) return;
if (service === 'turn_on') {
if (service === "turn_on") {
// eslint-disable-next-line
const { brightness, hs_color } = data;
this.update('on', Object.assign(this.attributes, {
brightness,
hs_color,
}));
} else if (service === 'turn_off') {
this.update('off');
} else if (service === 'toggle') {
if (this.state === 'on') {
this.handleService(domain, 'turn_off', data);
this.update(
"on",
Object.assign(this.attributes, {
brightness,
hs_color,
})
);
} else if (service === "turn_off") {
this.update("off");
} else if (service === "toggle") {
if (this.state === "on") {
this.handleService(domain, "turn_off", data);
} else {
this.handleService(domain, 'turn_on', data);
this.handleService(domain, "turn_on", data);
}
}
}
@@ -72,10 +79,10 @@ export class LockEntity extends Entity {
async handleService(domain, service, data) {
if (domain !== this.domain) return;
if (service === 'lock') {
this.update('locked');
} else if (service === 'unlock') {
this.update('unlocked');
if (service === "lock") {
this.update("locked");
} else if (service === "unlock") {
this.update("unlocked");
}
}
}
@@ -84,24 +91,26 @@ export class CoverEntity extends Entity {
async handleService(domain, service, data) {
if (domain !== this.domain) return;
if (service === 'open_cover') {
this.update('open');
} else if (service === 'close_cover') {
this.update('closing');
if (service === "open_cover") {
this.update("open");
} else if (service === "close_cover") {
this.update("closing");
}
}
}
export class GroupEntity extends Entity {
async handleService(domain, service, data) {
if (!['homeassistant', this.domain].includes(domain)) return;
if (!["homeassistant", this.domain].includes(domain)) return;
await Promise.all(this.attributes.entity_id.map((ent) => {
const entity = this.hass.mockEntities[ent];
return entity.handleService(entity.domain, service, data);
}));
await Promise.all(
this.attributes.entity_id.map((ent) => {
const entity = this.hass.mockEntities[ent];
return entity.handleService(entity.domain, service, data);
})
);
this.update(service === 'turn_on' ? 'on' : 'off');
this.update(service === "turn_on" ? "on" : "off");
}
}

View File

@@ -9,16 +9,18 @@ export default class FakeHass {
}
async callService(domain, service, serviceData) {
console.log('callService', { domain, service, serviceData });
console.log("callService", { domain, service, serviceData });
return Promise.resolve();
}
async callWS(msg) {
const callback = this._wsCommands[msg.type];
return callback ? callback(msg) : Promise.reject({
code: 'command_not_mocked',
message: 'This command is not implemented in the gallery.',
});
return callback
? callback(msg)
: Promise.reject({
code: "command_not_mocked",
message: "This command is not implemented in the gallery.",
});
}
async sendWS(msg) {
@@ -29,6 +31,6 @@ export default class FakeHass {
} else {
console.error(`Unknown command: ${msg.type}`);
}
console.log('sendWS', msg);
console.log("sendWS", msg);
}
}

View File

@@ -1,9 +1,9 @@
import fireEvent from '../../../src/common/dom/fire_event.js';
import { fireEvent } from "../../../src/common/dom/fire_event.js";
import demoConfig from './demo_config.js';
import demoResources from './demo_resources.js';
import demoConfig from "./demo_config.js";
import demoResources from "./demo_resources.js";
const ensureArray = val => (Array.isArray(val) ? val : [val]);
const ensureArray = (val) => (Array.isArray(val) ? val : [val]);
export default (elements, { initialStates = {} } = {}) => {
elements = ensureArray(elements);
@@ -14,13 +14,15 @@ export default (elements, { initialStates = {} } = {}) => {
function updateHass(obj) {
hass = Object.assign({}, hass, obj);
elements.forEach((el) => { el.hass = hass; });
elements.forEach((el) => {
el.hass = hass;
});
}
updateHass({
// Home Assistant properties
config: demoConfig,
language: 'en',
language: "en",
resources: demoResources,
states: initialStates,
@@ -29,21 +31,28 @@ export default (elements, { initialStates = {} } = {}) => {
// Home Assistant functions
async callService(domain, service, data) {
fireEvent(elements[0], 'show-notification', { message: `Called service ${domain}/${service}` });
fireEvent(elements[0], "show-notification", {
message: `Called service ${domain}/${service}`,
});
if (data.entity_id) {
await Promise.all(ensureArray(data.entity_id).map(ent =>
entities[ent].handleService(domain, service, data)));
await Promise.all(
ensureArray(data.entity_id).map((ent) =>
entities[ent].handleService(domain, service, data)
)
);
} else {
console.log('unmocked callService', domain, service, data);
console.log("unmocked callService", domain, service, data);
}
},
async callWS(msg) {
const callback = wsCommands[msg.type];
return callback ? callback(msg) : Promise.reject({
code: 'command_not_mocked',
message: 'This command is not implemented in the gallery.',
});
return callback
? callback(msg)
: Promise.reject({
code: "command_not_mocked",
message: "This command is not implemented in the gallery.",
});
},
async sendWS(msg) {
@@ -54,7 +63,7 @@ export default (elements, { initialStates = {} } = {}) => {
} else {
console.error(`Unknown command: ${msg.type}`);
}
console.log('sendWS', msg);
console.log("sendWS", msg);
},
// Mock functions
@@ -72,7 +81,7 @@ export default (elements, { initialStates = {} } = {}) => {
states[ent.entityId] = ent.toState();
});
this.updateStates(states);
}
},
});
return hass;

View File

@@ -0,0 +1,75 @@
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import "../components/demo-cards.js";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "alarm_armed", "armed_home", {
friendly_name: "Alarm",
}),
];
const CONFIGS = [
{
heading: "Basic Example",
config: `
- type: alarm-panel
entity: alarm_control_panel.alarm
`,
},
{
heading: "With Title",
config: `
- type: alarm-panel
entity: alarm_control_panel.alarm_armed
title: My Alarm
`,
},
{
heading: "Using only Arm_Home State",
config: `
- type: alarm-panel
entity: alarm_control_panel.alarm
states:
- arm_home
`,
},
{
heading: "Invalid Entity",
config: `
- type: alarm-panel
entity: alarm_control_panel.alarm1
`,
},
];
class DemoAlarmPanelEntity extends PolymerElement {
static get template() {
return html`
<demo-cards id='demos' hass='[[hass]]' configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-alarm-panel-card", DemoAlarmPanelEntity);

View File

@@ -1,28 +1,28 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import "../components/demo-cards.js";
const ENTITIES = [
getEntity('light', 'controller_1', 'on', {
friendly_name: 'Controller 1'
getEntity("light", "controller_1", "on", {
friendly_name: "Controller 1",
}),
getEntity('light', 'controller_2', 'on', {
friendly_name: 'Controller 2'
getEntity("light", "controller_2", "on", {
friendly_name: "Controller 2",
}),
getEntity('light', 'floor', 'off', {
friendly_name: 'Floor light'
getEntity("light", "floor", "off", {
friendly_name: "Floor light",
}),
getEntity('light', 'kitchen', 'on', {
friendly_name: 'Kitchen light'
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen light",
}),
];
const CONFIGS = [
{
heading: 'Controller',
heading: "Controller",
config: `
- type: entities
entities:
@@ -31,10 +31,10 @@ const CONFIGS = [
- type: divider
- light.floor
- light.kitchen
`
`,
},
{
heading: 'Demo',
heading: "Demo",
config: `
- type: conditional
conditions:
@@ -49,7 +49,7 @@ const CONFIGS = [
- light.controller_2
- light.floor
- light.kitchen
`
`,
},
];
@@ -68,7 +68,7 @@ class DemoConditional extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
value: CONFIGS,
},
hass: Object,
};
@@ -81,4 +81,4 @@ class DemoConditional extends PolymerElement {
}
}
customElements.define('demo-hui-conditional-card', DemoConditional);
customElements.define("demo-hui-conditional-card", DemoConditional);

View File

@@ -1,75 +1,70 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import "../components/demo-cards.js";
const ENTITIES = [
getEntity('light', 'bed_light', 'on', {
friendly_name: 'Bed Light'
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity('group', 'kitchen', 'on', {
entity_id: [
'light.bed_light',
],
getEntity("group", "kitchen", "on", {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: 'Kitchen'
friendly_name: "Kitchen",
}),
getEntity('lock', 'kitchen_door', 'locked', {
friendly_name: 'Kitchen Door'
getEntity("lock", "kitchen_door", "locked", {
friendly_name: "Kitchen Door",
}),
getEntity('cover', 'kitchen_window', 'open', {
friendly_name: 'Kitchen Window',
supported_features: 11
getEntity("cover", "kitchen_window", "open", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity('scene', 'romantic_lights', 'scening', {
entity_id: [
'light.bed_light',
'light.ceiling_lights'
],
friendly_name: 'Romantic lights'
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic lights",
}),
getEntity('device_tracker', 'demo_paulus', 'home', {
source_type: 'gps',
getEntity("device_tracker", "demo_paulus", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: 'Paulus'
friendly_name: "Paulus",
}),
getEntity('climate', 'ecobee', 'auto', {
getEntity("climate", "ecobee", "auto", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: 'Auto Low',
fan_list: ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off'],
operation_mode: 'auto',
operation_list: ['heat', 'cool', 'auto', 'off'],
hold_mode: 'home',
swing_mode: 'Auto',
swing_list: ['Auto', '1', '2', '3', 'Off'],
unit_of_measurement: '°F',
friendly_name: 'Ecobee',
supported_features: 1014
fan_mode: "Auto Low",
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
operation_mode: "auto",
operation_list: ["heat", "cool", "auto", "off"],
hold_mode: "home",
swing_mode: "Auto",
swing_list: ["Auto", "1", "2", "3", "Off"],
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
}),
getEntity('input_number', 'noise_allowance', 5, {
getEntity("input_number", "noise_allowance", 5, {
min: 0,
max: 10,
step: 1,
mode: 'slider',
unit_of_measurement: 'dB',
friendly_name: 'Allowed Noise',
icon: 'mdi:bell-ring'
})
mode: "slider",
unit_of_measurement: "dB",
friendly_name: "Allowed Noise",
icon: "mdi:bell-ring",
}),
];
const CONFIGS = [
{
heading: 'Basic',
heading: "Basic",
config: `
- type: entities
entities:
@@ -82,10 +77,10 @@ const CONFIGS = [
- light.non_existing
- climate.ecobee
- input_number.noise_allowance
`
`,
},
{
heading: 'With title, toggle-able',
heading: "With title, toggle-able",
config: `
- type: entities
entities:
@@ -98,10 +93,10 @@ const CONFIGS = [
- climate.ecobee
- input_number.noise_allowance
title: Random group
`
`,
},
{
heading: 'With title, toggle = false',
heading: "With title, toggle = false",
config: `
- type: entities
entities:
@@ -115,19 +110,19 @@ const CONFIGS = [
- input_number.noise_allowance
title: Random group
show_header_toggle: false
`
`,
},
{
heading: 'With title, can\'t toggle',
heading: "With title, can't toggle",
config: `
- type: entities
entities:
- device_tracker.demo_paulus
title: Random group
`
`,
},
{
heading: 'Custom name, secondary info, custom icon',
heading: "Custom name, secondary info, custom icon",
config: `
- type: entities
entities:
@@ -147,17 +142,13 @@ const CONFIGS = [
- input_number.noise_allowance
title: Random group
show_header_toggle: false
`
`,
},
{
heading: 'Special rows',
heading: "Special rows",
config: `
- type: entities
entities:
- type: weblink
url: http://google.com/
icon: mdi:google
name: Google
- type: call-service
icon: mdi:power
name: Bed light
@@ -165,13 +156,19 @@ const CONFIGS = [
service: light.toggle
service_data:
entity_id: light.bed_light
- type: section
label: Links
- type: weblink
url: http://google.com/
icon: mdi:google
name: Google
- type: divider
- type: divider
style:
height: 30px
margin: 4px 0
background: center / contain url("/images/divider.png") no-repeat
`
`,
},
];
@@ -190,7 +187,7 @@ class DemoEntities extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
value: CONFIGS,
},
hass: Object,
};
@@ -203,4 +200,4 @@ class DemoEntities extends PolymerElement {
}
}
customElements.define('demo-hui-entities-card', DemoEntities);
customElements.define("demo-hui-entities-card", DemoEntities);

View File

@@ -0,0 +1,95 @@
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import "../components/demo-cards.js";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
];
const CONFIGS = [
{
heading: "Basic example",
config: `
- type: entity-button
entity: light.bed_light
`,
},
{
heading: "With Name",
config: `
- type: entity-button
name: Bedroom
entity: light.bed_light
`,
},
{
heading: "With Icon",
config: `
- type: entity-button
entity: light.bed_light
icon: mdi:hotel
`,
},
{
heading: "Without State",
config: `
- type: entity-button
entity: light.bed_light
show_state: false
`,
},
{
heading: "Custom Tap Action (toggle)",
config: `
- type: entity-button
entity: light.bed_light
tap_action: toggle
`,
},
{
heading: "Running Service",
config: `
- type: entity-button
entity: light.bed_light
service: light.toggle
`,
},
{
heading: "Invalid Entity",
config: `
- type: entity-button
entity: sensor.invalid_entity
`,
},
];
class DemoEntityButtonEntity extends PolymerElement {
static get template() {
return html`
<demo-cards id='demos' hass='[[hass]]' configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-entity-button-card", DemoEntityButtonEntity);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'Basic',
heading: "Basic",
config: `
- type: entity-filter
entities:
@@ -18,10 +18,10 @@ const CONFIGS = [
state_filter:
- "on"
- not_home
`
`,
},
{
heading: 'With card config',
heading: "With card config",
config: `
- type: entity-filter
entities:
@@ -37,10 +37,10 @@ const CONFIGS = [
card:
type: glance
show_state: false
`
`,
},
{
heading: 'Showing single entity conditionally',
heading: "Showing single entity conditionally",
config: `
- type: entity-filter
entities:
@@ -50,8 +50,8 @@ const CONFIGS = [
card:
type: media-control
entity: media_player.lounge_room
`
}
`,
},
];
class DemoFilter extends PolymerElement {
@@ -65,10 +65,10 @@ class DemoFilter extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-entity-filter-card', DemoFilter);
customElements.define("demo-hui-entity-filter-card", DemoFilter);

View File

@@ -0,0 +1,83 @@
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: "Basic example",
config: `
- type: gauge
entity: sensor.brightness
`,
},
{
heading: "With title",
config: `
- type: gauge
title: Humidity
entity: sensor.outside_humidity
`,
},
{
heading: "Custom Unit of Measurement",
config: `
- type: gauge
entity: sensor.outside_temperature
unit_of_measurement: C
`,
},
{
heading: "Setting Severity Levels",
config: `
- type: gauge
entity: sensor.brightness
severity:
red: 32
green: 0
yellow: 23
`,
},
{
heading: "Setting Min and Max Values",
config: `
- type: gauge
entity: sensor.brightness
min: 0
max: 38
`,
},
{
heading: "Invalid Entity",
config: `
- type: gauge
entity: sensor.invalid_entity
`,
},
{
heading: "Non-Numeric Value",
config: `
- type: gauge
entity: plant.bonsai
`,
},
];
class DemoGaugeEntity extends PolymerElement {
static get template() {
return html`
<demo-cards configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
}
customElements.define("demo-hui-gauge-card", DemoGaugeEntity);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'Basic example',
heading: "Basic example",
config: `
- type: glance
entities:
@@ -16,10 +16,10 @@ const CONFIGS = [
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'With title',
heading: "With title",
config: `
- type: glance
title: This is glance
@@ -31,10 +31,10 @@ const CONFIGS = [
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'Custom column width',
heading: "Custom column width",
config: `
- type: glance
column_width: calc(100% / 7)
@@ -46,10 +46,10 @@ const CONFIGS = [
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'No name',
heading: "No name",
config: `
- type: glance
show_name: false
@@ -61,10 +61,10 @@ const CONFIGS = [
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'No state',
heading: "No state",
config: `
- type: glance
show_state: false
@@ -76,10 +76,10 @@ const CONFIGS = [
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'No name and no state',
heading: "No name and no state",
config: `
- type: glance
show_name: false
@@ -92,10 +92,10 @@ const CONFIGS = [
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'Custom name, custom icon',
heading: "Custom name, custom icon",
config: `
- type: glance
entities:
@@ -109,10 +109,10 @@ const CONFIGS = [
icon: mdi:alarm-light
- lock.kitchen_door
- light.ceiling_lights
`
`,
},
{
heading: 'Custom tap action',
heading: "Custom tap action",
config: `
- type: glance
entities:
@@ -126,10 +126,10 @@ const CONFIGS = [
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
`
`,
},
{
heading: 'Selectively hidden name',
heading: "Selectively hidden name",
config: `
- type: glance
entities:
@@ -140,7 +140,22 @@ const CONFIGS = [
- entity: cover.kitchen_window
name:
- light.kitchen_lights
`
`,
},
{
heading: "Primary theme",
config: `
- type: glance
theming: primary
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
];
@@ -155,10 +170,10 @@ class DemoPicEntity extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-glance-card', DemoPicEntity);
customElements.define("demo-hui-glance-card", DemoPicEntity);

View File

@@ -1,39 +1,39 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'Without title',
heading: "Without title",
config: `
- type: iframe
url: https://embed.windy.com/embed2.html
`
`,
},
{
heading: 'With title',
heading: "With title",
config: `
- type: iframe
url: https://embed.windy.com/embed2.html
title: Weather radar
`
`,
},
{
heading: 'Height-Width 3:4',
heading: "Height-Width 3:4",
config: `
- type: iframe
url: https://embed.windy.com/embed2.html
aspect_ratio: 75%
`
`,
},
{
heading: 'Height-Width 1:1',
heading: "Height-Width 1:1",
config: `
- type: iframe
url: https://embed.windy.com/embed2.html
aspect_ratio: 100%
`
`,
},
];
@@ -48,10 +48,10 @@ class DemoIframe extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-iframe-card', DemoIframe);
customElements.define("demo-hui-iframe-card", DemoIframe);

View File

@@ -1,120 +1,120 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import "../components/demo-cards.js";
const ENTITIES = [
getEntity('device_tracker', 'demo_paulus', 'not_home', {
source_type: 'gps',
getEntity("device_tracker", "demo_paulus", "not_home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: 'Paulus'
friendly_name: "Paulus",
}),
getEntity('device_tracker', 'demo_home_boy', 'home', {
source_type: 'gps',
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.87334,
longitude: 117.22745,
gps_accuracy: 20,
battery: 53,
friendly_name: 'Home Boy'
friendly_name: "Home Boy",
}),
getEntity('zone', 'home', 'zoning', {
getEntity("zone", "home", "zoning", {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: 'Home',
icon: 'mdi:home'
})
friendly_name: "Home",
icon: "mdi:home",
}),
];
const CONFIGS = [
{
heading: 'Without title',
heading: "Without title",
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- device_tracker.demo_home_boy
- zone.home
`
`,
},
{
heading: 'With title',
heading: "With title",
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- zone.home
title: Where is Paulus?
`
`,
},
{
heading: 'Height-Width 1:2',
heading: "Height-Width 1:2",
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- zone.home
aspect_ratio: 50%
`
`,
},
{
heading: 'Default Zoom',
heading: "Default Zoom",
config: `
- type: map
default_zoom: 12
entities:
- entity: device_tracker.demo_paulus
- zone.home
`
`,
},
{
heading: 'Default Zoom too High',
heading: "Default Zoom too High",
config: `
- type: map
default_zoom: 20
entities:
- entity: device_tracker.demo_paulus
- zone.home
`
`,
},
{
heading: 'Single Marker',
heading: "Single Marker",
config: `
- type: map
entities:
- device_tracker.demo_paulus
`
`,
},
{
heading: 'Single Marker Default Zoom',
heading: "Single Marker Default Zoom",
config: `
- type: map
default_zoom: 8
entities:
- device_tracker.demo_paulus
`
`,
},
{
heading: 'No Entities',
heading: "No Entities",
config: `
- type: map
entities:
- light.bed_light
`
`,
},
{
heading: 'No Entities, Default Zoom',
heading: "No Entities, Default Zoom",
config: `
- type: map
default_zoom: 8
entities:
- light.bed_light
`
`,
},
];
@@ -133,9 +133,9 @@ class DemoMap extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
value: CONFIGS,
},
hass: Object
hass: Object,
};
}
@@ -146,4 +146,4 @@ class DemoMap extends PolymerElement {
}
}
customElements.define('demo-hui-map-card', DemoMap);
customElements.define("demo-hui-map-card", DemoMap);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'markdown-it demo',
heading: "markdown-it demo",
config: `
- type: markdown
content: >
@@ -248,7 +248,7 @@ const CONFIGS = [
::: warning
*here be dragons*
:::
`
`,
},
];
@@ -263,10 +263,10 @@ class DemoMarkdown extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-markdown-card', DemoMarkdown);
customElements.define("demo-hui-markdown-card", DemoMarkdown);

View File

@@ -0,0 +1,105 @@
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import "../components/demo-cards.js";
const ENTITIES = [
getEntity("media_player", "bedroom", "playing", {
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
supported_features: 32,
}),
getEntity("media_player", "family_room", "paused", {
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
supported_features: 16417,
}),
getEntity("media_player", "family_room_no_play", "paused", {
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
supported_features: 33,
}),
getEntity("media_player", "living_room", "playing", {
media_content_type: "tvshow",
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
supported_features: 1,
}),
getEntity("media_player", "lounge_room", "idle", {
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
supported_features: 1,
}),
getEntity("media_player", "theater", "off", {
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
supported_features: 33,
}),
getEntity("media_player", "android_cast", "playing", {
media_title: "Android Screen Casting",
app_name: "Screen Mirroring",
supported_features: 21437,
}),
];
const CONFIGS = [
{
heading: "Media Players",
config: `
- type: entities
entities:
- entity: media_player.bedroom
name: Skip, no pause
- entity: media_player.family_room
name: Paused, music
- entity: media_player.family_room_no_play
name: Paused, no play
- entity: media_player.living_room
name: Pause, No skip, tvshow
- entity: media_player.android_cast
name: Screen casting
- entity: media_player.lounge_room
name: Chromcast Idle
- entity: media_player.theater
name: 'Player Off'
`,
},
];
class DemoHuiMediaPlayerRows extends PolymerElement {
static get template() {
return html`
<demo-cards
id='demos'
hass='[[hass]]'
configs="[[_configs]]"
></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
hass: Object,
};
}
ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'Card with few elements',
heading: "Card with few elements",
config: `
- type: picture-elements
image: /images/floorplan.png
@@ -49,7 +49,7 @@ const CONFIGS = [
style:
top: 8%
left: 35%
`
`,
},
];
@@ -64,10 +64,10 @@ class DemoPicElements extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-picture-elements-card', DemoPicElements);
customElements.define("demo-hui-picture-elements-card", DemoPicElements);

View File

@@ -1,67 +1,67 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'State on',
heading: "State on",
config: `
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
`
`,
},
{
heading: 'State off',
heading: "State off",
config: `
- type: picture-entity
image: /images/bed.png
entity: light.bed_light
`
`,
},
{
heading: 'Entity unavailable',
heading: "Entity unavailable",
config: `
- type: picture-entity
image: /images/living_room.png
entity: light.non_existing
`
`,
},
{
heading: 'Camera entity',
heading: "Camera entity",
config: `
- type: picture-entity
entity: camera.demo_camera
`
`,
},
{
heading: 'Hidden name',
heading: "Hidden name",
config: `
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
show_name: false
`
`,
},
{
heading: 'Hidden state',
heading: "Hidden state",
config: `
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
show_state: false
`
`,
},
{
heading: 'Both hidden',
heading: "Both hidden",
config: `
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
show_name: false
show_state: false
`
`,
},
];
@@ -76,10 +76,10 @@ class DemoPicEntity extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-picture-entity-card', DemoPicEntity);
customElements.define("demo-hui-picture-entity-card", DemoPicEntity);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'Title, dialog, toggle',
heading: "Title, dialog, toggle",
config: `
- type: picture-glance
image: /images/living_room.png
@@ -15,10 +15,10 @@ const CONFIGS = [
- light.ceiling_lights
- binary_sensor.movement_backyard
- binary_sensor.basement_floor_wet
`
`,
},
{
heading: 'Title, dialog, no toggle',
heading: "Title, dialog, no toggle",
config: `
- type: picture-glance
image: /images/living_room.png
@@ -26,10 +26,10 @@ const CONFIGS = [
entities:
- binary_sensor.movement_backyard
- binary_sensor.basement_floor_wet
`
`,
},
{
heading: 'Title, no dialog, toggle',
heading: "Title, no dialog, toggle",
config: `
- type: picture-glance
image: /images/living_room.png
@@ -37,10 +37,10 @@ const CONFIGS = [
entities:
- switch.decorative_lights
- light.ceiling_lights
`
`,
},
{
heading: 'No title, dialog, toggle',
heading: "No title, dialog, toggle",
config: `
- type: picture-glance
image: /images/living_room.png
@@ -49,30 +49,30 @@ const CONFIGS = [
- light.ceiling_lights
- binary_sensor.movement_backyard
- binary_sensor.basement_floor_wet
`
`,
},
{
heading: 'No title, dialog, no toggle',
heading: "No title, dialog, no toggle",
config: `
- type: picture-glance
image: /images/living_room.png
entities:
- binary_sensor.movement_backyard
- binary_sensor.basement_floor_wet
`
`,
},
{
heading: 'No title, no dialog, toggle',
heading: "No title, no dialog, toggle",
config: `
- type: picture-glance
image: /images/living_room.png
entities:
- switch.decorative_lights
- light.ceiling_lights
`
`,
},
{
heading: 'Custom icon',
heading: "Custom icon",
config: `
- type: picture-glance
image: /images/living_room.png
@@ -81,7 +81,7 @@ const CONFIGS = [
- entity: switch.decorative_lights
icon: mdi:power
- binary_sensor.basement_floor_wet
`
`,
},
];
@@ -96,10 +96,10 @@ class DemoPicGlance extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-picture-glance-card', DemoPicGlance);
customElements.define("demo-hui-picture-glance-card", DemoPicGlance);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/demo-cards.js';
import "../components/demo-cards.js";
const CONFIGS = [
{
heading: 'Vertical Stack',
heading: "Vertical Stack",
config: `
- type: vertical-stack
cards:
@@ -17,10 +17,10 @@ const CONFIGS = [
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
`
`,
},
{
heading: 'Horizontal Stack',
heading: "Horizontal Stack",
config: `
- type: horizontal-stack
cards:
@@ -32,10 +32,10 @@ const CONFIGS = [
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
`
`,
},
{
heading: 'Combination of both',
heading: "Combination of both",
config: `
- type: vertical-stack
cards:
@@ -52,7 +52,7 @@ const CONFIGS = [
- type: picture-entity
image: /images/bed.png
entity: light.bed_light
`
`,
},
];
@@ -67,10 +67,10 @@ class DemoStack extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
}
value: CONFIGS,
},
};
}
}
customElements.define('demo-hui-stack-card', DemoStack);
customElements.define("demo-hui-stack-card", DemoStack);

View File

@@ -1,13 +1,13 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/dialogs/more-info/controls/more-info-content.js';
import '../../../src/components/ha-card.js';
import "../../../src/dialogs/more-info/controls/more-info-content.js";
import "../../../src/components/ha-card.js";
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import getEntity from "../data/entity.js";
import provideHass from "../data/provide_hass.js";
import '../components/demo-more-infos.js';
import "../components/demo-more-infos.js";
/* eslint-disable no-unused-vars */
@@ -20,17 +20,16 @@ const SUPPORT_TRANSITION = 32;
const SUPPORT_WHITE_VALUE = 128;
const ENTITIES = [
getEntity('light', 'bed_light', 'on', {
friendly_name: 'Basic Light'
getEntity("light", "bed_light", "on", {
friendly_name: "Basic Light",
}),
getEntity('light', 'kitchen_light', 'on', {
friendly_name: 'Brightness Light',
getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light",
brightness: 80,
supported_features: SUPPORT_BRIGHTNESS,
}),
];
class DemoMoreInfoLight extends PolymerElement {
static get template() {
return html`
@@ -45,7 +44,7 @@ class DemoMoreInfoLight extends PolymerElement {
return {
_entities: {
type: Array,
value: ENTITIES.map(ent => ent.entityId),
value: ENTITIES.map((ent) => ent.entityId),
},
};
}
@@ -57,4 +56,4 @@ class DemoMoreInfoLight extends PolymerElement {
}
}
customElements.define('demo-more-info-light', DemoMoreInfoLight);
customElements.define("demo-more-info-light", DemoMoreInfoLight);

View File

@@ -1,12 +1,12 @@
import '@polymer/paper-styles/typography.js';
import '@polymer/polymer/lib/elements/dom-if.js';
import '@polymer/polymer/lib/elements/dom-repeat.js';
import "@polymer/paper-styles/typography.js";
import "@polymer/polymer/lib/elements/dom-if.js";
import "@polymer/polymer/lib/elements/dom-repeat.js";
import '../../src/resources/hass-icons.js';
import '../../src/resources/ha-style.js';
import '../../src/resources/roboto.js';
import '../../src/components/ha-iconset-svg.js';
import "../../src/resources/hass-icons.js";
import "../../src/resources/ha-style.js";
import "../../src/resources/roboto.js";
import "../../src/components/ha-iconset-svg.js";
import './ha-gallery.js';
import "./ha-gallery.js";
document.body.appendChild(document.createElement('ha-gallery'));
document.body.appendChild(document.createElement("ha-gallery"));

View File

@@ -1,19 +1,19 @@
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/iron-icon/iron-icon.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-item/paper-item.js';
import '@polymer/paper-item/paper-item-body.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/app-layout/app-header-layout/app-header-layout.js";
import "@polymer/app-layout/app-header/app-header.js";
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/iron-icon/iron-icon.js";
import "@polymer/paper-card/paper-card.js";
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-item/paper-item-body.js";
import "@polymer/paper-icon-button/paper-icon-button.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../src/managers/notification-manager.js';
import "../../src/managers/notification-manager.js";
const DEMOS = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
const DEMOS = require.context("./demos", true, /^(.*\.(js$))[^.]*$/im);
const fixPath = path => path.substr(2, path.length - 5);
const fixPath = (path) => path.substr(2, path.length - 5);
class HaGallery extends PolymerElement {
static get template() {
@@ -131,19 +131,19 @@ class HaGallery extends PolymerElement {
_demo: {
type: String,
value: document.location.hash.substr(1),
observer: '_demoChanged',
observer: "_demoChanged",
},
_demos: {
type: Array,
value: DEMOS.keys().map(fixPath)
value: DEMOS.keys().map(fixPath),
},
_lovelaceDemos: {
type: Array,
computed: '_computeLovelace(_demos)',
computed: "_computeLovelace(_demos)",
},
_moreInfoDemos: {
type: Array,
computed: '_computeMoreInfos(_demos)',
computed: "_computeMoreInfos(_demos)",
},
};
}
@@ -151,18 +151,21 @@ class HaGallery extends PolymerElement {
ready() {
super.ready();
this.addEventListener(
'show-notification',
ev => this.$.notifications.showNotification(ev.detail.message)
this.addEventListener("show-notification", (ev) =>
this.$.notifications.showNotification(ev.detail.message)
);
this.addEventListener('hass-more-info', (ev) => {
this.addEventListener("hass-more-info", (ev) => {
if (ev.detail.entityId) {
this.$.notifications.showNotification(`Showing more info for ${ev.detail.entityId}`);
this.$.notifications.showNotification(
`Showing more info for ${ev.detail.entityId}`
);
}
});
window.addEventListener('hashchange', () => { this._demo = document.location.hash.substr(1); });
window.addEventListener("hashchange", () => {
this._demo = document.location.hash.substr(1);
});
}
_withDefault(value, def) {
@@ -182,20 +185,20 @@ class HaGallery extends PolymerElement {
}
_computeHeaderButtonClass(demo) {
return demo ? '' : 'invisible';
return demo ? "" : "invisible";
}
_backTapped() {
document.location.hash = '';
document.location.hash = "";
}
_computeLovelace(demos) {
return demos.filter(demo => demo.includes('hui'));
return demos.filter((demo) => demo.includes("hui"));
}
_computeMoreInfos(demos) {
return demos.filter(demo => demo.includes('more-info'));
return demos.filter((demo) => demo.includes("more-info"));
}
}
customElements.define('ha-gallery', HaGallery);
customElements.define("ha-gallery", HaGallery);

View File

@@ -1,76 +1,71 @@
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require("path");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { babelLoaderConfig } = require("../config/babel.js");
const isProd = process.env.NODE_ENV === 'production';
const chunkFilename = isProd ?
'chunk.[chunkhash].js' : '[name].chunk.js';
const buildPath = path.resolve(__dirname, 'dist');
const publicPath = isProd ? './' : 'http://localhost:8080/';
const isProd = process.env.NODE_ENV === "production";
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
const buildPath = path.resolve(__dirname, "dist");
const publicPath = isProd ? "./" : "http://localhost:8080/";
module.exports = {
mode: isProd ? 'production' : 'development',
mode: isProd ? "production" : "development",
// Disabled in prod while we make Home Assistant able to serve the right files.
// Was source-map
devtool: isProd ? 'none' : 'inline-source-map',
entry: './src/entrypoint.js',
devtool: isProd ? "none" : "inline-source-map",
entry: "./src/entrypoint.js",
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
plugins: [
// Only support the syntax, Webpack will handle it.
'syntax-dynamic-import',
[
'transform-react-jsx',
{
pragma: 'h'
}
],
],
},
},
},
babelLoaderConfig({ latestBuild: true }),
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
loader: "html-loader",
options: {
exportAsEs6Default: true,
}
}
},
},
},
]
],
},
plugins: [
new CopyWebpackPlugin([
'public',
{ from: '../public', to: 'static' },
{ from: '../build-translations/output', to: 'static/translations' },
{ from: '../node_modules/leaflet/dist/leaflet.css', to: 'static/images/leaflet/' },
{ from: '../node_modules/@polymer/font-roboto-local/fonts', to: 'static/fonts' },
{ from: '../node_modules/leaflet/dist/images', to: 'static/images/leaflet/' },
"public",
{ from: "../public", to: "static" },
{ from: "../build-translations/output", to: "static/translations" },
{
from: "../node_modules/leaflet/dist/leaflet.css",
to: "static/images/leaflet/",
},
{
from: "../node_modules/@polymer/font-roboto-local/fonts",
to: "static/fonts",
},
{
from: "../node_modules/leaflet/dist/images",
to: "static/images/leaflet/",
},
]),
isProd && new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
}
}),
isProd &&
new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
},
}),
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json"],
},
output: {
filename: '[name].js',
filename: "[name].js",
chunkFilename: chunkFilename,
path: buildPath,
publicPath,
},
devServer: {
contentBase: './public',
}
contentBase: "./public",
},
};

View File

@@ -1,8 +1,8 @@
var path = require('path');
var path = require("path");
module.exports = {
polymer_dir: path.resolve(__dirname, '..'),
build_dir: path.resolve(__dirname, '../build'),
output: path.resolve(__dirname, '../hass_frontend'),
output_es5: path.resolve(__dirname, '../hass_frontend_es5'),
polymer_dir: path.resolve(__dirname, ".."),
build_dir: path.resolve(__dirname, "../build"),
output: path.resolve(__dirname, "../hass_frontend"),
output_es5: path.resolve(__dirname, "../hass_frontend_es5"),
};

View File

@@ -1,31 +1,34 @@
const gulp = require('gulp');
const path = require('path');
const fs = require('fs');
const config = require('../config');
const gulp = require("gulp");
const path = require("path");
const fs = require("fs");
const config = require("../config");
const ICON_PACKAGE_PATH = path.resolve(__dirname, '../../node_modules/@mdi/svg/');
const META_PATH = path.resolve(ICON_PACKAGE_PATH, 'meta.json');
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, 'svg');
const OUTPUT_DIR = path.resolve(__dirname, '../../build');
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, 'mdi.html');
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, 'hass-icons.html');
const ICON_PACKAGE_PATH = path.resolve(
__dirname,
"../../node_modules/@mdi/svg/"
);
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
const OUTPUT_DIR = path.resolve(__dirname, "../../build");
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "mdi.html");
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "hass-icons.html");
const BUILT_IN_PANEL_ICONS = [
'calendar', // Calendar
'settings', // Config
'home-assistant', // Hass.io
'poll-box', // History panel
'format-list-bulleted-type', // Logbook
'mailbox', // Mailbox
'account-location', // Map
'cart', // Shopping List
"calendar", // Calendar
"settings", // Config
"home-assistant", // Hass.io
"poll-box", // History panel
"format-list-bulleted-type", // Logbook
"mailbox", // Mailbox
"account-location", // Map
"cart", // Shopping List
];
// Given an icon name, load the SVG file
function loadIcon(name) {
const iconPath = path.resolve(ICON_PATH, `${name}.svg`);
try {
return fs.readFileSync(iconPath, 'utf-8');
return fs.readFileSync(iconPath, "utf-8");
} catch (err) {
return null;
}
@@ -33,7 +36,7 @@ function loadIcon(name) {
// Given an SVG file, convert it to an iron-iconset-svg definition
function transformXMLtoPolymer(name, xml) {
const start = xml.indexOf('><path') + 1;
const start = xml.indexOf("><path") + 1;
const end = xml.length - start - 6;
const path = xml.substr(start, end);
return `<g id="${name}">${path}</g>`;
@@ -41,22 +44,26 @@ function transformXMLtoPolymer(name, xml) {
// Given an iconset name and icon names, generate a polymer iconset
function generateIconset(name, iconNames) {
const iconDefs = iconNames.map(name => {
const iconDef = loadIcon(name);
if (!iconDef) {
throw new Error(`Unknown icon referenced: ${name}`);
}
return transformXMLtoPolymer(name, iconDef)
}).join('');
const iconDefs = iconNames
.map((name) => {
const iconDef = loadIcon(name);
if (!iconDef) {
throw new Error(`Unknown icon referenced: ${name}`);
}
return transformXMLtoPolymer(name, iconDef);
})
.join("");
return `<ha-iconset-svg name="${name}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
}
// Generate the full MDI iconset
function genMDIIcons() {
const meta = JSON.parse(fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), 'UTF-8'));
const iconNames = meta.map(iconInfo => iconInfo.name);
const meta = JSON.parse(
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
);
const iconNames = meta.map((iconInfo) => iconInfo.name);
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset('mdi', iconNames));
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
}
// Helper function to map recursively over files in a folder and it's subfolders
@@ -75,30 +82,31 @@ function mapFiles(startPath, filter, mapFunc) {
// Find all icons used by the project.
function findIcons(path, iconsetName) {
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, 'g');
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, "g");
const icons = new Set();
function processFile(filename) {
const content = fs.readFileSync(filename);
let match;
// eslint-disable-next-line
while (match = iconRegex.exec(content)) {
while ((match = iconRegex.exec(content))) {
// strip off "hass:" and add to set
icons.add(match[0].substr(iconsetName.length + 1));
}
}
mapFiles(path, '.js', processFile);
mapFiles(path, ".js", processFile);
mapFiles(path, ".ts", processFile);
return Array.from(icons);
}
function genHassIcons() {
const iconNames = findIcons('./src', 'hass').concat(BUILT_IN_PANEL_ICONS);
const iconNames = findIcons("./src", "hass").concat(BUILT_IN_PANEL_ICONS);
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset('hass', iconNames));
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
}
gulp.task('gen-icons-mdi', () => genMDIIcons());
gulp.task('gen-icons-hass', () => genHassIcons());
gulp.task('gen-icons', ['gen-icons-hass', 'gen-icons-mdi'], () => {});
gulp.task("gen-icons-mdi", () => genMDIIcons());
gulp.task("gen-icons-hass", () => genHassIcons());
gulp.task("gen-icons", ["gen-icons-hass", "gen-icons-mdi"], () => {});
module.exports = {
findIcons,

View File

@@ -1,37 +1,44 @@
const path = require('path');
const gulp = require('gulp');
const foreach = require('gulp-foreach');
const hash = require('gulp-hash');
const insert = require('gulp-insert');
const merge = require('gulp-merge-json');
const minify = require('gulp-jsonminify');
const rename = require('gulp-rename');
const transform = require('gulp-json-transform');
const path = require("path");
const gulp = require("gulp");
const foreach = require("gulp-foreach");
const hash = require("gulp-hash");
const insert = require("gulp-insert");
const merge = require("gulp-merge-json");
const minify = require("gulp-jsonminify");
const rename = require("gulp-rename");
const transform = require("gulp-json-transform");
const inDir = 'translations';
const workDir = 'build-translations';
const fullDir = workDir + '/full';
const coreDir = workDir + '/core';
const outDir = workDir + '/output';
const inDir = "translations";
const workDir = "build-translations";
const fullDir = workDir + "/full";
const coreDir = workDir + "/core";
const outDir = workDir + "/output";
// Panel translations which should be split from the core translations. These
// should mirror the fragment definitions in polymer.json, so that we load
// additional resources at equivalent points.
const TRANSLATION_FRAGMENTS = [
'config',
'history',
'logbook',
'mailbox',
'shopping-list',
"config",
"history",
"logbook",
"mailbox",
"profile",
"shopping-list",
"page-authorize",
"page-onboarding",
];
const tasks = [];
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach(function (key) {
if (typeof (data[key]) === 'object') {
output = Object.assign({}, output, recursiveFlatten(prefix + key + '.', data[key]));
Object.keys(data).forEach(function(key) {
if (typeof data[key] === "object") {
output = Object.assign(
{},
output,
recursiveFlatten(prefix + key + ".", data[key])
);
} else {
output[prefix + key] = data[key];
}
@@ -40,14 +47,14 @@ function recursiveFlatten(prefix, data) {
}
function flatten(data) {
return recursiveFlatten('', data);
return recursiveFlatten("", data);
}
function emptyFilter(data) {
const newData = {};
Object.keys(data).forEach((key) => {
if (data[key]) {
if (typeof (data[key]) === 'object') {
if (typeof data[key] === "object") {
newData[key] = emptyFilter(data[key]);
} else {
newData[key] = data[key];
@@ -67,16 +74,18 @@ function emptyFilter(data) {
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
*/
const re_key_reference = /\[%key:([^%]+)%\]/;
function lokalise_transform (data, original) {
function lokalise_transform(data, original) {
const output = {};
Object.entries(data).forEach(([key, value]) => {
if (value instanceof Object) {
output[key] = lokalise_transform(value, original);
} else {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split('::').reduce((tr, k) => tr[k], original);
if (typeof replace !== 'string') {
throw Error(`Invalid key placeholder ${key} in src/translations/en.json`);
const replace = key.split("::").reduce((tr, k) => tr[k], original);
if (typeof replace !== "string") {
throw Error(
`Invalid key placeholder ${key} in src/translations/en.json`
);
}
return replace;
});
@@ -94,21 +103,24 @@ function lokalise_transform (data, original) {
* project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately.
*/
let taskName = 'build-master-translation';
gulp.task(taskName, function () {
return gulp.src('src/translations/en.json')
.pipe(transform(function(data, file) {
return lokalise_transform(data, data);
}))
.pipe(rename('translationMaster.json'))
let taskName = "build-master-translation";
gulp.task(taskName, function() {
return gulp
.src("src/translations/en.json")
.pipe(
transform(function(data, file) {
return lokalise_transform(data, data);
})
)
.pipe(rename("translationMaster.json"))
.pipe(gulp.dest(workDir));
});
tasks.push(taskName);
taskName = 'build-merged-translations';
gulp.task(taskName, ['build-master-translation'], function () {
return gulp.src(inDir + '/*.json')
.pipe(foreach(function(stream, file) {
taskName = "build-merged-translations";
gulp.task(taskName, ["build-master-translation"], function() {
return gulp.src(inDir + "/*.json").pipe(
foreach(function(stream, file) {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
@@ -116,139 +128,169 @@ gulp.task(taskName, ['build-master-translation'], function () {
// TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated
// than a base translation + region.
const tr = path.basename(file.history[0], '.json');
const subtags = tr.split('-');
const src = [workDir + '/translationMaster.json'];
const tr = path.basename(file.history[0], ".json");
const subtags = tr.split("-");
const src = [workDir + "/translationMaster.json"];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join('-');
src.push(inDir + '/' + lang + '.json');
const lang = subtags.slice(0, i).join("-");
src.push(inDir + "/" + lang + ".json");
}
return gulp.src(src)
.pipe(transform(data => emptyFilter(data)))
.pipe(merge({
fileName: tr + '.json',
}))
return gulp
.src(src)
.pipe(transform((data) => emptyFilter(data)))
.pipe(
merge({
fileName: tr + ".json",
})
)
.pipe(gulp.dest(fullDir));
}));
})
);
});
tasks.push(taskName);
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = 'build-translation-fragment-' + fragment;
gulp.task(taskName, ['build-merged-translations'], function () {
taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, ["build-merged-translations"], function() {
// Return only the translations for this fragment.
return gulp.src(fullDir + '/*.json')
.pipe(transform(data => ({
ui: {
panel: {
[fragment]: data.ui.panel[fragment],
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data) => ({
ui: {
panel: {
[fragment]: data.ui.panel[fragment],
},
},
},
})))
.pipe(gulp.dest(workDir + '/' + fragment));
}))
)
.pipe(gulp.dest(workDir + "/" + fragment));
});
tasks.push(taskName);
splitTasks.push(taskName);
});
taskName = 'build-translation-core';
gulp.task(taskName, ['build-merged-translations'], function () {
taskName = "build-translation-core";
gulp.task(taskName, ["build-merged-translations"], function() {
// Remove the fragment translations from the core translation.
return gulp.src(fullDir + '/*.json')
.pipe(transform((data) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
return data;
}))
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
return data;
})
)
.pipe(gulp.dest(coreDir));
});
tasks.push(taskName);
splitTasks.push(taskName);
taskName = 'build-flattened-translations';
gulp.task(taskName, splitTasks, function () {
taskName = "build-flattened-translations";
gulp.task(taskName, splitTasks, function() {
// Flatten the split versions of our translations, and move them into outDir
return gulp.src(
TRANSLATION_FRAGMENTS.map(fragment => workDir + '/' + fragment + '/*.json')
.concat(coreDir + '/*.json'),
{ base: workDir },
)
.pipe(transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
}))
return gulp
.src(
TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json"
).concat(coreDir + "/*.json"),
{ base: workDir }
)
.pipe(
transform(function(data) {
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
})
)
.pipe(minify())
.pipe(rename((filePath) => {
if (filePath.dirname === 'core') {
filePath.dirname = '';
}
}))
.pipe(
rename((filePath) => {
if (filePath.dirname === "core") {
filePath.dirname = "";
}
})
)
.pipe(gulp.dest(outDir));
});
tasks.push(taskName);
taskName = 'build-translation-fingerprints';
gulp.task(taskName, ['build-flattened-translations'], function () {
return gulp.src(outDir + '/**/*.json')
.pipe(rename({
extname: '',
}))
.pipe(hash({
algorithm: 'md5',
hashLength: 32,
template: '<%= name %>-<%= hash %>.json',
}))
.pipe(hash.manifest('translationFingerprints.json'))
.pipe(transform(function (data) {
// After generating fingerprints of our translation files, consolidate
// all translation fragment fingerprints under the translation name key
const newData = {};
Object.entries(data).forEach(([key, value]) => {
const parts = key.split('/');
let translation = key;
if (parts.length === 2) {
translation = parts[1];
}
if (!(translation in newData)) {
newData[translation] = {
fingerprints: {},
};
}
newData[translation].fingerprints[key] = value;
});
return newData;
}))
taskName = "build-translation-fingerprints";
gulp.task(taskName, ["build-flattened-translations"], function() {
return gulp
.src(outDir + "/**/*.json")
.pipe(
rename({
extname: "",
})
)
.pipe(
hash({
algorithm: "md5",
hashLength: 32,
template: "<%= name %>-<%= hash %>.json",
})
)
.pipe(hash.manifest("translationFingerprints.json"))
.pipe(
transform(function(data) {
// After generating fingerprints of our translation files, consolidate
// all translation fragment fingerprints under the translation name key
const newData = {};
Object.entries(data).forEach(([key, value]) => {
const parts = key.split("/");
let translation = key;
if (parts.length === 2) {
translation = parts[1];
}
if (!(translation in newData)) {
newData[translation] = {
fingerprints: {},
};
}
newData[translation].fingerprints[key] = value;
});
return newData;
})
)
.pipe(gulp.dest(workDir));
});
tasks.push(taskName);
taskName = 'build-translations';
gulp.task(taskName, ['build-translation-fingerprints'], function () {
return gulp.src([
'src/translations/translationMetadata.json',
workDir + '/translationFingerprints.json',
])
taskName = "build-translations";
gulp.task(taskName, ["build-translation-fingerprints"], function() {
return gulp
.src([
"src/translations/translationMetadata.json",
workDir + "/translationFingerprints.json",
])
.pipe(merge({}))
.pipe(transform(function (data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (data[key].nativeName) {
newData[key] = data[key];
} else {
console.warn(`Skipping language ${key}. Native name was not translated.`);
}
if (data[key]) newData[key] = value;
});
return newData;
}))
.pipe(transform(data => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
})))
.pipe(rename('translationMetadata.json'))
.pipe(
transform(function(data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (data[key].nativeName) {
newData[key] = data[key];
} else {
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
}
if (data[key]) newData[key] = value;
});
return newData;
})
)
.pipe(
transform((data) => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir));
});
tasks.push(taskName);

1
hassio/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
hassio-icons.html

View File

@@ -1,8 +1,8 @@
const path = require('path');
const path = require("path");
module.exports = {
// Target directory for the build.
buildDir: path.resolve(__dirname, 'build'),
buildDir: path.resolve(__dirname, "build"),
// Path where the Hass.io frontend will be publicly available.
publicPath: '/api/hassio/app',
}
publicPath: "/api/hassio/app",
};

View File

@@ -1,15 +1,12 @@
#!/usr/bin/env node
const fs = require('fs');
const {
findIcons,
generateIconset,
} = require('../../gulp/tasks/gen-icons.js');
const fs = require("fs");
const { findIcons, generateIconset } = require("../../gulp/tasks/gen-icons.js");
const MENU_BUTTON_ICON = 'menu';
const MENU_BUTTON_ICON = "menu";
function genHassioIcons() {
const iconNames = findIcons('./src', 'hassio').concat(MENU_BUTTON_ICON);
fs.writeFileSync('./hassio-icons.html', generateIconset('hassio', iconNames));
const iconNames = findIcons("./src", "hassio").concat(MENU_BUTTON_ICON);
fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames));
}
genHassioIcons();

View File

@@ -1,10 +1,10 @@
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
import "../components/hassio-card-content.js";
import "../resources/hassio-style.js";
import NavigateMixin from "../../../src/mixins/navigate-mixin.js";
class HassioAddonRepository extends NavigateMixin(PolymerElement) {
static get template() {
@@ -48,21 +48,27 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
}
sortAddons(a, b) {
return a.name < b.name ? -1 : 1;
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
}
computeIcon(addon) {
return addon.installed && addon.installed !== addon.version ? 'hassio:arrow-up-bold-circle' : 'hassio:puzzle';
return addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle";
}
computeIconTitle(addon) {
if (addon.installed) return addon.installed !== addon.version ? 'New version available' : 'Add-on is installed';
return 'Add-on is not installed';
if (addon.installed)
return addon.installed !== addon.version
? "New version available"
: "Add-on is installed";
return "Add-on is not installed";
}
computeIconClass(addon) {
if (addon.installed) return addon.installed !== addon.version ? 'update' : 'installed';
return '';
if (addon.installed)
return addon.installed !== addon.version ? "update" : "installed";
return "";
}
addonTapped(ev) {
@@ -70,4 +76,4 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
}
}
customElements.define('hassio-addon-repository', HassioAddonRepository);
customElements.define("hassio-addon-repository", HassioAddonRepository);

View File

@@ -1,8 +1,8 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import './hassio-addon-repository.js';
import './hassio-repositories-editor.js';
import "./hassio-addon-repository.js";
import "./hassio-repositories-editor.js";
class HassioAddonStore extends PolymerElement {
static get template() {
@@ -30,7 +30,7 @@ class HassioAddonStore extends PolymerElement {
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
this.loadData();
}
@@ -41,42 +41,45 @@ class HassioAddonStore extends PolymerElement {
}
sortRepos(a, b) {
if (a.slug === 'local') {
if (a.slug === "local") {
return -1;
} else if (b.slug === 'local') {
return 1;
} else if (a.slug === 'core') {
return -1;
} else if (b.slug === 'core') {
}
if (b.slug === "local") {
return 1;
}
return a.name < b.name ? -1 : 1;
if (a.slug === "core") {
return -1;
}
if (b.slug === "core") {
return 1;
}
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
}
computeAddons(repo) {
return this.addons.filter(function (addon) {
return this.addons.filter(function(addon) {
return addon.repository === repo;
});
}
loadData() {
this.hass.callApi('get', 'hassio/addons')
.then((info) => {
this.hass.callApi("get", "hassio/addons").then(
(info) => {
this.addons = info.data.addons;
this.repos = info.data.repositories;
}, () => {
},
() => {
this.addons = [];
this.repos = [];
});
}
);
}
refreshData() {
this.hass.callApi('post', 'hassio/addons/reload')
.then(() => {
this.loadData();
});
this.hass.callApi("post", "hassio/addons/reload").then(() => {
this.loadData();
});
}
}
customElements.define('hassio-addon-store', HassioAddonStore);
customElements.define("hassio-addon-store", HassioAddonStore);

View File

@@ -1,12 +1,12 @@
import '@polymer/iron-icon/iron-icon.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-input/paper-input.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-icon/iron-icon.js";
import "@polymer/paper-card/paper-card.js";
import "@polymer/paper-input/paper-input.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import "../../../src/components/buttons/ha-call-api-button.js";
import "../components/hassio-card-content.js";
import "../resources/hassio-style.js";
class HassioRepositoriesEditor extends PolymerElement {
static get template() {
@@ -60,7 +60,7 @@ class HassioRepositoriesEditor extends PolymerElement {
hass: Object,
repos: {
type: Array,
observer: 'reposChanged',
observer: "reposChanged",
},
repoList: Array,
repoUrl: String,
@@ -68,8 +68,10 @@ class HassioRepositoriesEditor extends PolymerElement {
}
reposChanged(repos) {
this.repoList = repos.filter(repo => repo.slug !== 'core' && repo.slug !== 'local');
this.repoUrl = '';
this.repoList = repos.filter(
(repo) => repo.slug !== "core" && repo.slug !== "local"
);
this.repoUrl = "";
}
sortRepos(a, b) {
@@ -77,15 +79,17 @@ class HassioRepositoriesEditor extends PolymerElement {
}
computeRemoveRepoData(repoList, url) {
const list = repoList.filter(repo => repo.url !== url).map(repo => repo.url);
const list = repoList
.filter((repo) => repo.url !== url)
.map((repo) => repo.url);
return { addons_repositories: list };
}
computeAddRepoData(repoList, url) {
const list = repoList ? repoList.map(repo => repo.url) : [];
const list = repoList ? repoList.map((repo) => repo.url) : [];
list.push(url);
return { addons_repositories: list };
}
}
customElements.define('hassio-repositories-editor', HassioRepositoriesEditor);
customElements.define("hassio-repositories-editor", HassioRepositoriesEditor);

View File

@@ -1,15 +1,15 @@
import 'web-animations-js/web-animations-next-lite.min.js';
import "web-animations-js/web-animations-next-lite.min.js";
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
import '@polymer/paper-item/paper-item.js';
import '@polymer/paper-listbox/paper-listbox.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu.js";
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-listbox/paper-listbox.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/resources/ha-style.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../../../src/resources/ha-style.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
class HassioAddonAudio extends EventsMixin(PolymerElement) {
static get template() {
@@ -64,7 +64,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
hass: Object,
addon: {
type: Object,
observer: 'addonChanged'
observer: "addonChanged",
},
inputDevices: Array,
outputDevices: Array,
@@ -76,40 +76,55 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
addonChanged(addon) {
this.setProperties({
selectedInput: addon.audio_input || 'null',
selectedOutput: addon.audio_output || 'null'
selectedInput: addon.audio_input || "null",
selectedOutput: addon.audio_output || "null",
});
if (this.outputDevices) return;
const noDevice = [{ device: 'null', name: '-' }];
this.hass.callApi('get', 'hassio/hardware/audio').then((resp) => {
const dev = resp.data.audio;
const input = Object.keys(dev.input).map(key => ({ device: key, name: dev.input[key] }));
const output = Object.keys(dev.output).map(key => ({ device: key, name: dev.output[key] }));
this.setProperties({
inputDevices: noDevice.concat(input),
outputDevices: noDevice.concat(output)
});
}, () => {
this.setProperties({
inputDevices: noDevice,
outputDevices: noDevice
});
});
const noDevice = [{ device: "null", name: "-" }];
this.hass.callApi("get", "hassio/hardware/audio").then(
(resp) => {
const dev = resp.data.audio;
const input = Object.keys(dev.input).map((key) => ({
device: key,
name: dev.input[key],
}));
const output = Object.keys(dev.output).map((key) => ({
device: key,
name: dev.output[key],
}));
this.setProperties({
inputDevices: noDevice.concat(input),
outputDevices: noDevice.concat(output),
});
},
() => {
this.setProperties({
inputDevices: noDevice,
outputDevices: noDevice,
});
}
);
}
_saveSettings() {
this.error = null;
const path = `hassio/addons/${this.addon.slug}/options`;
this.hass.callApi('post', path, {
audio_input: this.selectedInput === 'null' ? null : this.selectedInput,
audio_output: this.selectedOutput === 'null' ? null : this.selectedOutput
}).then(() => {
this.fire('hass-api-called', { success: true, path: path });
}, (resp) => {
this.error = resp.body.message;
});
this.hass
.callApi("post", path, {
audio_input: this.selectedInput === "null" ? null : this.selectedInput,
audio_output:
this.selectedOutput === "null" ? null : this.selectedOutput,
})
.then(
() => {
this.fire("hass-api-called", { success: true, path: path });
},
(resp) => {
this.error = resp.body.message;
}
);
}
}
customElements.define('hassio-addon-audio', HassioAddonAudio);
customElements.define("hassio-addon-audio", HassioAddonAudio);

View File

@@ -1,10 +1,10 @@
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js";
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import "../../../src/components/buttons/ha-call-api-button.js";
class HassioAddonConfig extends PolymerElement {
static get template() {
@@ -52,12 +52,12 @@ class HassioAddonConfig extends PolymerElement {
hass: Object,
addon: {
type: Object,
observer: 'addonChanged',
observer: "addonChanged",
},
addonSlug: String,
config: {
type: String,
observer: 'configChanged',
observer: "configChanged",
},
configParsed: Object,
error: String,
@@ -71,15 +71,15 @@ class HassioAddonConfig extends PolymerElement {
}
addonChanged(addon) {
this.config = addon ? JSON.stringify(addon.options, null, 2) : '';
this.config = addon ? JSON.stringify(addon.options, null, 2) : "";
}
configChanged(config) {
try {
this.$.config.classList.remove('syntaxerror');
this.$.config.classList.remove("syntaxerror");
this.configParsed = JSON.parse(config);
} catch (err) {
this.$.config.classList.add('syntaxerror');
this.$.config.classList.add("syntaxerror");
this.configParsed = null;
}
}
@@ -87,12 +87,14 @@ class HassioAddonConfig extends PolymerElement {
saveTapped() {
this.error = null;
this.hass.callApi('post', `hassio/addons/${this.addonSlug}/options`, {
options: this.configParsed
}).catch((resp) => {
this.error = resp.body.message;
});
this.hass
.callApi("post", `hassio/addons/${this.addonSlug}/options`, {
options: this.configParsed,
})
.catch((resp) => {
this.error = resp.body.message;
});
}
}
customElements.define('hassio-addon-config', HassioAddonConfig);
customElements.define("hassio-addon-config", HassioAddonConfig);

View File

@@ -1,16 +1,16 @@
import '@polymer/iron-icon/iron-icon.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-toggle-button/paper-toggle-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-icon/iron-icon.js";
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import "@polymer/paper-toggle-button/paper-toggle-button.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import '../../../src/components/ha-markdown.js';
import '../../../src/resources/ha-style.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../../../src/components/buttons/ha-call-api-button.js";
import "../../../src/components/ha-markdown.js";
import "../../../src/resources/ha-style.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
import '../components/hassio-card-content.js';
import "../components/hassio-card-content.js";
class HassioAddonInfo extends EventsMixin(PolymerElement) {
static get template() {
@@ -157,21 +157,26 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
addonSlug: String,
isRunning: {
type: Boolean,
computed: 'computeIsRunning(addon)',
computed: "computeIsRunning(addon)",
},
};
}
computeIsRunning(addon) {
return addon && addon.state === 'started';
return addon && addon.state === "started";
}
computeUpdateAvailable(addon) {
return addon && !addon.detached && addon.version && addon.version !== addon.last_version;
return (
addon &&
!addon.detached &&
addon.version &&
addon.version !== addon.last_version
);
}
pathWebui(webui) {
return webui && webui.replace('[HOST]', document.location.hostname);
return webui && webui.replace("[HOST]", document.location.hostname);
}
computeShowWebUI(webui, isRunning) {
@@ -179,49 +184,54 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
}
computeStartOnBoot(state) {
return state === 'auto';
return state === "auto";
}
startOnBootToggled() {
const data = { boot: this.addon.boot === 'auto' ? 'manual' : 'auto' };
this.hass.callApi('POST', `hassio/addons/${this.addonSlug}/options`, data);
const data = { boot: this.addon.boot === "auto" ? "manual" : "auto" };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
}
autoUpdateToggled() {
const data = { auto_update: !this.addon.auto_update };
this.hass.callApi('POST', `hassio/addons/${this.addonSlug}/options`, data);
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
}
openChangelog() {
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/changelog`)
.then(
resp => resp
, () => 'Error getting changelog'
).then((content) => {
this.fire('hassio-markdown-dialog', {
title: 'Changelog',
this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
.then((resp) => resp, () => "Error getting changelog")
.then((content) => {
this.fire("hassio-markdown-dialog", {
title: "Changelog",
content: content,
});
});
}
_unistallClicked() {
if (!confirm('Are you sure you want to uninstall this add-on?')) {
if (!confirm("Are you sure you want to uninstall this add-on?")) {
return;
}
const path = `hassio/addons/${this.addonSlug}/uninstall`;
const eventData = {
path: path,
};
this.hass.callApi('post', path).then((resp) => {
eventData.success = true;
eventData.response = resp;
}, (resp) => {
eventData.success = false;
eventData.response = resp;
}).then(() => {
this.fire('hass-api-called', eventData);
});
this.hass
.callApi("post", path)
.then(
(resp) => {
eventData.success = true;
eventData.response = resp;
},
(resp) => {
eventData.success = false;
eventData.response = resp;
}
)
.then(() => {
this.fire("hass-api-called", eventData);
});
}
}
customElements.define('hassio-addon-info', HassioAddonInfo);
customElements.define("hassio-addon-info", HassioAddonInfo);

View File

@@ -1,9 +1,9 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/resources/ha-style.js';
import "../../../src/resources/ha-style.js";
class HassioAddonLogs extends PolymerElement {
static get template() {
@@ -33,7 +33,7 @@ class HassioAddonLogs extends PolymerElement {
hass: Object,
addonSlug: {
type: String,
observer: 'addonSlugChanged',
observer: "addonSlugChanged",
},
log: String,
};
@@ -41,7 +41,9 @@ class HassioAddonLogs extends PolymerElement {
addonSlugChanged(slug) {
if (!this.hass) {
setTimeout(() => { this.addonChanged(slug); }, 0);
setTimeout(() => {
this.addonChanged(slug);
}, 0);
return;
}
@@ -49,11 +51,12 @@ class HassioAddonLogs extends PolymerElement {
}
refresh() {
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/logs`)
this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/logs`)
.then((info) => {
this.log = info;
});
}
}
customElements.define('hassio-addon-logs', HassioAddonLogs);
customElements.define("hassio-addon-logs", HassioAddonLogs);

View File

@@ -1,11 +1,11 @@
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-input/paper-input.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-card/paper-card.js";
import "@polymer/paper-input/paper-input.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import '../../../src/resources/ha-style.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../../../src/components/buttons/ha-call-api-button.js";
import "../../../src/resources/ha-style.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
class HassioAddonNetwork extends EventsMixin(PolymerElement) {
static get template() {
@@ -64,7 +64,7 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
config: Object,
addon: {
type: Object,
observer: 'addonChanged',
observer: "addonChanged",
},
error: String,
resetData: {
@@ -80,29 +80,36 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
if (!addon) return;
const network = addon.network || {};
const items = Object.keys(network).map(key => ({
const items = Object.keys(network).map((key) => ({
container: key,
host: network[key]
host: network[key],
}));
this.config = items.sort(function (el1, el2) { return el1.host - el2.host; });
this.config = items.sort(function(el1, el2) {
return el1.host - el2.host;
});
}
saveTapped() {
this.error = null;
const data = {};
this.config.forEach(function (item) {
this.config.forEach(function(item) {
data[item.container] = parseInt(item.host);
});
const path = `hassio/addons/${this.addonSlug}/options`;
this.hass.callApi('post', path, {
network: data
}).then(() => {
this.fire('hass-api-called', { success: true, path: path });
}, (resp) => {
this.error = resp.body.message;
});
this.hass
.callApi("post", path, {
network: data,
})
.then(
() => {
this.fire("hass-api-called", { success: true, path: path });
},
(resp) => {
this.error = resp.body.message;
}
);
}
}
customElements.define('hassio-addon-network', HassioAddonNetwork);
customElements.define("hassio-addon-network", HassioAddonNetwork);

View File

@@ -1,19 +1,19 @@
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/app-route/app-route.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/app-layout/app-header-layout/app-header-layout.js";
import "@polymer/app-layout/app-header/app-header.js";
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/app-route/app-route.js";
import "@polymer/paper-icon-button/paper-icon-button.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/ha-menu-button.js';
import '../../../src/resources/ha-style.js';
import '../hassio-markdown-dialog.js';
import './hassio-addon-audio.js';
import './hassio-addon-config.js';
import './hassio-addon-info.js';
import './hassio-addon-logs.js';
import './hassio-addon-network.js';
import "../../../src/components/ha-menu-button.js";
import "../../../src/resources/ha-style.js";
import "../hassio-markdown-dialog.js";
import "./hassio-addon-audio.js";
import "./hassio-addon-config.js";
import "./hassio-addon-info.js";
import "./hassio-addon-logs.js";
import "./hassio-addon-network.js";
class HassioAddonView extends PolymerElement {
static get template() {
@@ -91,7 +91,7 @@ class HassioAddonView extends PolymerElement {
route: Object,
routeData: {
type: Object,
observer: 'routeDataChanged',
observer: "routeDataChanged",
},
routeMatches: Boolean,
addon: Object,
@@ -99,15 +99,17 @@ class HassioAddonView extends PolymerElement {
markdownTitle: String,
markdownContent: {
type: String,
value: '',
value: "",
},
};
}
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.addEventListener('hassio-markdown-dialog', ev => this.openMarkdown(ev));
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
this.addEventListener("hassio-markdown-dialog", (ev) =>
this.openMarkdown(ev)
);
}
apiCalled(ev) {
@@ -115,7 +117,7 @@ class HassioAddonView extends PolymerElement {
if (!path) return;
if (path.substr(path.lastIndexOf('/') + 1) === 'uninstall') {
if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") {
this.backTapped();
} else {
this.routeDataChanged(this.routeData);
@@ -124,12 +126,14 @@ class HassioAddonView extends PolymerElement {
routeDataChanged(routeData) {
if (!this.routeMatches || !routeData || !routeData.slug) return;
this.hass.callApi('get', `hassio/addons/${routeData.slug}/info`)
.then((info) => {
this.hass.callApi("get", `hassio/addons/${routeData.slug}/info`).then(
(info) => {
this.addon = info.data;
}, () => {
},
() => {
this.addon = null;
});
}
);
}
backTapped() {
@@ -141,8 +145,8 @@ class HassioAddonView extends PolymerElement {
markdownTitle: ev.detail.title,
markdownContent: ev.detail.content,
});
this.shadowRoot.querySelector('hassio-markdown-dialog').openDialog();
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
}
}
customElements.define('hassio-addon-view', HassioAddonView);
customElements.define("hassio-addon-view", HassioAddonView);

View File

@@ -1,8 +1,8 @@
import '@polymer/iron-icon/iron-icon.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-icon/iron-icon.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/ha-relative-time.js';
import "../../../src/components/ha-relative-time.js";
class HassioCardContent extends PolymerElement {
static get template() {
@@ -65,11 +65,11 @@ class HassioCardContent extends PolymerElement {
datetime: String,
icon: {
type: String,
value: 'hass:help-circle'
value: "hass:help-circle",
},
iconTitle: String,
iconClass: String,
};
}
}
customElements.define('hassio-card-content', HassioCardContent);
customElements.define("hassio-card-content", HassioCardContent);

View File

@@ -1,10 +1,10 @@
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
import "../components/hassio-card-content.js";
import "../resources/hassio-style.js";
import NavigateMixin from "../../../src/mixins/navigate-mixin.js";
class HassioAddons extends NavigateMixin(PolymerElement) {
static get template() {
@@ -46,28 +46,32 @@ class HassioAddons extends NavigateMixin(PolymerElement) {
}
computeIcon(addon) {
return addon.installed !== addon.version ? 'hassio:arrow-up-bold-circle' : 'hassio:puzzle';
return addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle";
}
computeIconTitle(addon) {
if (addon.installed !== addon.version) return 'New version available';
return addon.state === 'started' ? 'Add-on is running' : 'Add-on is stopped';
if (addon.installed !== addon.version) return "New version available";
return addon.state === "started"
? "Add-on is running"
: "Add-on is stopped";
}
computeIconClass(addon) {
if (addon.installed !== addon.version) return 'update';
return addon.state === 'started' ? 'running' : '';
if (addon.installed !== addon.version) return "update";
return addon.state === "started" ? "running" : "";
}
addonTapped(ev) {
this.navigate('/hassio/addon/' + ev.model.addon.slug);
this.navigate("/hassio/addon/" + ev.model.addon.slug);
ev.target.blur();
}
openStore(ev) {
this.navigate('/hassio/store');
this.navigate("/hassio/store");
ev.target.blur();
}
}
customElements.define('hassio-addons', HassioAddons);
customElements.define("hassio-addons", HassioAddons);

View File

@@ -1,9 +1,9 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import './hassio-addons.js';
import './hassio-hass-update.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "./hassio-addons.js";
import "./hassio-hass-update.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
class HassioDashboard extends EventsMixin(PolymerElement) {
static get template() {
@@ -29,4 +29,4 @@ class HassioDashboard extends EventsMixin(PolymerElement) {
}
}
customElements.define('hassio-dashboard', HassioDashboard);
customElements.define("hassio-dashboard", HassioDashboard);

View File

@@ -1,11 +1,11 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import "../../../src/components/buttons/ha-call-api-button.js";
import "../components/hassio-card-content.js";
import "../resources/hassio-style.js";
class HassioHassUpdate extends PolymerElement {
static get template() {
@@ -19,6 +19,9 @@ class HassioHassUpdate extends PolymerElement {
color: var(--google-red-500);
margin-top: 16px;
}
a {
color: var(--primary-color);
}
</style>
<template is="dom-if" if="[[computeUpdateAvailable(hassInfo)]]">
<div class="content">
@@ -30,6 +33,7 @@ class HassioHassUpdate extends PolymerElement {
<template is="dom-if" if="[[error]]">
<div class="error">Error: [[error]]</div>
</template>
<p><a href='https://www.home-assistant.io/latest-release-notes/' target='_blank'>Read the release notes</a></p>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/homeassistant/update">Update</ha-call-api-button>
@@ -52,7 +56,7 @@ class HassioHassUpdate extends PolymerElement {
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
apiCalled(ev) {
@@ -63,8 +67,8 @@ class HassioHassUpdate extends PolymerElement {
const response = ev.detail.response;
if (typeof response.body === 'object') {
this.errors = response.body.message || 'Unknown error';
if (typeof response.body === "object") {
this.errors = response.body.message || "Unknown error";
} else {
this.errors = response.body;
}
@@ -75,4 +79,4 @@ class HassioHassUpdate extends PolymerElement {
}
}
customElements.define('hassio-hass-update', HassioHassUpdate);
customElements.define("hassio-hass-update", HassioHassUpdate);

View File

@@ -1,5 +1,4 @@
window.loadES5Adapter().then(() => {
import(/* webpackChunkName: "hassio-icons" */ './resources/hassio-icons.js');
import(/* webpackChunkName: "hassio-main" */ './hassio-main.js');
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons.js");
import(/* webpackChunkName: "hassio-main" */ "./hassio-main.js");
});

View File

@@ -1,8 +1,8 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import './hassio-main.js';
import './resources/hassio-icons.js';
import "./hassio-main.js";
import "./resources/hassio-icons.js";
class HassioApp extends PolymerElement {
static get template() {
@@ -29,13 +29,13 @@ class HassioApp extends PolymerElement {
ready() {
super.ready();
window.setProperties = this.setProperties.bind(this);
this.addEventListener('location-changed', () => this._locationChanged());
this.addEventListener('hass-open-menu', () => this._menuEvent(true));
this.addEventListener('hass-close-menu', () => this._menuEvent(false));
this.addEventListener("location-changed", () => this._locationChanged());
this.addEventListener("hass-open-menu", () => this._menuEvent(true));
this.addEventListener("hass-close-menu", () => this._menuEvent(false));
}
_menuEvent(shouldOpen) {
this.hassioPanel.fire(shouldOpen ? 'hass-open-menu' : 'hass-close-menu');
this.hassioPanel.fire(shouldOpen ? "hass-open-menu" : "hass-close-menu");
}
_locationChanged() {
@@ -43,4 +43,4 @@ class HassioApp extends PolymerElement {
}
}
customElements.define('hassio-app', HassioApp);
customElements.define("hassio-app", HassioApp);

View File

@@ -1,4 +1,4 @@
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
class HassioData extends PolymerElement {
static get properties() {
@@ -36,25 +36,24 @@ class HassioData extends PolymerElement {
}
fetchSupervisorInfo() {
return this.hass.callApi('get', 'hassio/supervisor/info')
.then((info) => {
this.supervisor = info.data;
});
return this.hass.callApi("get", "hassio/supervisor/info").then((info) => {
this.supervisor = info.data;
});
}
fetchHostInfo() {
return this.hass.callApi('get', 'hassio/host/info')
.then((info) => {
this.host = info.data;
});
return this.hass.callApi("get", "hassio/host/info").then((info) => {
this.host = info.data;
});
}
fetchHassInfo() {
return this.hass.callApi('get', 'hassio/homeassistant/info')
return this.hass
.callApi("get", "hassio/homeassistant/info")
.then((info) => {
this.homeassistant = info.data;
});
}
}
customElements.define('hassio-data', HassioData);
customElements.define("hassio-data", HassioData);

View File

@@ -1,16 +1,17 @@
import '@polymer/app-route/app-route.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/app-route/app-route.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../src/layouts/hass-loading-screen.js';
import './addon-view/hassio-addon-view.js';
import './hassio-data.js';
import './hassio-pages-with-tabs.js';
import "../../src/layouts/hass-loading-screen.js";
import "./addon-view/hassio-addon-view.js";
import "./hassio-data.js";
import "./hassio-pages-with-tabs.js";
import applyThemesOnElement from '../../src/common/dom/apply_themes_on_element.js';
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element.js";
import EventsMixin from "../../src/mixins/events-mixin.js";
import NavigateMixin from "../../src/mixins/navigate-mixin.js";
class HassioMain extends NavigateMixin(PolymerElement) {
class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
static get template() {
return html`
<app-route route="[[route]]" pattern="/:page" data="{{routeData}}"></app-route>
@@ -40,11 +41,11 @@ class HassioMain extends NavigateMixin(PolymerElement) {
type: Object,
// Fake route object
value: {
prefix: '/hassio',
path: '/dashboard',
__queryParams: {}
prefix: "/hassio",
path: "/dashboard",
__queryParams: {},
},
observer: 'routeChanged',
observer: "routeChanged",
},
routeData: Object,
supervisorInfo: Object,
@@ -52,7 +53,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
hassInfo: Object,
loaded: {
type: Boolean,
computed: 'computeIsLoaded(supervisorInfo, hostInfo, hassInfo)',
computed: "computeIsLoaded(supervisorInfo, hostInfo, hassInfo)",
},
};
}
@@ -60,7 +61,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
ready() {
super.ready();
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
connectedCallback() {
@@ -73,7 +74,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
let tries = 1;
const tryUpdate = () => {
this.$.data.refresh().catch(function () {
this.$.data.refresh().catch(function() {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
@@ -84,20 +85,19 @@ class HassioMain extends NavigateMixin(PolymerElement) {
}
computeIsLoaded(supervisorInfo, hostInfo, hassInfo) {
return (supervisorInfo !== null &&
hostInfo !== null &&
hassInfo !== null);
return supervisorInfo !== null && hostInfo !== null && hassInfo !== null;
}
routeChanged(route) {
if (route.path === '' && route.prefix === '/hassio') {
this.navigate('/hassio/dashboard', true);
if (route.path === "" && route.prefix === "/hassio") {
this.navigate("/hassio/dashboard", true);
}
this.fire("iron-resize");
}
equalsAddon(page) {
return page && page === 'addon';
return page && page === "addon";
}
}
customElements.define('hassio-main', HassioMain);
customElements.define("hassio-main", HassioMain);

View File

@@ -1,12 +1,12 @@
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
import '@polymer/paper-dialog/paper-dialog.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js";
import "@polymer/paper-dialog/paper-dialog.js";
import "@polymer/paper-icon-button/paper-icon-button.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../src/components/ha-markdown.js';
import '../../src/resources/ha-style.js';
import "../../src/components/ha-markdown.js";
import "../../src/resources/ha-style.js";
class HassioMarkdownDialog extends PolymerElement {
static get template() {
@@ -73,4 +73,4 @@ class HassioMarkdownDialog extends PolymerElement {
this.$.dialog.open();
}
}
customElements.define('hassio-markdown-dialog', HassioMarkdownDialog);
customElements.define("hassio-markdown-dialog", HassioMarkdownDialog);

View File

@@ -1,24 +1,24 @@
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/paper-tabs/paper-tab.js';
import '@polymer/paper-tabs/paper-tabs.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/app-layout/app-header-layout/app-header-layout.js";
import "@polymer/app-layout/app-header/app-header.js";
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/paper-icon-button/paper-icon-button.js";
import "@polymer/paper-tabs/paper-tab.js";
import "@polymer/paper-tabs/paper-tabs.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../src/components/ha-menu-button.js';
import '../../src/resources/ha-style.js';
import './addon-store/hassio-addon-store.js';
import './dashboard/hassio-dashboard.js';
import './hassio-markdown-dialog.js';
import './snapshots/hassio-snapshot.js';
import './snapshots/hassio-snapshots.js';
import './system/hassio-system.js';
import "../../src/components/ha-menu-button.js";
import "../../src/resources/ha-style.js";
import "./addon-store/hassio-addon-store.js";
import "./dashboard/hassio-dashboard.js";
import "./hassio-markdown-dialog.js";
import "./snapshots/hassio-snapshot.js";
import "./snapshots/hassio-snapshots.js";
import "./system/hassio-system.js";
import scrollToTarget from '../../src/common/dom/scroll-to-target.js';
import scrollToTarget from "../../src/common/dom/scroll-to-target.js";
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
import NavigateMixin from "../../src/mixins/navigate-mixin.js";
class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
static get template() {
@@ -87,18 +87,20 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
markdownTitle: String,
markdownContent: {
type: String,
value: '',
value: "",
},
};
}
ready() {
super.ready();
this.addEventListener('hassio-markdown-dialog', ev => this.openMarkdown(ev));
this.addEventListener("hassio-markdown-dialog", (ev) =>
this.openMarkdown(ev)
);
}
handlePageSelected(ev) {
const newPage = ev.detail.item.getAttribute('page-name');
const newPage = ev.detail.item.getAttribute("page-name");
if (newPage !== this.page) {
this.navigate(`/hassio/${newPage}`);
}
@@ -110,14 +112,14 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
}
showRefreshButton(page) {
return page === 'store' || page === 'snapshots';
return page === "store" || page === "snapshots";
}
refreshClicked() {
if (this.page === 'snapshots') {
this.shadowRoot.querySelector('hassio-snapshots').refreshData();
if (this.page === "snapshots") {
this.shadowRoot.querySelector("hassio-snapshots").refreshData();
} else {
this.shadowRoot.querySelector('hassio-addon-store').refreshData();
this.shadowRoot.querySelector("hassio-addon-store").refreshData();
}
}
@@ -126,8 +128,8 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
markdownTitle: ev.detail.title,
markdownContent: ev.detail.content,
});
this.shadowRoot.querySelector('hassio-markdown-dialog').openDialog();
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
}
}
customElements.define('hassio-pages-with-tabs', HassioPagesWithTabs);
customElements.define("hassio-pages-with-tabs", HassioPagesWithTabs);

View File

@@ -1,7 +1,7 @@
import '../../../src/components/ha-iconset-svg.js';
import iconSetContent from '../../hassio-icons.html';
import "../../../src/components/ha-iconset-svg.js";
import iconSetContent from "../../hassio-icons.html";
const documentContainer = document.createElement('template');
documentContainer.setAttribute('style', 'display: none;');
const documentContainer = document.createElement("template");
documentContainer.setAttribute("style", "display: none;");
documentContainer.innerHTML = iconSetContent;
document.head.appendChild(documentContainer.content);

View File

@@ -1,5 +1,5 @@
const documentContainer = document.createElement('template');
documentContainer.setAttribute('style', 'display: none;');
const documentContainer = document.createElement("template");
documentContainer.setAttribute("style", "display: none;");
documentContainer.innerHTML = `<dom-module id="hassio-style">
<template>

View File

@@ -1,14 +1,14 @@
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-checkbox/paper-checkbox.js';
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
import '@polymer/paper-dialog/paper-dialog.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/paper-input/paper-input.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/app-layout/app-toolbar/app-toolbar.js";
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-checkbox/paper-checkbox.js";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js";
import "@polymer/paper-dialog/paper-dialog.js";
import "@polymer/paper-icon-button/paper-icon-button.js";
import "@polymer/paper-input/paper-input.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/resources/ha-style.js';
import "../../../src/resources/ha-style.js";
class HassioSnapshot extends PolymerElement {
static get template() {
@@ -114,7 +114,7 @@ class HassioSnapshot extends PolymerElement {
snapshotSlug: {
type: String,
notify: true,
observer: '_snapshotSlugChanged',
observer: "_snapshotSlugChanged",
},
snapshotDeleted: {
type: Boolean,
@@ -131,84 +131,124 @@ class HassioSnapshot extends PolymerElement {
}
_snapshotSlugChanged(snapshotSlug) {
if (!snapshotSlug || snapshotSlug === 'update') return;
this.hass.callApi('get', `hassio/snapshots/${snapshotSlug}/info`)
.then((info) => {
if (!snapshotSlug || snapshotSlug === "update") return;
this.hass.callApi("get", `hassio/snapshots/${snapshotSlug}/info`).then(
(info) => {
info.data.folders = this._computeFolders(info.data.folders);
info.data.addons = this._computeAddons(info.data.addons);
this.snapshot = info.data;
this.$.dialog.open();
}, () => {
},
() => {
this.snapshot = null;
});
}
);
}
_computeFolders(folders) {
const list = [];
if (folders.includes('homeassistant')) list.push({ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true });
if (folders.includes('ssl')) list.push({ slug: 'ssl', name: 'SSL', checked: true });
if (folders.includes('share')) list.push({ slug: 'share', name: 'Share', checked: true });
if (folders.includes('addons/local')) list.push({ slug: 'addons/local', name: 'Local add-ons', checked: true });
if (folders.includes("homeassistant"))
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: true,
});
if (folders.includes("ssl"))
list.push({ slug: "ssl", name: "SSL", checked: true });
if (folders.includes("share"))
list.push({ slug: "share", name: "Share", checked: true });
if (folders.includes("addons/local"))
list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
return list;
}
_computeAddons(addons) {
return addons.map(addon => (
{ slug: addon.slug, name: addon.name, version: addon.version, checked: true }));
return addons.map((addon) => ({
slug: addon.slug,
name: addon.name,
version: addon.version,
checked: true,
}));
}
_isFullSnapshot(type) {
return type === 'full';
return type === "full";
}
_partialRestoreClicked() {
if (!confirm('Are you sure you want to restore this snapshot?')) {
if (!confirm("Are you sure you want to restore this snapshot?")) {
return;
}
const addons = this.snapshot.addons.filter(addon => addon.checked).map(addon => addon.slug);
const folders =
this.snapshot.folders.filter(folder => folder.checked).map(folder => folder.slug);
const addons = this.snapshot.addons
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this.snapshot.folders
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data = {
homeassistant: this.restoreHass,
addons: addons,
folders: folders
folders: folders,
};
if (this.snapshot.protected) data.password = this.snapshotPassword;
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/restore/partial`, data).then(() => {
alert('Snapshot restored!');
this.$.dialog.close();
}, (error) => {
this.error = error.body.message;
});
this.hass
.callApi(
"post",
`hassio/snapshots/${this.snapshotSlug}/restore/partial`,
data
)
.then(
() => {
alert("Snapshot restored!");
this.$.dialog.close();
},
(error) => {
this.error = error.body.message;
}
);
}
_fullRestoreClicked() {
if (!confirm('Are you sure you want to restore this snapshot?')) {
if (!confirm("Are you sure you want to restore this snapshot?")) {
return;
}
const data = this.snapshot.protected ? { password: this.snapshotPassword } : null;
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/restore/full`, data)
.then(() => {
alert('Snapshot restored!');
this.$.dialog.close();
}, (error) => {
this.error = error.body.message;
});
const data = this.snapshot.protected
? { password: this.snapshotPassword }
: null;
this.hass
.callApi(
"post",
`hassio/snapshots/${this.snapshotSlug}/restore/full`,
data
)
.then(
() => {
alert("Snapshot restored!");
this.$.dialog.close();
},
(error) => {
this.error = error.body.message;
}
);
}
_deleteClicked() {
if (!confirm('Are you sure you want to delete this snapshot?')) {
if (!confirm("Are you sure you want to delete this snapshot?")) {
return;
}
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/remove`)
.then(() => {
this.$.dialog.close();
this.snapshotDeleted = true;
}, (error) => {
this.error = error.body.message;
});
this.hass
.callApi("post", `hassio/snapshots/${this.snapshotSlug}/remove`)
.then(
() => {
this.$.dialog.close();
this.snapshotDeleted = true;
},
(error) => {
this.error = error.body.message;
}
);
}
_computeDownloadUrl(snapshotSlug) {
@@ -217,7 +257,7 @@ class HassioSnapshot extends PolymerElement {
}
_computeDownloadName(snapshot) {
const name = this._computeName(snapshot).replace(/[^a-z0-9]+/gi, '_');
const name = this._computeName(snapshot).replace(/[^a-z0-9]+/gi, "_");
return `Hass_io_${name}.tar`;
}
@@ -226,11 +266,11 @@ class HassioSnapshot extends PolymerElement {
}
_computeType(type) {
return type === 'full' ? 'Full snapshot' : 'Partial snapshot';
return type === "full" ? "Full snapshot" : "Partial snapshot";
}
_computeSize(size) {
return (Math.ceil(size * 10) / 10) + ' MB';
return Math.ceil(size * 10) / 10 + " MB";
}
_sortAddons(a, b) {
@@ -239,12 +279,12 @@ class HassioSnapshot extends PolymerElement {
_formatDatetime(datetime) {
return new Date(datetime).toLocaleDateString(navigator.language, {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit'
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
}
@@ -252,4 +292,4 @@ class HassioSnapshot extends PolymerElement {
this.snapshotSlug = null;
}
}
customElements.define('hassio-snapshot', HassioSnapshot);
customElements.define("hassio-snapshot", HassioSnapshot);

View File

@@ -1,15 +1,15 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-checkbox/paper-checkbox.js';
import '@polymer/paper-input/paper-input.js';
import '@polymer/paper-radio-button/paper-radio-button.js';
import '@polymer/paper-radio-group/paper-radio-group.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import "@polymer/paper-checkbox/paper-checkbox.js";
import "@polymer/paper-input/paper-input.js";
import "@polymer/paper-radio-button/paper-radio-button.js";
import "@polymer/paper-radio-group/paper-radio-group.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../components/hassio-card-content.js";
import "../resources/hassio-style.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
class HassioSnapshots extends EventsMixin(PolymerElement) {
static get template() {
@@ -105,16 +105,16 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
hass: Object,
snapshotName: {
type: String,
value: '',
value: "",
},
snapshotPassword: {
type: String,
value: '',
value: "",
},
snapshotHasPassword: Boolean,
snapshotType: {
type: String,
value: 'full',
value: "full",
},
snapshots: {
type: Array,
@@ -122,16 +122,20 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
},
installedAddons: {
type: Array,
observer: '_installedAddonsChanged',
observer: "_installedAddonsChanged",
},
addonList: Array,
folderList: {
type: Array,
value: [
{ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true },
{ slug: 'ssl', name: 'SSL', checked: true },
{ slug: 'share', name: 'Share', checked: true },
{ slug: 'addons/local', name: 'Local add-ons', checked: true },
{
slug: "homeassistant",
name: "Home Assistant configuration",
checked: true,
},
{ slug: "ssl", name: "SSL", checked: true },
{ slug: "share", name: "Share", checked: true },
{ slug: "addons/local", name: "Local add-ons", checked: true },
],
},
snapshotSlug: {
@@ -141,7 +145,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
snapshotDeleted: {
type: Boolean,
notify: true,
observer: '_snapshotDeletedChanged',
observer: "_snapshotDeletedChanged",
},
creatingSnapshot: Boolean,
dialogOpened: Boolean,
@@ -151,7 +155,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this._apiCalled(ev));
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
this._updateSnapshots();
}
@@ -162,57 +166,70 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
}
_updateSnapshots() {
this.hass.callApi('get', 'hassio/snapshots')
.then((result) => {
this.hass.callApi("get", "hassio/snapshots").then(
(result) => {
this.snapshots = result.data.snapshots;
}, (error) => {
},
(error) => {
this.error = error.message;
});
}
);
}
_createSnapshot() {
this.error = '';
this.error = "";
if (this.snapshotHasPassword && !this.snapshotPassword.length) {
this.error = 'Please enter a password.';
this.error = "Please enter a password.";
return;
}
this.creatingSnapshot = true;
let name = this.snapshotName;
if (!name.length) {
name = new Date().toLocaleDateString(navigator.language, {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric' });
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
});
}
let data;
let path;
if (this.snapshotType === 'full') {
if (this.snapshotType === "full") {
data = { name: name };
path = 'hassio/snapshots/new/full';
path = "hassio/snapshots/new/full";
} else {
const addons = this.addonList.filter(addon => addon.checked).map(addon => addon.slug);
const folders = this.folderList.filter(folder => folder.checked).map(folder => folder.slug);
const addons = this.addonList
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this.folderList
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
data = { name: name, folders: folders, addons: addons };
path = 'hassio/snapshots/new/partial';
path = "hassio/snapshots/new/partial";
}
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
}
this.hass.callApi('post', path, data)
.then(() => {
this.hass.callApi("post", path, data).then(
() => {
this.creatingSnapshot = false;
this.fire('hass-api-called', { success: true });
}, (error) => {
this.fire("hass-api-called", { success: true });
},
(error) => {
this.creatingSnapshot = false;
this.error = error.message;
});
}
);
}
_installedAddonsChanged(addons) {
this.addonList = addons.map(addon => ({ slug: addon.slug, name: addon.name, checked: true }));
this.addonList = addons.map((addon) => ({
slug: addon.slug,
name: addon.name,
checked: true,
}));
}
_sortAddons(a, b) {
@@ -228,12 +245,15 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
}
_computeDetails(snapshot) {
const type = snapshot.type === 'full' ? 'Full snapshot' : 'Partial snapshot';
const type =
snapshot.type === "full" ? "Full snapshot" : "Partial snapshot";
return snapshot.protected ? `${type}, password protected` : type;
}
_computeIcon(type) {
return type === 'full' ? 'hassio:package-variant-closed' : 'hassio:package-variant';
return type === "full"
? "hassio:package-variant-closed"
: "hassio:package-variant";
}
_snapshotClicked(ev) {
@@ -241,7 +261,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
}
_fullSelected(type) {
return type === 'full';
return type === "full";
}
_snapshotDeletedChanged(snapshotDeleted) {
@@ -252,11 +272,10 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
}
refreshData() {
this.hass.callApi('post', 'hassio/snapshots/reload')
.then(() => {
this._updateSnapshots();
});
this.hass.callApi("post", "hassio/snapshots/reload").then(() => {
this._updateSnapshots();
});
}
}
customElements.define('hassio-snapshots', HassioSnapshots);
customElements.define("hassio-snapshots", HassioSnapshots);

View File

@@ -1,10 +1,10 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../../../src/components/buttons/ha-call-api-button.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
class HassioHostInfo extends EventsMixin(PolymerElement) {
static get template() {
@@ -17,6 +17,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
}
.card-content {
height: 200px;
color: var(--primary-text-color);
}
@media screen and (max-width: 830px) {
paper-card {
@@ -96,16 +97,16 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
hass: Object,
data: {
type: Object,
observer: '_dataChanged'
observer: "_dataChanged",
},
errors: String,
_hassOs: Object
_hassOs: Object,
};
}
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
apiCalled(ev) {
@@ -116,19 +117,18 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
var response = ev.detail.response;
if (typeof response.body === 'object') {
this.errors = response.body.message || 'Unknown error';
if (typeof response.body === "object") {
this.errors = response.body.message || "Unknown error";
} else {
this.errors = response.body;
}
}
_dataChanged(data) {
if (data.features && data.features.includes('hassos')) {
this.hass.callApi('get', 'hassio/hassos/info')
.then((resp) => {
this._hassOs = resp.data;
});
if (data.features && data.features.includes("hassos")) {
this.hass.callApi("get", "hassio/hassos/info").then((resp) => {
this._hassOs = resp.data;
});
} else {
this._hassOs = {};
}
@@ -143,28 +143,31 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
}
_showHardware() {
this.hass.callApi('get', 'hassio/hardware/info')
this.hass
.callApi("get", "hassio/hardware/info")
.then(
resp => this._objectToMarkdown(resp.data)
, () => 'Error getting hardware info'
).then((content) => {
this.fire('hassio-markdown-dialog', {
title: 'Hardware',
(resp) => this._objectToMarkdown(resp.data),
() => "Error getting hardware info"
)
.then((content) => {
this.fire("hassio-markdown-dialog", {
title: "Hardware",
content: content,
});
});
}
_objectToMarkdown(obj, indent = '') {
let data = '';
_objectToMarkdown(obj, indent = "") {
let data = "";
Object.keys(obj).forEach((key) => {
if (typeof obj[key] !== 'object') {
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';
data +=
`${indent} - ` + obj[key].join(`\n${indent} - `) + "\n";
}
} else {
data += this._objectToMarkdown(obj[key], ` ${indent}`);
@@ -176,11 +179,11 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
_changeHostnameClicked() {
const curHostname = this.data.hostname;
const hostname = prompt('Please enter a new hostname:', curHostname);
const hostname = prompt("Please enter a new hostname:", curHostname);
if (hostname && hostname !== curHostname) {
this.hass.callApi('post', 'hassio/host/options', { hostname });
this.hass.callApi("post", "hassio/host/options", { hostname });
}
}
}
customElements.define('hassio-host-info', HassioHostInfo);
customElements.define("hassio-host-info", HassioHostInfo);

View File

@@ -1,10 +1,10 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../../../src/components/buttons/ha-call-api-button.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../../../src/components/buttons/ha-call-api-button.js";
import EventsMixin from "../../../src/mixins/events-mixin.js";
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
static get template() {
@@ -16,6 +16,7 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
}
.card-content {
height: 200px;
color: var(--primary-text-color);
}
@media screen and (max-width: 830px) {
paper-card {
@@ -84,14 +85,14 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
errors: String,
leaveBeta: {
type: Object,
value: { channel: 'stable' },
value: { channel: "stable" },
},
};
}
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
apiCalled(ev) {
@@ -102,8 +103,8 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
var response = ev.detail.response;
if (typeof response.body === 'object') {
this.errors = response.body.message || 'Unknown error';
if (typeof response.body === "object") {
this.errors = response.body.message || "Unknown error";
} else {
this.errors = response.body;
}
@@ -118,18 +119,20 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
}
_joinBeta() {
if (!confirm(`WARNING:
if (
!confirm(`WARNING:
Beta releases are for testers and early adopters and can contain unstable code changes. Make sure you have backups of your data before you activate this feature.
This inludes beta releases for:
- Home Assistant (Release Candidates)
- Hass.io supervisor
- Host system`)) {
- Host system`)
) {
return;
}
const method = 'post';
const path = 'hassio/supervisor/options';
const data = { channel: 'beta' };
const method = "post";
const path = "hassio/supervisor/options";
const data = { channel: "beta" };
const eventData = {
method: method,
@@ -137,17 +140,22 @@ This inludes beta releases for:
data: data,
};
this.hass.callApi(method, path, data)
.then((resp) => {
eventData.success = true;
eventData.response = resp;
}, (resp) => {
eventData.success = false;
eventData.response = resp;
}).then(() => {
this.fire('hass-api-called', eventData);
this.hass
.callApi(method, path, data)
.then(
(resp) => {
eventData.success = true;
eventData.response = resp;
},
(resp) => {
eventData.success = false;
eventData.response = resp;
}
)
.then(() => {
this.fire("hass-api-called", eventData);
});
}
}
customElements.define('hassio-supervisor-info', HassioSupervisorInfo);
customElements.define("hassio-supervisor-info", HassioSupervisorInfo);

View File

@@ -1,7 +1,7 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
class HassioSupervisorLog extends PolymerElement {
static get template() {
@@ -38,12 +38,14 @@ class HassioSupervisorLog extends PolymerElement {
}
loadData() {
this.hass.callApi('get', 'hassio/supervisor/logs')
.then((info) => {
this.hass.callApi("get", "hassio/supervisor/logs").then(
(info) => {
this.log = info;
}, () => {
this.log = 'Error fetching logs';
});
},
() => {
this.log = "Error fetching logs";
}
);
}
refreshTapped() {
@@ -51,4 +53,4 @@ class HassioSupervisorLog extends PolymerElement {
}
}
customElements.define('hassio-supervisor-log', HassioSupervisorLog);
customElements.define("hassio-supervisor-log", HassioSupervisorLog);

View File

@@ -1,10 +1,10 @@
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-flex-layout/iron-flex-layout-classes.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import './hassio-host-info.js';
import './hassio-supervisor-info.js';
import './hassio-supervisor-log.js';
import "./hassio-host-info.js";
import "./hassio-supervisor-info.js";
import "./hassio-supervisor-log.js";
class HassioSystem extends PolymerElement {
static get template() {
@@ -40,4 +40,4 @@ class HassioSystem extends PolymerElement {
}
}
customElements.define('hassio-system', HassioSystem);
customElements.define("hassio-system", HassioSystem);

View File

@@ -1,69 +1,54 @@
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const config = require('./config.js');
const config = require("./config.js");
const { babelLoaderConfig } = require("../config/babel.js");
const isProdBuild = process.env.NODE_ENV === 'production'
const chunkFilename = isProdBuild ?
'chunk.[chunkhash].js' : '[name].chunk.js';
const isProdBuild = process.env.NODE_ENV === "production";
const chunkFilename = isProdBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
module.exports = {
mode: isProdBuild ? 'production' : 'development',
devtool: isProdBuild ? 'source-map' : 'inline-source-map',
mode: isProdBuild ? "production" : "development",
devtool: isProdBuild ? "source-map" : "inline-source-map",
entry: {
entrypoint: './src/entrypoint.js',
entrypoint: "./src/entrypoint.js",
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[require('babel-preset-env').default, { modules: false }]
],
plugins: [
// Only support the syntax, Webpack will handle it.
"syntax-dynamic-import",
],
},
},
},
babelLoaderConfig({ latestBuild: false }),
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
loader: "html-loader",
options: {
exportAsEs6Default: true,
}
}
}
]
},
},
},
],
},
plugins: [
isProdBuild && new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
}
}),
isProdBuild && new CompressionPlugin({
cache: true,
exclude: [
/\.js\.map$/,
/\.LICENSE$/,
/\.py$/,
/\.txt$/,
]
}),
isProdBuild &&
new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
},
}),
isProdBuild &&
new CompressionPlugin({
cache: true,
exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/],
}),
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json"],
},
output: {
filename: '[name].js',
filename: "[name].js",
chunkFilename,
path: config.buildDir,
publicPath: `${config.publicPath}/`,
}
},
};

View File

@@ -8,98 +8,109 @@
"version": "1.0.0",
"scripts": {
"build": "script/build_frontend",
"lint": "eslint src hassio/src gallery/src test-mocha && polymer lint",
"lint": "eslint src hassio/src gallery/src test-mocha && tslint -c tslint.json 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc",
"mocha": "node_modules/.bin/mocha --opts test-mocha/mocha.opts",
"test": "npm run lint && npm run mocha"
"test": "npm run lint && npm run mocha",
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
"bash": "sh ./script/docker_run.sh bash $npm_package_version"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@mdi/svg": "^2.4.85",
"@polymer/app-layout": "^3.0.0-pre.19",
"@polymer/app-localize-behavior": "^3.0.0-pre.19",
"@polymer/app-route": "^3.0.0-pre.19",
"@polymer/app-storage": "^3.0.0-pre.19",
"@polymer/font-roboto": "^3.0.0-pre.19",
"@polymer/font-roboto-local": "^3.0.0-pre.19",
"@polymer/iron-autogrow-textarea": "^3.0.0-pre.19",
"@polymer/iron-flex-layout": "^3.0.0-pre.19",
"@polymer/iron-icon": "^3.0.0-pre.19",
"@polymer/iron-iconset-svg": "^3.0.0-pre.19",
"@polymer/iron-image": "^3.0.0-pre.19",
"@polymer/iron-input": "^3.0.0-pre.19",
"@polymer/iron-label": "^3.0.0-pre.19",
"@polymer/iron-media-query": "^3.0.0-pre.19",
"@polymer/iron-pages": "^3.0.0-pre.19",
"@polymer/iron-resizable-behavior": "^3.0.0-pre.19",
"@polymer/neon-animation": "^3.0.0-pre.19",
"@polymer/paper-button": "^3.0.0-pre.19",
"@polymer/paper-card": "^3.0.0-pre.19",
"@polymer/paper-checkbox": "^3.0.0-pre.19",
"@polymer/paper-dialog": "^3.0.0-pre.19",
"@polymer/paper-dialog-behavior": "^3.0.0-pre.19",
"@polymer/paper-dialog-scrollable": "^3.0.0-pre.19",
"@polymer/paper-drawer-panel": "^3.0.0-pre.19",
"@polymer/paper-dropdown-menu": "^3.0.0-pre.19",
"@polymer/paper-fab": "^3.0.0-pre.19",
"@polymer/paper-icon-button": "^3.0.0-pre.19",
"@polymer/paper-input": "^3.0.0-pre.19",
"@polymer/paper-item": "^3.0.0-pre.19",
"@polymer/paper-listbox": "^3.0.0-pre.19",
"@polymer/paper-menu-button": "^3.0.0-pre.19",
"@polymer/paper-progress": "^3.0.0-pre.19",
"@polymer/paper-radio-button": "^3.0.0-pre.19",
"@polymer/paper-radio-group": "^3.0.0-pre.19",
"@polymer/paper-ripple": "^3.0.0-pre.19",
"@polymer/paper-scroll-header-panel": "^3.0.0-pre.19",
"@polymer/paper-slider": "^3.0.0-pre.19",
"@polymer/paper-spinner": "^3.0.0-pre.19",
"@polymer/paper-styles": "^3.0.0-pre.19",
"@polymer/paper-tabs": "^3.0.0-pre.19",
"@polymer/paper-toast": "^3.0.0-pre.19",
"@polymer/paper-toggle-button": "^3.0.0-pre.19",
"@polymer/polymer": "^3.0.2",
"@vaadin/vaadin-combo-box": "4.1.0-alpha2",
"@vaadin/vaadin-date-picker": "3.2.0-alpha3",
"@webcomponents/shadycss": "^1.3.1",
"@webcomponents/webcomponentsjs": "^2.0.2",
"@material/mwc-ripple": "^0.3.1",
"@mdi/svg": "^2.7.94",
"@polymer/app-layout": "^3.0.1",
"@polymer/app-localize-behavior": "^3.0.1",
"@polymer/app-route": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
"@polymer/font-roboto": "^3.0.2",
"@polymer/font-roboto-local": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-iconset-svg": "^3.0.1",
"@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-media-query": "^3.0.1",
"@polymer/iron-pages": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/lit-element": "^0.6.2",
"@polymer/neon-animation": "^3.0.1",
"@polymer/paper-button": "^3.0.1",
"@polymer/paper-card": "^3.0.1",
"@polymer/paper-checkbox": "^3.0.1",
"@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-drawer-panel": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-fab": "^3.0.1",
"@polymer/paper-icon-button": "^3.0.1",
"@polymer/paper-input": "^3.0.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-menu-button": "^3.0.1",
"@polymer/paper-progress": "^3.0.1",
"@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-scroll-header-panel": "^3.0.1",
"@polymer/paper-slider": "^3.0.1",
"@polymer/paper-spinner": "^3.0.1",
"@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.0.1",
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-toggle-button": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "^3.0.5",
"@vaadin/vaadin-combo-box": "4.2.0-alpha3",
"@vaadin/vaadin-date-picker": "3.3.0-alpha1",
"@webcomponents/shadycss": "^1.5.2",
"@webcomponents/webcomponentsjs": "^2.1.3",
"chart.js": "~2.7.2",
"chartjs-chart-timeline": "^0.2.1",
"es6-object-assign": "^1.1.0",
"eslint-import-resolver-webpack": "^0.10.0",
"eslint-import-resolver-webpack": "^0.10.1",
"fecha": "^2.3.3",
"home-assistant-js-websocket": "^2.1.0",
"home-assistant-js-websocket": "^3.1.4",
"intl-messageformat": "^2.2.0",
"js-yaml": "^3.12.0",
"leaflet": "^1.3.1",
"marked": "^0.4.0",
"mdn-polyfills": "^5.8.0",
"leaflet": "^1.3.4",
"lit-html": "^0.12.0",
"marked": "^0.5.0",
"mdn-polyfills": "^5.12.0",
"moment": "^2.22.2",
"preact": "^8.2.9",
"preact-compat": "^3.18.0",
"react-big-calendar": "^0.19.1",
"regenerator-runtime": "^0.11.1",
"unfetch": "^3.0.0",
"preact": "^8.3.1",
"preact-compat": "^3.18.4",
"react-big-calendar": "^0.19.2",
"regenerator-runtime": "^0.12.1",
"unfetch": "^4.0.1",
"web-animations-js": "^2.3.1",
"xss": "^1.0.3"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"@babel/core": "^7.1.2",
"@babel/plugin-external-helpers": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-typescript": "7.0.0",
"@gfx/zopfli": "^1.0.9",
"babel-eslint": "^10",
"babel-loader": "^8.0.4",
"babel-minify-webpack-plugin": "^0.3.1",
"chai": "^4.1.2",
"compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.5.1",
"compression-webpack-plugin": "^2.0.0",
"copy-webpack-plugin": "^4.5.2",
"del": "^3.0.0",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-react": "^7.9.1",
"eslint": "^5.6.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^3.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.11.1",
"gulp": "^3.9.1",
"gulp-foreach": "^0.1.0",
"gulp-hash": "^4.2.2",
@@ -107,36 +118,65 @@
"gulp-json-transform": "^0.4.5",
"gulp-jsonminify": "^1.1.0",
"gulp-merge-json": "^1.3.1",
"gulp-rename": "^1.3.0",
"gulp-rename": "^1.4.0",
"html-loader": "^0.5.5",
"html-minifier": "^3.5.16",
"html-minifier": "^3.5.20",
"html-webpack-plugin": "^3.2.0",
"husky": "^1.1.0",
"lint-staged": "^7.3.0",
"merge-stream": "^1.0.1",
"mocha": "^5.2.0",
"parse5": "^5.0.0",
"polymer-analyzer": "^3.0.1",
"polymer-bundler": "^4.0.1",
"polymer-cli": "^1.7.4",
"reify": "^0.16.2",
"parse5": "^5.1.0",
"polymer-analyzer": "^3.1.2",
"polymer-bundler": "^4.0.2",
"polymer-cli": "^1.8.0",
"prettier": "^1.14.3",
"raw-loader": "^0.5.1",
"reify": "^0.17.3",
"require-dir": "^1.0.0",
"sinon": "^6.0.0",
"uglifyjs-webpack-plugin": "^1.2.6",
"sinon": "^6.3.4",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"tslint-eslint-rules": "^5.4.0",
"typescript": "3.1.3",
"wct-browser-legacy": "^1.0.1",
"web-component-tester": "^6.7.0",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"workbox-webpack-plugin": "^3.3.0"
"web-component-tester": "^6.8.0",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.8",
"workbox-webpack-plugin": "^3.5.0"
},
"resolutions": {
"inherits": "2.0.3",
"samsam": "1.1.3",
"supports-color": "3.1.2",
"type-detect": "1.0.0",
"@webcomponents/webcomponentsjs": "2.0.2",
"@webcomponents/shadycss": "^1.3.1",
"@vaadin/vaadin-overlay": "3.0.2-pre.2",
"@polymer/polymer": "3.0.5",
"@webcomponents/webcomponentsjs": "2.1.3",
"@webcomponents/shadycss": "^1.5.2",
"@vaadin/vaadin-overlay": "3.2.0-alpha3",
"@vaadin/vaadin-lumo-styles": "1.2.0",
"fecha": "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz"
},
"main": "src/home-assistant.js"
"main": "src/home-assistant.js",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"*.{js,json,css,md}": [
"prettier --write",
"git add"
]
},
"ignore": [
"translations/**"
]
},
"prettier": {
"trailingComma": "es5",
"arrowParens": "always"
}
}

View File

@@ -24,7 +24,7 @@
],
"lint": {
"rules": ["polymer-3"],
"ignoreWarnings": ["could-not-resolve-reference"],
"ignoreWarnings": ["could-not-resolve-reference", "could-not-load"],
"filesToIgnore": [
"**/*.html",
"**/src/panels/config/js/**/*.js",

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# Docker entry point inspired by travis build and script/build_frontend
# Stop on errors
set -e
# Build the frontend but not used the npm run build
/bin/bash script/build_frontend
# TEST
npm run test
#
#xvfb-run wct

103
script/docker_run.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
# Basic Docker Management scripts
# With this script you can build software, or enter an agnostic development environment and run commands interactively.
check_mandatory_tools(){
if [ "x$(which docker)" == "x" ]; then
echo "UNKNOWN - Missing docker binary! Are you sure it is installed and reachable?"
exit 3
fi
docker info > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "UNKNOWN - Unable to talk to the docker daemon! Maybe the docker daemon is not running"
exit 3
fi
}
check_dev_image(){
if [[ "$(docker images -q ${IMAGE_NAME}:$IMAGE_TAG 2> /dev/null)" == "" ]]; then
echo "UNKNOWN - Can't find the development docker image ${IMAGE_NAME}:$IMAGE_TAG"
while true; do
read -p "Do you want to create it now?" yn
case $yn in
[Yy]* ) create_image; break;;
[Nn]* ) exit 3;;
* ) echo "Please answer y or n";;
esac
done
fi
}
# Building the basic image for compiling the production frontend
create_image(){
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
}
#
# Execute interactive bash on basic image
#
run_bash_on_docker(){
check_dev_image
docker run -it \
-v $PWD/:/frontend/ \
-v /frontend/node_modules \
-v /frontend/bower_components \
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash
}
#
# Execute the basic image for compiling the production frontend
#
build_all(){
check_dev_image
docker run -it \
-v $PWD/:/frontend/ \
-v /frontend/node_modules \
-v /frontend/bower_components \
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash script/build_frontend
}
# Init Global Variable
IMAGE_NAME=home_assistant_fe_image
IMAGE_TAG=${2:-latest}
check_mandatory_tools
case "$1" in
setup_env)
create_image
;;
bash)
run_bash_on_docker
;;
build)
build_all
;;
*)
echo "NAME"
echo " Docker Management."
echo ""
echo "SYNOPSIS"
echo " ${0} command [version]"
echo ""
echo "DESCRIPTION"
echo " With this script you can build software, or enter an agnostic development environment and run commands interactively."
echo ""
echo " The command are:"
echo " setup_env Create develop images"
echo " bash Run bash on develop enviroments"
echo " build Run silent build"
echo ""
echo " The version is optional, if not inserted it assumes \"latest\". "
exit 1
;;
esac
exit 0

View File

@@ -28,7 +28,7 @@ mkdir -p ${LOCAL_DIR}
docker run \
-v ${LOCAL_DIR}:/opt/dest/locale \
lokalise/lokalise-cli@sha256:ddf5677f58551261008342df5849731c88bcdc152ab645b133b21819aede8218 lokalise \
lokalise/lokalise-cli@sha256:b8329d20280263cad04f65b843e54b9e8e6909a348a678eac959550b5ef5c75f lokalise \
--token ${LOKALISE_TOKEN} \
export ${PROJECT_ID} \
--export_empty skip \

View File

@@ -1,20 +1,22 @@
from setuptools import setup, find_packages
setup(name='home-assistant-frontend',
version='20180826.0',
description='The Home Assistant frontend',
url='https://github.com/home-assistant/home-assistant-polymer',
author='The Home Assistant Authors',
author_email='hello@home-assistant.io',
license='Apache License 2.0',
packages=find_packages(include=[
'hass_frontend',
'hass_frontend_es5',
'hass_frontend.*',
'hass_frontend_es5.*'
]),
install_requires=[
'user-agents==1.1.0',
],
include_package_data=True,
zip_safe=False)
setup(
name="home-assistant-frontend",
version="20181024.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
author_email="hello@home-assistant.io",
license="Apache License 2.0",
packages=find_packages(
include=[
"hass_frontend",
"hass_frontend_es5",
"hass_frontend.*",
"hass_frontend_es5.*",
]
),
install_requires=["user-agents==1.1.0"],
include_package_data=True,
zip_safe=False,
)

View File

@@ -1,94 +1,151 @@
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '@polymer/paper-button/paper-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import '../components/ha-form.js';
import EventsMixin from '../mixins/events-mixin.js';
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import "@polymer/paper-button/paper-button.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import "../components/ha-form.js";
import LocalizeLiteMixin from "../mixins/localize-lite-mixin.js";
/*
* @appliesMixin EventsMixin
*/
class HaAuthFlow extends EventsMixin(PolymerElement) {
class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
static get template() {
return html`
<style>
:host {
/* So we can set min-height to avoid jumping during loading */
display: block;
}
.action {
margin: 32px 0;
margin: 24px 0 8px;
text-align: center;
}
.error {
color: red;
}
</style>
<template is="dom-if" if="[[_equals(_state, &quot;loading&quot;)]]">
Please wait
</template>
<template is="dom-if" if="[[_equals(_state, &quot;error&quot;)]]">
Something went wrong
</template>
<template is="dom-if" if="[[_equals(_state, &quot;step&quot;)]]">
<template is="dom-if" if="[[_equals(_step.type, &quot;abort&quot;)]]">
Aborted
<form>
<template is="dom-if" if="[[_equals(_state, &quot;loading&quot;)]]">
[[localize('ui.panel.page-authorize.form.working')]]:
</template>
<template is="dom-if" if="[[_equals(_step.type, &quot;create_entry&quot;)]]">
Success!
<template is="dom-if" if="[[_equals(_state, &quot;error&quot;)]]">
<div class='error'>Error: [[_errorMsg]]</div>
</template>
<template is="dom-if" if="[[_equals(_step.type, &quot;form&quot;)]]">
<ha-form data="{{_stepData}}" schema="[[_step.data_schema]]" error="[[_step.errors]]"></ha-form>
<template is="dom-if" if="[[_equals(_state, &quot;step&quot;)]]">
<template is="dom-if" if="[[_equals(_step.type, &quot;abort&quot;)]]">
[[localize('ui.panel.page-authorize.abort_intro')]]:
<ha-markdown content="[[_computeStepAbortedReason(localize, _step)]]"></ha-markdown>
</template>
<template is="dom-if" if="[[_equals(_step.type, &quot;form&quot;)]]">
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
<ha-markdown content="[[_computeStepDescription(localize, _step)]]" allow-svg></ha-markdown>
</template>
<ha-form
data="{{_stepData}}"
schema="[[_step.data_schema]]"
error="[[_step.errors]]"
compute-label="[[_computeLabelCallback(localize, _step)]]"
compute-error="[[_computeErrorCallback(localize, _step)]]"
></ha-form>
</template>
<div class='action'>
<paper-button
raised
on-click='_handleSubmit'
>[[_computeSubmitCaption(_step.type)]]</paper-button>
</div>
</template>
<div class='action'>
<paper-button raised on-click="_handleSubmit">[[_computeSubmitCaption(_step.type)]]</paper-button>
</div>
</template>
</form>
`;
}
static get properties() {
return {
authProvider: Object,
authProvider: {
type: Object,
observer: "_providerChanged",
},
clientId: String,
redirectUri: String,
oauth2State: String,
_state: {
type: String,
value: 'loading'
value: "loading",
},
_stepData: {
type: Object,
value: () => ({}),
},
_step: Object,
_step: {
type: Object,
notify: true,
},
_errorMsg: String,
};
}
async ready() {
ready() {
super.ready();
this.addEventListener('keypress', (ev) => {
this.addEventListener("keypress", (ev) => {
if (ev.keyCode === 13) {
this._handleSubmit();
}
});
}
connectedCallback() {
super.connectedCallback();
async _providerChanged(newProvider, oldProvider) {
if (oldProvider && this._step && this._step.type === "form") {
fetch(`/auth/login_flow/${this._step.flow_id}`, {
method: "DELETE",
credentials: "same-origin",
}).catch(() => {});
}
fetch('/auth/login_flow', {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
client_id: this.clientId,
handler: [this.authProvider.type, this.authProvider.id],
redirect_uri: this.redirectUri,
})
}).then((response) => {
if (!response.ok) throw new Error();
return response.json();
}).then(step => this.setProperties({
_step: step,
_state: 'step',
})).catch((err) => {
try {
const response = await fetch("/auth/login_flow", {
method: "POST",
credentials: "same-origin",
body: JSON.stringify({
client_id: this.clientId,
handler: [newProvider.type, newProvider.id],
redirect_uri: this.redirectUri,
}),
});
const data = await response.json();
if (response.ok) {
this._updateStep(data);
} else {
this.setProperties({
_state: "error",
_errorMsg: data.message,
});
}
} catch (err) {
// eslint-disable-next-line
console.error('Error starting auth flow', err);
this._state = 'error';
});
console.error("Error starting auth flow", err);
this.setProperties({
_state: "error",
_errorMsg: this.localize("ui.panel.page-authorize.form.unknown_error"),
});
}
}
_updateStep(step) {
const props = {
_step: step,
_state: "step",
};
if (
this._step &&
(step.flow_id !== this._step.flow_id ||
step.step_id !== this._step.step_id)
) {
props._stepData = {};
}
this.setProperties(props);
}
_equals(a, b) {
@@ -96,35 +153,80 @@ class HaAuthFlow extends EventsMixin(PolymerElement) {
}
_computeSubmitCaption(stepType) {
return stepType === 'form' ? 'Next' : 'Start over';
return stepType === "form" ? "Next" : "Start over";
}
_handleSubmit() {
if (this._step.type !== 'form') {
this.fire('reset');
_computeStepAbortedReason(localize, step) {
return localize(
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${
step.reason
}`
);
}
_computeStepDescription(localize, step) {
const args = [
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${
step.step_id
}.description`,
];
const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => {
args.push(key);
args.push(placeholders[key]);
});
return localize(...args);
}
_computeLabelCallback(localize, step) {
// Returns a callback for ha-form to calculate labels per schema object
return (schema) =>
localize(
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${
step.step_id
}.data.${schema.name}`
);
}
_computeErrorCallback(localize, step) {
// Returns a callback for ha-form to calculate error messages
return (error) =>
localize(
`ui.panel.page-authorize.form.providers.${
step.handler[0]
}.error.${error}`
);
}
async _handleSubmit() {
if (this._step.type !== "form") {
this._providerChanged(this.authProvider, null);
return;
}
this._state = 'loading';
this._state = "loading";
// To avoid a jumping UI.
this.style.setProperty("min-height", `${this.offsetHeight}px`);
const postData = Object.assign({}, this._stepData, {
client_id: this.clientId,
});
fetch(`/auth/login_flow/${this._step.flow_id}`, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify(postData)
}).then((response) => {
if (!response.ok) throw new Error();
return response.json();
}).then((newStep) => {
if (newStep.type === 'create_entry') {
try {
const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, {
method: "POST",
credentials: "same-origin",
body: JSON.stringify(postData),
});
const newStep = await response.json();
if (newStep.type === "create_entry") {
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
let url = this.redirectUri;
if (!url.includes('?')) {
url += '?';
} else if (!url.endsWith('&')) {
url += '&';
if (!url.includes("?")) {
url += "?";
} else if (!url.endsWith("&")) {
url += "&";
}
url += `code=${encodeURIComponent(newStep.result)}`;
@@ -136,20 +238,14 @@ class HaAuthFlow extends EventsMixin(PolymerElement) {
document.location = url;
return;
}
const props = {
_step: newStep,
_state: 'step',
};
if (newStep.step_id !== this._step.step_id) {
props._stepData = {};
}
this.setProperties(props);
}).catch((err) => {
this._updateStep(newStep);
} catch (err) {
// eslint-disable-next-line
console.error('Error loading auth providers', err);
this._state = 'error-loading';
});
console.error("Error submitting step", err);
this._state = "error-loading";
} finally {
this.style.setProperty("min-height", "");
}
}
}
customElements.define('ha-auth-flow', HaAuthFlow);
customElements.define("ha-auth-flow", HaAuthFlow);

View File

@@ -1,81 +1,82 @@
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
import '@polymer/polymer/lib/elements/dom-if.js';
import '@polymer/polymer/lib/elements/dom-repeat.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/polymer/lib/elements/dom-if.js";
import "@polymer/polymer/lib/elements/dom-repeat.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../auth/ha-auth-flow.js';
import '../auth/ha-pick-auth-provider.js';
import "../components/ha-markdown.js";
class HaAuthorize extends PolymerElement {
import LocalizeLiteMixin from "../mixins/localize-lite-mixin.js";
import "./ha-auth-flow.js";
class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-positioning"></style>
<style>
.content {
padding: 20px 16px;
max-width: 360px;
margin: 0 auto;
ha-markdown {
display: block;
margin-bottom: 16px;
}
.header {
text-align: center;
font-size: 1.96em;
display: flex;
align-items: center;
justify-content: center;
font-weight: 300;
ha-markdown a {
color: var(--primary-color);
}
.header img {
margin-right: 16px;
ha-markdown p:last-child{
margin-bottom: 0;
}
ha-pick-auth-provider {
display: block;
margin-top: 48px;
}
</style>
<div class="content layout vertical fit">
<div class='header'>
<img src="/static/icons/favicon-192x192.png" height="52">
Home Assistant
</div>
<p>Logging in to <b>[[clientId]]</b>.</p>
<template is="dom-if" if="[[!_authProviders]]">
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
</template>
<template is="dom-if" if="[[_authProvider]]">
<ha-auth-flow
client-id="[[clientId]]"
redirect-uri="[[redirectUri]]"
oauth2-state="[[oauth2State]]"
auth-provider="[[_authProvider]]"
on-reset="_handleReset"
></ha-auth-flow>
</template>
<template is="dom-if" if="[[!_authProvider]]">
<template is="dom-if" if="[[_authProviders]]">
<ha-markdown content='[[_computeIntro(localize, clientId, _authProvider)]]'></ha-markdown>
<ha-auth-flow
resources="[[resources]]"
client-id="[[clientId]]"
redirect-uri="[[redirectUri]]"
oauth2-state="[[oauth2State]]"
auth-provider="[[_authProvider]]"
step="{{step}}"
></ha-auth-flow>
<template is="dom-if" if="[[_computeMultiple(_authProviders)]]">
<ha-pick-auth-provider
resources="[[resources]]"
client-id="[[clientId]]"
auth-providers="[[_computeInactiveProvders(_authProvider, _authProviders)]]"
on-pick="_handleAuthProviderPick"
></ha-pick-auth-provider>
</template>
</div>
</template>
`;
}
static get properties() {
return {
_authProvider: {
type: String,
value: null,
},
_authProvider: String,
_authProviders: Array,
clientId: String,
redirectUri: String,
oauth2State: String,
translationFragment: {
type: String,
value: "page-authorize",
},
};
}
ready() {
async ready() {
super.ready();
const query = {};
const values = location.search.substr(1).split('&');
const values = location.search.substr(1).split("&");
for (let i = 0; i < values.length; i++) {
const value = values[i].split('=');
const value = values[i].split("=");
if (value.length > 1) {
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
}
@@ -85,12 +86,67 @@ class HaAuthorize extends PolymerElement {
if (query.redirect_uri) props.redirectUri = query.redirect_uri;
if (query.state) props.oauth2State = query.state;
this.setProperties(props);
import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider.js");
// Fetch auth providers
try {
const response = await window.providersPromise;
const authProviders = await response.json();
// Forward to main screen which will redirect to right onboarding page.
if (
response.status === 400 &&
authProviders.code === "onboarding_required"
) {
location.href = "/";
return;
}
if (authProviders.length === 0) {
alert("No auth providers returned. Unable to finish login.");
return;
}
this.setProperties({
_authProviders: authProviders,
_authProvider: authProviders[0],
});
} catch (err) {
// eslint-disable-next-line
console.error("Error loading auth providers", err);
this._state = "error-loading";
}
}
_handleAuthProviderPick(ev) {
_computeMultiple(array) {
return array && array.length > 1;
}
async _handleAuthProviderPick(ev) {
this._authProvider = ev.detail;
}
_handleReset() {
this._authProvider = null;
_computeInactiveProvders(curProvider, providers) {
return providers.filter(
(prv) => prv.type !== curProvider.type || prv.id !== curProvider.id
);
}
_computeIntro(localize, clientId, authProvider) {
return (
localize(
"ui.panel.page-authorize.authorizing_client",
"clientId",
clientId
) +
"\n\n" +
localize(
"ui.panel.page-authorize.logging_in_with",
"authProviderName",
authProvider.name
)
);
}
}
customElements.define('ha-authorize', HaAuthorize);
customElements.define("ha-authorize", HaAuthorize);

View File

@@ -1,14 +1,17 @@
import '@polymer/paper-item/paper-item.js';
import '@polymer/paper-item/paper-item-body.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-item/paper-item-body.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import EventsMixin from '../mixins/events-mixin.js';
import EventsMixin from "../mixins/events-mixin.js";
import LocalizeLiteMixin from "../mixins/localize-lite-mixin.js";
/*
* @appliesMixin EventsMixin
*/
class HaPickAuthProvider extends EventsMixin(PolymerElement) {
class HaPickAuthProvider extends EventsMixin(
LocalizeLiteMixin(PolymerElement)
) {
static get template() {
return html`
<style>
@@ -19,23 +22,12 @@ class HaPickAuthProvider extends EventsMixin(PolymerElement) {
margin-top: 0;
}
</style>
<template is="dom-if" if="[[_equal(_state, &quot;loading&quot;)]]">
Loading auth providers.
</template>
<template is="dom-if" if="[[_equal(_state, &quot;no-results&quot;)]]">
No auth providers found.
</template>
<template is="dom-if" if="[[_equal(_state, &quot;error-loading&quot;)]]">
Error loading
</template>
<template is="dom-if" if="[[_equal(_state, &quot;pick&quot;)]]">
<p>Pick an auth provider to log in with:</p>
<template is="dom-repeat" items="[[authProviders]]">
<paper-item on-click="_handlePick">
<paper-item-body>[[item.name]]</paper-item-body>
<iron-icon icon="hass:chevron-right"></iron-icon>
</paper-item>
</template>
<p>[[localize('ui.panel.page-authorize.pick_auth_provider')]]:</p>
<template is="dom-repeat" items="[[authProviders]]">
<paper-item on-click="_handlePick">
<paper-item-body>[[item.name]]</paper-item-body>
<iron-icon icon="hass:chevron-right"></iron-icon>
</paper-item>
</template>
`;
}
@@ -44,39 +36,18 @@ class HaPickAuthProvider extends EventsMixin(PolymerElement) {
return {
_state: {
type: String,
value: 'loading'
value: "loading",
},
authProviders: Array,
clientId: String,
};
}
connectedCallback() {
super.connectedCallback();
fetch('/auth/providers', { credentials: 'same-origin' }).then((response) => {
if (!response.ok) throw new Error();
return response.json();
}).then((authProviders) => {
this.setProperties({
authProviders,
_state: 'pick',
});
if (authProviders.length === 1) {
this.fire('pick', authProviders[0]);
}
}).catch((err) => {
// eslint-disable-next-line
console.error('Error loading auth providers', err);
this._state = 'error-loading';
});
}
_handlePick(ev) {
this.fire('pick', ev.model.item);
this.fire("pick", ev.model.item);
}
_equal(a, b) {
return a === b;
}
}
customElements.define('ha-pick-auth-provider', HaPickAuthProvider);
customElements.define("ha-pick-auth-provider", HaPickAuthProvider);

View File

@@ -1,7 +1,7 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/entity/ha-state-label-badge.js';
import "../components/entity/ha-state-label-badge.js";
class HaBadgesCard extends PolymerElement {
static get template() {
@@ -25,4 +25,4 @@ class HaBadgesCard extends PolymerElement {
};
}
}
customElements.define('ha-badges-card', HaBadgesCard);
customElements.define("ha-badges-card", HaBadgesCard);

View File

@@ -1,11 +1,10 @@
import '@polymer/paper-styles/element-styles/paper-material-styles.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-styles/element-styles/paper-material-styles.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import computeStateName from '../common/entity/compute_state_name.js';
import EventsMixin from '../mixins/events-mixin.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import computeStateName from "../common/entity/compute_state_name.js";
import EventsMixin from "../mixins/events-mixin.js";
import LocalizeMixin from "../mixins/localize-mixin.js";
const UPDATE_INTERVAL = 10000; // ms
/*
@@ -67,11 +66,11 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
hass: Object,
stateObj: {
type: Object,
observer: 'updateCameraFeedSrc',
observer: "updateCameraFeedSrc",
},
cameraFeedSrc: {
type: String,
value: '',
value: "",
},
imageLoaded: {
type: Boolean,
@@ -82,7 +81,7 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
ready() {
super.ready();
this.addEventListener('click', () => this.cardTapped());
this.addEventListener("click", () => this.cardTapped());
}
connectedCallback() {
@@ -96,13 +95,13 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
cardTapped() {
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
}
async updateCameraFeedSrc() {
try {
const { content_type: contentType, content } = await this.hass.callWS({
type: 'camera_thumbnail',
type: "camera_thumbnail",
entity_id: this.stateObj.entity_id,
});
this.setProperties({
@@ -118,4 +117,4 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
return computeStateName(stateObj);
}
}
customElements.define('ha-camera-card', HaCameraCard);
customElements.define("ha-camera-card", HaCameraCard);

View File

@@ -1,28 +1,29 @@
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import './ha-camera-card.js';
import './ha-entities-card.js';
import './ha-history_graph-card.js';
import './ha-media_player-card.js';
import './ha-persistent_notification-card.js';
import './ha-plant-card.js';
import './ha-weather-card.js';
import "./ha-camera-card.js";
import "./ha-entities-card.js";
import "./ha-history_graph-card.js";
import "./ha-media_player-card.js";
import "./ha-persistent_notification-card.js";
import "./ha-plant-card.js";
import "./ha-weather-card.js";
import dynamicContentUpdater from '../common/dom/dynamic_content_updater.js';
import dynamicContentUpdater from "../common/dom/dynamic_content_updater.js";
class HaCardChooser extends PolymerElement {
static get properties() {
return {
cardData: {
type: Object,
observer: 'cardDataChanged',
observer: "cardDataChanged",
},
};
}
_updateCard(newData) {
dynamicContentUpdater(
this, 'HA-' + newData.cardType.toUpperCase() + '-CARD',
this,
"HA-" + newData.cardType.toUpperCase() + "-CARD",
newData
);
}
@@ -32,7 +33,7 @@ class HaCardChooser extends PolymerElement {
this.observer = new IntersectionObserver((entries) => {
if (!entries.length) return;
if (entries[0].isIntersecting) {
this.style.height = '';
this.style.height = "";
if (this._detachedChild) {
this.appendChild(this._detachedChild);
this._detachedChild = null;
@@ -59,13 +60,13 @@ class HaCardChooser extends PolymerElement {
// ha-entities-card is exempt from observer as it doesn't load heavy resources.
// and usually doesn't load external resources (except for entity_picture).
const eligibleToObserver =
(window.IntersectionObserver && newData.cardType !== 'entities');
window.IntersectionObserver && newData.cardType !== "entities";
if (!eligibleToObserver) {
if (this.observer) {
this.observer.unobserve(this);
this.observer = null;
}
this.style.height = '';
this.style.height = "";
this._updateCard(newData);
return;
}
@@ -77,4 +78,4 @@ class HaCardChooser extends PolymerElement {
}
}
}
customElements.define('ha-card-chooser', HaCardChooser);
customElements.define("ha-card-chooser", HaCardChooser);

View File

@@ -1,18 +1,17 @@
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-flex-layout/iron-flex-layout-classes.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/entity/ha-entity-toggle.js';
import '../components/ha-card.js';
import '../state-summary/state-card-content.js';
import "../components/entity/ha-entity-toggle.js";
import "../components/ha-card.js";
import "../state-summary/state-card-content.js";
import computeStateDomain from '../common/entity/compute_state_domain.js';
import computeStateName from '../common/entity/compute_state_name.js';
import stateMoreInfoType from '../common/entity/state_more_info_type.js';
import canToggleState from '../common/entity/can_toggle_state.js';
import EventsMixin from '../mixins/events-mixin.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import computeStateDomain from "../common/entity/compute_state_domain.js";
import computeStateName from "../common/entity/compute_state_name.js";
import stateMoreInfoType from "../common/entity/state_more_info_type.js";
import canToggleState from "../common/entity/can_toggle_state.js";
import EventsMixin from "../mixins/events-mixin.js";
import LocalizeMixin from "../mixins/localize-mixin.js";
class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
@@ -74,7 +73,7 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
groupEntity: Object,
title: {
type: String,
computed: 'computeTitle(states, groupEntity, localize)',
computed: "computeTitle(states, groupEntity, localize)",
},
};
}
@@ -91,34 +90,40 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
return computeStateName(groupEntity).trim();
}
const domain = computeStateDomain(states[0]);
return (localize && localize(`domain.${domain}`)) || domain.replace(/_/g, ' ');
return (
(localize && localize(`domain.${domain}`)) || domain.replace(/_/g, " ")
);
}
computeTitleClass(groupEntity) {
let classes = 'header horizontal layout center ';
let classes = "header horizontal layout center ";
if (groupEntity) {
classes += 'more-info';
classes += "more-info";
}
return classes;
}
computeStateClass(stateObj) {
return stateMoreInfoType(stateObj) !== 'hidden' ? 'state more-info' : 'state';
return stateMoreInfoType(stateObj) !== "hidden"
? "state more-info"
: "state";
}
addTapEvents() {
const cards = this.root.querySelectorAll('.state');
const cards = this.root.querySelectorAll(".state");
cards.forEach((card) => {
if (card.classList.contains('more-info')) {
card.addEventListener('click', this.entityTapped);
if (card.classList.contains("more-info")) {
card.addEventListener("click", this.entityTapped);
} else {
card.removeEventListener('click', this.entityTapped);
card.removeEventListener("click", this.entityTapped);
}
});
}
entityTapped(ev) {
const item = this.root.querySelector('dom-repeat').itemForElement(ev.target);
const item = this.root
.querySelector("dom-repeat")
.itemForElement(ev.target);
let entityId;
if (!item && !this.groupEntity) {
return;
@@ -130,12 +135,16 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
} else {
entityId = this.groupEntity.entity_id;
}
this.fire('hass-more-info', { entityId: entityId });
this.fire("hass-more-info", { entityId: entityId });
}
showGroupToggle(groupEntity, states) {
if (!groupEntity || !states || groupEntity.attributes.control === 'hidden' ||
(groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
if (
!groupEntity ||
!states ||
groupEntity.attributes.control === "hidden" ||
(groupEntity.state !== "on" && groupEntity.state !== "off")
) {
return false;
}
@@ -156,4 +165,4 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
return canToggleCount > 1;
}
}
customElements.define('ha-entities-card', HaEntitiesCard);
customElements.define("ha-entities-card", HaEntitiesCard);

View File

@@ -1,13 +1,12 @@
import '@polymer/paper-card/paper-card.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-card/paper-card.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/state-history-charts.js';
import '../data/ha-state-history-data.js';
import "../components/state-history-charts.js";
import "../data/ha-state-history-data";
import computeStateName from '../common/entity/compute_state_name.js';
import EventsMixin from '../mixins/events-mixin.js';
import computeStateName from "../common/entity/compute_state_name.js";
import EventsMixin from "../mixins/events-mixin.js";
/*
* @appliesMixin EventsMixin
@@ -55,7 +54,7 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
hass: Object,
stateObj: {
type: Object,
observer: 'stateObjObserver',
observer: "stateObjObserver",
},
inDialog: {
type: Boolean,
@@ -76,14 +75,19 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
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 = Object.assign({}, {
refresh: stateObj.attributes.refresh || 0,
cacheKey: stateObj.entity_id,
hoursToShow: stateObj.attributes.hours_to_show || 24
});
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 = Object.assign(
{},
{
refresh: stateObj.attributes.refresh || 0,
cacheKey: stateObj.entity_id,
hoursToShow: stateObj.attributes.hours_to_show || 24,
}
);
}
}
@@ -92,7 +96,7 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
}
computeContentClass(inDialog) {
return inDialog ? '' : 'content';
return inDialog ? "" : "content";
}
computeHistoryEntities(stateObj) {
@@ -104,11 +108,11 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
}
cardTapped(ev) {
const mq = window.matchMedia('(min-width: 610px) and (min-height: 550px)');
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 });
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
}
}
}
customElements.define('ha-history_graph-card', HaHistoryGraphCard);
customElements.define("ha-history_graph-card", HaHistoryGraphCard);

View File

@@ -1,15 +1,15 @@
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/paper-progress/paper-progress.js';
import '@polymer/paper-styles/element-styles/paper-material-styles.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/iron-flex-layout/iron-flex-layout-classes.js";
import "@polymer/paper-icon-button/paper-icon-button.js";
import "@polymer/paper-progress/paper-progress.js";
import "@polymer/paper-styles/element-styles/paper-material-styles.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import HassMediaPlayerEntity from '../util/hass-media-player-model.js';
import HassMediaPlayerEntity from "../util/hass-media-player-model.js";
import computeStateName from '../common/entity/compute_state_name.js';
import EventsMixin from '../mixins/events-mixin.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import computeStateName from "../common/entity/compute_state_name.js";
import EventsMixin from "../mixins/events-mixin.js";
import LocalizeMixin from "../mixins/localize-mixin.js";
/*
* @appliesMixin LocalizeMixin
@@ -189,12 +189,12 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
stateObj: Object,
playerObj: {
type: Object,
computed: 'computePlayerObj(hass, stateObj)',
observer: 'playerObjChanged',
computed: "computePlayerObj(hass, stateObj)",
observer: "playerObjChanged",
},
playbackControlIcon: {
type: String,
computed: 'computePlaybackControlIcon(playerObj)',
computed: "computePlaybackControlIcon(playerObj)",
},
playbackPosition: Number,
};
@@ -203,7 +203,10 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
async playerObjChanged(playerObj, oldPlayerObj) {
if (playerObj.isPlaying && playerObj.showProgress) {
if (!this._positionTracking) {
this._positionTracking = setInterval(() => this.updatePlaybackPosition(), 1000);
this._positionTracking = setInterval(
() => this.updatePlaybackPosition(),
1000
);
}
} else if (this._positionTracking) {
clearInterval(this._positionTracking);
@@ -214,25 +217,27 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
const picture = playerObj.stateObj.attributes.entity_picture;
const oldPicture = oldPlayerObj && oldPlayerObj.stateObj.attributes.entity_picture;
const oldPicture =
oldPlayerObj && oldPlayerObj.stateObj.attributes.entity_picture;
if (picture !== oldPicture && !picture) {
this.$.cover.style.backgroundImage = '';
this.$.cover.style.backgroundImage = "";
return;
} else if (picture === oldPicture) {
}
if (picture === oldPicture) {
return;
}
// We have a new picture url
try {
const { content_type: contentType, content } = await this.hass.callWS({
type: 'media_player_thumbnail',
type: "media_player_thumbnail",
entity_id: playerObj.stateObj.entity_id,
});
this.$.cover.style.backgroundImage = `url(data:${contentType};base64,${content})`;
} catch (err) {
this.$.cover.style.backgroundImage = '';
this.$.cover.parentElement.classList.add('no-cover');
this.$.cover.style.backgroundImage = "";
this.$.cover.parentElement.classList.add("no-cover");
}
}
@@ -241,14 +246,14 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
computeBannerClasses(playerObj) {
var cls = 'banner';
var cls = "banner";
if (playerObj.isOff || playerObj.isIdle) {
cls += ' is-off no-cover';
cls += " is-off no-cover";
} else if (!playerObj.stateObj.attributes.entity_picture) {
cls += ' no-cover';
} else if (playerObj.stateObj.attributes.media_content_type === 'music') {
cls += ' content-type-music';
cls += " no-cover";
} else if (playerObj.stateObj.attributes.media_content_type === "music") {
cls += " content-type-music";
}
return cls;
@@ -259,7 +264,9 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
computeHidePowerButton(playerObj) {
return playerObj.isOff ? !playerObj.supportsTurnOn : !playerObj.supportsTurnOff;
return playerObj.isOff
? !playerObj.supportsTurnOn
: !playerObj.supportsTurnOff;
}
computePlayerObj(hass, stateObj) {
@@ -267,21 +274,29 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
computePrimaryText(localize, playerObj) {
return playerObj.primaryTitle
|| localize(`state.media_player.${playerObj.stateObj.state}`)
|| localize(`state.default.${playerObj.stateObj.state}`) || playerObj.stateObj.state;
return (
playerObj.primaryTitle ||
localize(`state.media_player.${playerObj.stateObj.state}`) ||
localize(`state.default.${playerObj.stateObj.state}`) ||
playerObj.stateObj.state
);
}
computePlaybackControlIcon(playerObj) {
if (playerObj.isPlaying) {
return playerObj.supportsPause ? 'hass:pause' : 'hass:stop';
} else if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
if (playerObj.hasMediaControl && playerObj.supportsPause && !playerObj.isPaused) {
return 'hass:play-pause';
}
return playerObj.supportsPlay ? 'hass:play' : null;
return playerObj.supportsPause ? "hass:pause" : "hass:stop";
}
return '';
if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
if (
playerObj.hasMediaControl &&
playerObj.supportsPause &&
!playerObj.isPaused
) {
return "hass:play-pause";
}
return playerObj.supportsPlay ? "hass:play" : null;
}
return "";
}
_computeStateName(stateObj) {
@@ -295,7 +310,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
handleOpenMoreInfo(ev) {
ev.stopPropagation();
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
}
handlePlaybackControl(ev) {
@@ -313,4 +328,4 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
this.playerObj.togglePower();
}
}
customElements.define('ha-media_player-card', HaMediaPlayerCard);
customElements.define("ha-media_player-card", HaMediaPlayerCard);

View File

@@ -1,13 +1,13 @@
import '@polymer/paper-button/paper-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import "@polymer/paper-button/paper-button.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/ha-card.js';
import '../components/ha-markdown.js';
import "../components/ha-card.js";
import "../components/ha-markdown.js";
import computeStateName from '../common/entity/compute_state_name.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import computeStateName from "../common/entity/compute_state_name.js";
import LocalizeMixin from "../mixins/localize-mixin.js";
import computeObjectId from "../common/entity/compute_object_id";
/*
* @appliesMixin LocalizeMixin
@@ -59,13 +59,17 @@ class HaPersistentNotificationCard extends LocalizeMixin(PolymerElement) {
}
computeTitle(stateObj) {
return (stateObj.attributes.title ||
computeStateName(stateObj));
return stateObj.attributes.title || computeStateName(stateObj);
}
dismissTap(ev) {
ev.preventDefault();
this.hass.callApi('DELETE', 'states/' + this.stateObj.entity_id);
this.hass.callService("persistent_notification", "dismiss", {
notification_id: computeObjectId(this.stateObj.entity_id),
});
}
}
customElements.define('ha-persistent_notification-card', HaPersistentNotificationCard);
customElements.define(
"ha-persistent_notification-card",
HaPersistentNotificationCard
);

View File

@@ -1,11 +1,11 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/ha-card.js';
import '../components/ha-icon.js';
import "../components/ha-card.js";
import "../components/ha-icon.js";
import computeStateName from '../common/entity/compute_state_name.js';
import EventsMixin from '../mixins/events-mixin.js';
import computeStateName from "../common/entity/compute_state_name.js";
import EventsMixin from "../mixins/events-mixin.js";
class HaPlantCard extends EventsMixin(PolymerElement) {
static get template() {
@@ -85,18 +85,18 @@ class HaPlantCard extends EventsMixin(PolymerElement) {
static get properties() {
return {
hass: Object,
stateObj: Object
stateObj: Object,
};
}
constructor() {
super();
this.sensors = {
moisture: 'hass:water',
temperature: 'hass:thermometer',
brightness: 'hass:white-balance-sunny',
conductivity: 'hass:emoticon-poop',
battery: 'hass:battery'
moisture: "hass:water",
temperature: "hass:thermometer",
brightness: "hass:white-balance-sunny",
conductivity: "hass:emoticon-poop",
battery: "hass:battery",
};
}
@@ -105,16 +105,17 @@ class HaPlantCard extends EventsMixin(PolymerElement) {
}
computeAttributes(data) {
return Object.keys(this.sensors).filter(key => key in data);
return Object.keys(this.sensors).filter((key) => key in data);
}
computeIcon(attr, batLvl) {
const icon = this.sensors[attr];
if (attr === 'battery') {
if (attr === "battery") {
if (batLvl <= 5) {
return `${icon}-alert`;
} else if (batLvl < 95) {
return `${icon}-${Math.round((batLvl / 10) - 0.01) * 10}`;
}
if (batLvl < 95) {
return `${icon}-${Math.round(batLvl / 10 - 0.01) * 10}`;
}
}
return icon;
@@ -125,20 +126,22 @@ class HaPlantCard extends EventsMixin(PolymerElement) {
}
computeUom(dict, attr) {
return dict[attr] || '';
return dict[attr] || "";
}
computeAttributeClass(problem, attr) {
return problem.indexOf(attr) === -1 ? '' : 'problem';
return problem.indexOf(attr) === -1 ? "" : "problem";
}
computeImageClass(entityPicture) {
return entityPicture ? 'has-plant-image' : '';
return entityPicture ? "has-plant-image" : "";
}
attributeClicked(ev) {
this.fire('hass-more-info', { entityId: this.stateObj.attributes.sensors[ev.model.item] });
this.fire("hass-more-info", {
entityId: this.stateObj.attributes.sensors[ev.model.item],
});
}
}
customElements.define('ha-plant-card', HaPlantCard);
customElements.define("ha-plant-card", HaPlantCard);

View File

@@ -1,17 +1,16 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import '../components/ha-card.js';
import '../components/ha-icon.js';
import "../components/ha-card.js";
import "../components/ha-icon.js";
import EventsMixin from '../mixins/events-mixin.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import EventsMixin from "../mixins/events-mixin.js";
import LocalizeMixin from "../mixins/localize-mixin.js";
/*
* @appliesMixin LocalizeMixin
*/
class HaWeatherCard extends
LocalizeMixin(EventsMixin(PolymerElement)) {
class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style>
@@ -129,11 +128,11 @@ class HaWeatherCard extends
<template is="dom-repeat" items="[[forecast]]">
<div>
<div class="weekday">[[computeDate(item.datetime)]]<br>
<template is="dom-if" if="[[!item.templow]]">
<template is="dom-if" if="[[!_showValue(item.templow)]]">
[[computeTime(item.datetime)]]
</template>
</div>
<template is="dom-if" if="[[item.condition]]">
<template is="dom-if" if="[[_showValue(item.condition)]]">
<div class="icon">
<ha-icon icon="[[getWeatherIcon(item.condition)]]"></ha-icon>
</div>
@@ -160,42 +159,57 @@ class HaWeatherCard extends
stateObj: Object,
forecast: {
type: Array,
computed: 'computeForecast(stateObj.attributes.forecast)'
}
computed: "computeForecast(stateObj.attributes.forecast)",
},
};
}
constructor() {
super();
this.cardinalDirections = [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'
"N",
"NNE",
"NE",
"ENE",
"E",
"ESE",
"SE",
"SSE",
"S",
"SSW",
"SW",
"WSW",
"W",
"WNW",
"NW",
"NNW",
"N",
];
this.weatherIcons = {
'clear-night': 'hass:weather-night',
cloudy: 'hass:weather-cloudy',
fog: 'hass:weather-fog',
hail: 'hass:weather-hail',
lightning: 'mid:weather-lightning',
'lightning-rainy': 'hass:weather-lightning-rainy',
partlycloudy: 'hass:weather-partlycloudy',
pouring: 'hass:weather-pouring',
rainy: 'hass:weather-rainy',
snowy: 'hass:weather-snowy',
'snowy-rainy': 'hass:weather-snowy-rainy',
sunny: 'hass:weather-sunny',
windy: 'hass:weather-windy',
'windy-variant': 'hass:weather-windy-variant'
"clear-night": "hass:weather-night",
cloudy: "hass:weather-cloudy",
fog: "hass:weather-fog",
hail: "hass:weather-hail",
lightning: "hass:weather-lightning",
"lightning-rainy": "hass:weather-lightning-rainy",
partlycloudy: "hass:weather-partlycloudy",
pouring: "hass:weather-pouring",
rainy: "hass:weather-rainy",
snowy: "hass:weather-snowy",
"snowy-rainy": "hass:weather-snowy-rainy",
sunny: "hass:weather-sunny",
windy: "hass:weather-windy",
"windy-variant": "hass:weather-windy-variant",
};
}
ready() {
this.addEventListener('click', this._onClick);
this.addEventListener("click", this._onClick);
super.ready();
}
_onClick() {
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
}
computeForecast(forecast) {
@@ -203,16 +217,16 @@ class HaWeatherCard extends
}
getUnit(measure) {
const lengthUnit = this.hass.config.core.unit_system.length || '';
const lengthUnit = this.hass.config.unit_system.length || "";
switch (measure) {
case 'air_pressure':
return lengthUnit === 'km' ? 'hPa' : 'inHg';
case 'length':
case "air_pressure":
return lengthUnit === "km" ? "hPa" : "inHg";
case "length":
return lengthUnit;
case 'precipitation':
return lengthUnit === 'km' ? 'mm' : 'in';
case "precipitation":
return lengthUnit === "km" ? "mm" : "in";
default:
return this.hass.config.core.unit_system[measure] || '';
return this.hass.config.unit_system[measure] || "";
}
}
@@ -239,20 +253,22 @@ class HaWeatherCard extends
getWind(speed, bearing, localize) {
if (bearing != null) {
const cardinalDirection = this.windBearingToText(bearing);
return `${speed} ${this.getUnit('length')}/h (${localize(`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`) || cardinalDirection})`;
return `${speed} ${this.getUnit("length")}/h (${localize(
`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`
) || cardinalDirection})`;
}
return `${speed} ${this.getUnit('length')}/h`;
return `${speed} ${this.getUnit("length")}/h`;
}
_showValue(item) {
return typeof item !== 'undefined' && item !== null;
return typeof item !== "undefined" && item !== null;
}
computeDate(data) {
const date = new Date(data);
return date.toLocaleDateString(
this.hass.selectedLanguage || this.hass.language,
{ weekday: 'short' }
{ weekday: "short" }
);
}
@@ -260,8 +276,8 @@ class HaWeatherCard extends
const date = new Date(data);
return date.toLocaleTimeString(
this.hass.selectedLanguage || this.hass.language,
{ hour: 'numeric' }
{ hour: "numeric" }
);
}
}
customElements.define('ha-weather-card', HaWeatherCard);
customElements.define("ha-weather-card", HaWeatherCard);

View File

@@ -0,0 +1,78 @@
/**
* Auth class that connects to a native app for authentication.
*/
import { Auth } from "home-assistant-js-websocket";
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
if (!window.externalApp && !window.webkit) {
throw new Error(
"External auth requires either externalApp or webkit defined on Window object."
);
}
export default class ExternalAuth extends Auth {
constructor(hassUrl) {
super();
this.data = {
hassUrl,
access_token: "",
// This will trigger connection to do a refresh right away
expires: 0,
};
}
async refreshAccessToken() {
const responseProm = new Promise((resolve, reject) => {
window[CALLBACK_SET_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
});
// Allow promise to set resolve on window object.
await 0;
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
if (window.externalApp) {
window.externalApp.getExternalAuth(callbackPayload);
} else {
window.webkit.messageHandlers.getExternalAuth.postMessage(
callbackPayload
);
}
// Response we expect back:
// {
// "access_token": "qwere",
// "expires_in": 1800
// }
const tokens = await responseProm;
this.data.access_token = tokens.access_token;
this.data.expires = tokens.expires_in * 1000 + Date.now();
}
async revoke() {
const responseProm = new Promise((resolve, reject) => {
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
});
// Allow promise to set resolve on window object.
await 0;
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
if (window.externalApp) {
window.externalApp.revokeExternalAuth(callbackPayload);
} else {
window.webkit.messageHandlers.revokeExternalAuth.postMessage(
callbackPayload
);
}
await responseProm;
}
}

View File

@@ -1,76 +0,0 @@
import { storeTokens, loadTokens } from './token_storage.js';
function genClientId() {
return `${location.protocol}//${location.host}/`;
}
export function redirectLogin() {
document.location.href = `/auth/authorize?response_type=code&client_id=${encodeURIComponent(genClientId())}&redirect_uri=${encodeURIComponent(location.toString())}`;
return new Promise((() => {}));
}
function fetchTokenRequest(code) {
const data = new FormData();
data.append('client_id', genClientId());
data.append('grant_type', 'authorization_code');
data.append('code', code);
return fetch('/auth/token', {
credentials: 'same-origin',
method: 'POST',
body: data,
}).then((resp) => {
if (!resp.ok) throw new Error('Unable to fetch tokens');
return resp.json().then((tokens) => {
tokens.expires = (tokens.expires_in * 1000) + Date.now();
return tokens;
});
});
}
function refreshTokenRequest(tokens) {
const data = new FormData();
data.append('client_id', genClientId());
data.append('grant_type', 'refresh_token');
data.append('refresh_token', tokens.refresh_token);
return fetch('/auth/token', {
credentials: 'same-origin',
method: 'POST',
body: data,
}).then((resp) => {
if (!resp.ok) throw new Error('Unable to fetch tokens');
return resp.json().then((newTokens) => {
newTokens.expires = (newTokens.expires_in * 1000) + Date.now();
return newTokens;
});
});
}
export function resolveCode(code) {
return fetchTokenRequest(code).then((tokens) => {
storeTokens(tokens);
history.replaceState(null, null, location.pathname);
return tokens;
}, (err) => {
// eslint-disable-next-line
console.error('Resolve token failed', err);
alert('Unable to fetch tokens');
redirectLogin();
});
}
export function refreshToken() {
const tokens = loadTokens();
if (tokens === null) {
return redirectLogin();
}
return refreshTokenRequest(tokens).then((accessTokenResp) => {
const newTokens = Object.assign({}, tokens, accessTokenResp);
storeTokens(newTokens);
return newTokens;
}, () => redirectLogin());
}

View File

@@ -10,27 +10,31 @@ if (!tokenCache) {
}
export function askWrite() {
return tokenCache.tokens !== undefined && tokenCache.writeEnabled === undefined;
return (
tokenCache.tokens !== undefined && tokenCache.writeEnabled === undefined
);
}
export function storeTokens(tokens) {
export function saveTokens(tokens) {
tokenCache.tokens = tokens;
if (tokenCache.writeEnabled) {
try {
storage.tokens = JSON.stringify(tokens);
} catch (err) {} // eslint-disable-line
storage.hassTokens = JSON.stringify(tokens);
} catch (err) {} // eslint-disable-line
}
}
export function enableWrite() {
tokenCache.writeEnabled = true;
storeTokens(tokenCache.tokens);
saveTokens(tokenCache.tokens);
}
export function loadTokens() {
if (tokenCache.tokens === undefined) {
try {
const tokens = storage.tokens;
// Delete the old token cache.
delete storage.tokens;
const tokens = storage.hassTokens;
if (tokens) {
tokenCache.tokens = JSON.parse(tokens);
tokenCache.writeEnabled = true;

View File

@@ -1,4 +1,4 @@
/** Return if a component is loaded. */
export default function isComponentLoaded(hass, component) {
return hass && hass.config.core.components.indexOf(component) !== -1;
return hass && hass.config.components.indexOf(component) !== -1;
}

View File

@@ -0,0 +1,4 @@
/** Return if the displaymode is in standalone mode (PWA). */
export default function isPwa() {
return window.matchMedia("(display-mode: standalone)").matches;
}

View File

@@ -1,4 +1,4 @@
/** Get the location name from a hass object. */
export default function computeLocationName(hass) {
return hass && hass.config.core.location_name;
return hass && hass.config.location_name;
}

View File

@@ -5,82 +5,80 @@
// Each constant should have a description what it is supposed to be used for.
/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = 'hass:bookmark';
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
/** Domains that have a state card. */
export const DOMAINS_WITH_CARD = [
'climate',
'cover',
'configurator',
'input_select',
'input_number',
'input_text',
'lock',
'media_player',
'scene',
'script',
'timer',
'vacuum',
'weblink',
"climate",
"cover",
"configurator",
"input_select",
"input_number",
"input_text",
"lock",
"media_player",
"scene",
"script",
"timer",
"vacuum",
"water_heater",
"weblink",
];
/** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [
'alarm_control_panel',
'automation',
'camera',
'climate',
'configurator',
'cover',
'fan',
'group',
'history_graph',
'input_datetime',
'light',
'lock',
'media_player',
'script',
'sun',
'updater',
'vacuum',
'weather'
"alarm_control_panel",
"automation",
"camera",
"climate",
"configurator",
"cover",
"fan",
"group",
"history_graph",
"input_datetime",
"light",
"lock",
"media_player",
"script",
"sun",
"updater",
"vacuum",
"water_heater",
"weather",
];
/** Domains that show no more info dialog. */
export const DOMAINS_HIDE_MORE_INFO = [
'input_number',
'input_select',
'input_text',
'scene',
'weblink'
"input_number",
"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',
"camera",
"configurator",
"history_graph",
"scene",
];
/** States that we consider "off". */
export const STATES_OFF = [
'closed',
'locked',
'off'
];
export const STATES_OFF = ["closed", "locked", "off"];
/** Domains where we allow toggle in Lovelace. */
export const DOMAINS_TOGGLE = new Set([
'fan',
'input_boolean',
'light',
'switch'
"fan",
"input_boolean",
"light",
"switch",
]);
/** Temperature units. */
export const UNIT_C = '°C';
export const UNIT_F = '°F';
export const UNIT_C = "°C";
export const UNIT_F = "°F";
/** Entity ID of the default view. */
export const DEFAULT_VIEW_ENTITY_ID = 'group.default_view';
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";

View File

@@ -1,4 +1,4 @@
export default function durationToSeconds(duration) {
const parts = duration.split(':').map(Number);
return (parts[0] * 3600) + (parts[1] * 60) + parts[2];
const parts = duration.split(":").map(Number);
return parts[0] * 3600 + parts[1] * 60 + parts[2];
}

View File

@@ -1,21 +1,21 @@
import fecha from 'fecha';
import fecha from "fecha";
// Check for support of native locale string options
function toLocaleDateStringSupportsOptions() {
try {
new Date().toLocaleDateString('i');
new Date().toLocaleDateString("i");
} catch (e) {
return e.name === 'RangeError';
return e.name === "RangeError";
}
return false;
}
export default (toLocaleDateStringSupportsOptions() ?
function (dateObj, locales) {
return dateObj.toLocaleDateString(
locales,
{ year: 'numeric', month: 'long', day: 'numeric' },
);
} : function (dateObj, locales) { // eslint-disable-line no-unused-vars
return fecha.format(dateObj, 'mediumDate');
});
export default (toLocaleDateStringSupportsOptions()
? (dateObj, locales) =>
dateObj.toLocaleDateString(locales, {
year: "numeric",
month: "long",
day: "numeric",
})
: // eslint-disable-next-line no-unused-vars
(dateObj, locales) => fecha.format(dateObj, "mediumDate"));

View File

@@ -1,24 +1,23 @@
import fecha from 'fecha';
import fecha from "fecha";
// Check for support of native locale string options
function toLocaleStringSupportsOptions() {
try {
new Date().toLocaleString('i');
new Date().toLocaleString("i");
} catch (e) {
return e.name === 'RangeError';
return e.name === "RangeError";
}
return false;
}
export default (toLocaleStringSupportsOptions() ?
function (dateObj, locales) {
return dateObj.toLocaleString(locales, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
} : function (dateObj, locales) { // eslint-disable-line no-unused-vars
return fecha.format(dateObj, 'haDateTime');
});
export default (toLocaleStringSupportsOptions()
? (dateObj, locales) =>
dateObj.toLocaleString(locales, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
})
: // eslint-disable-next-line no-unused-vars
(dateObj, locales) => fecha.format(dateObj, "haDateTime"));

View File

@@ -1,21 +1,20 @@
import fecha from 'fecha';
import fecha from "fecha";
// Check for support of native locale string options
function toLocaleTimeStringSupportsOptions() {
try {
new Date().toLocaleTimeString('i');
new Date().toLocaleTimeString("i");
} catch (e) {
return e.name === 'RangeError';
return e.name === "RangeError";
}
return false;
}
export default (toLocaleTimeStringSupportsOptions() ?
function (dateObj, locales) {
return dateObj.toLocaleTimeString(
locales,
{ hour: 'numeric', minute: '2-digit' }
);
} : function (dateObj, locales) { // eslint-disable-line no-unused-vars
return fecha.format(dateObj, 'shortTime');
});
export default (toLocaleTimeStringSupportsOptions()
? (dateObj, locales) =>
dateObj.toLocaleTimeString(locales, {
hour: "numeric",
minute: "2-digit",
})
: // eslint-disable-next-line no-unused-vars
(dateObj, locales) => fecha.format(dateObj, "shortTime"));

View File

@@ -1,30 +1,33 @@
/** Calculate a string representing a date object as relative time from now.
*
* Example output: 5 minutes ago, in 3 days.
*/
const tests = [
60, 'second',
60, 'minute',
24, 'hour',
7, 'day',
];
*/
const tests = [60, "second", 60, "minute", 24, "hour", 7, "day"];
export default function relativeTime(dateObj, localize) {
let delta = (new Date() - dateObj) / 1000;
const tense = delta >= 0 ? 'past' : 'future';
const tense = delta >= 0 ? "past" : "future";
delta = Math.abs(delta);
for (let i = 0; i < tests.length; i += 2) {
if (delta < tests[i]) {
delta = Math.floor(delta);
const time = localize(`ui.components.relative_time.duration.${tests[i + 1]}`, 'count', delta);
return localize(`ui.components.relative_time.${tense}`, 'time', time);
const time = localize(
`ui.components.relative_time.duration.${tests[i + 1]}`,
"count",
delta
);
return localize(`ui.components.relative_time.${tense}`, "time", time);
}
delta /= tests[i];
}
delta = Math.floor(delta);
const time = localize('ui.components.relative_time.duration.week', 'count', delta);
return localize(`ui.components.relative_time.${tense}`, 'time', time);
const time = localize(
"ui.components.relative_time.duration.week",
"count",
delta
);
return localize(`ui.components.relative_time.${tense}`, "time", time);
}

View File

@@ -1,16 +1,18 @@
const leftPad = number => (number < 10 ? `0${number}` : number);
const leftPad = (number) => (number < 10 ? `0${number}` : number);
export default function secondsToDuration(d) {
const h = Math.floor(d / 3600);
const m = Math.floor((d % 3600) / 60);
const s = Math.floor(d % 3600 % 60);
const s = Math.floor((d % 3600) % 60);
if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
} else if (m > 0) {
}
if (m > 0) {
return `${m}:${leftPad(s)}`;
} else if (s > 0) {
return '' + s;
}
if (s > 0) {
return "" + s;
}
return null;
}

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