Compare commits

...

646 Commits

Author SHA1 Message Date
Paulus Schoutsen
67c032c85a Merge pull request #2429 from home-assistant/dev
2019109.0
2019-01-09 14:39:11 -08:00
Paulus Schoutsen
417ffde3e8 Update translations 2019-01-09 14:37:32 -08:00
Paulus Schoutsen
f3064f0071 Bumped version to 2019109.0 2019-01-09 14:36:55 -08:00
Bram Kragten
e9d912cc87 Remove styleMap for base-unit (#2428) 2019-01-09 14:04:49 -08:00
yosilevy
2517e5ba60 Proper RTL support in weather forecast card (#2424)
* Proper RTL support in weather forecast card

* States panel force left to right since RTL is not usable

* Updated class names and switched div to span
2019-01-09 21:34:24 +01:00
Bram Kragten
64b405dd4d Set min height to thermostat card (#2416)
* Set min height

* Set min height on #root

* Add min-height on firstUpdated and remove after roundSlider is loaded

* Move back to #thermostat
2019-01-08 11:27:01 -08:00
Bram Kragten
ddb050d1fd Change title to name (#2425) 2019-01-08 08:18:44 -06:00
hulkhaugen
3f6a8cac80 Removed excessive bracket in css (#2411)
* Removed excessive bracket in css code

* lint
2019-01-07 10:40:12 +01:00
Bram Kragten
ad113367e6 🛠️ Small fixes to sensor editor (#2415)
* Small fixes to sensor editor

* any -> string | number
2019-01-06 20:06:59 -06:00
David F. Mulcahey
f4f08ab0d1 Config panel for ZHA (#2389)
* zha config panel

* implement issue cluster command

* update layout

* read zigbee attribute service

* set attribute and manufacturer code override

* cleanup

* adjust style and documentation wording

* html cleanup

* ha-call-ws-api-button

* use call-ws-api-button

* fix deprecated syntax - travis error

* emulate new z-wave node info functionality

* start converting to lit

* fix style includes

* fix help toggle

* remove old panel

* cleanup

* cleanup

* convert to lit / ts

* import styles

* types - review comment
2019-01-04 19:11:08 -06:00
John Arild Berentsen
c2a57099d3 Allow for removal of broadcast node in UI (#2390)
* allow for removal of broadcast

* prettier

* prettier
2019-01-03 23:31:30 +01:00
Emil Stjerneman
adf0c6d891 Lovelace alarm panel respects the code_format attribute on the alarm entity (#2379) 2019-01-03 23:09:07 +01:00
Milan V
38a2627227 Fix climate control rounding error (#2375)
* Fix climate rounding error

* Update ha-climate-control.js
2019-01-03 23:07:37 +01:00
Ian Richardson
5a90edc893 thermostat card: follow same step logic as more-info (#2369)
* Follow same step logic as more-info

* Address review comments

* Address review comments

* Address review comments and cleanup
2019-01-03 22:53:55 +01:00
Ian Richardson
88473581c2 UI Editor for entity-button card (#2393) 2019-01-03 22:53:10 +01:00
Bram Kragten
88d23eb9dd Check config on save (#2346)
* Check config on save

Untested...

* views is list of objects, resources not required

* Lint

* Only warn on "# " to prevent warnings on colors

Could miss some comments...

* Improve # check

Warn on # at the start of a line or when followed by white space

* Faster

* Multiline

* Check if # is added

* Also handle paste and just show the warning on every #
2019-01-03 22:46:57 +01:00
Andrey
25c788871f Allow empty name (#2388) 2019-01-03 22:46:14 +01:00
Xus Badia
f272801253 Update favicon-apple-180x180.png (#2385) 2018-12-28 18:22:40 +01:00
Ian Richardson
2e750dc1e2 UI Editor for map card (#2287)
*  UI Editor for `map` card

* Address review comments

* Revert change properly

* Address review comments

* Note to change to interface after LL conversion

* Remove config options if empty

Should apply to other editors

* entitites is required

* cleanup

* cleanup

* Fix for number values

* Name chunk

* Remove zoom default
2018-12-21 13:53:26 +01:00
Paulus Schoutsen
3c5fb6d1ad Update translations 2018-12-19 14:43:12 +01:00
Paulus Schoutsen
32cd683b8a Bumped version to 20181219.0 2018-12-19 14:42:39 +01:00
Paulus Schoutsen
6c029b39e0 Merge branch 'master' into dev 2018-12-19 14:03:44 +01:00
Paulus Schoutsen
7efad04e42 Merge pull request #2367 from home-assistant/20181211-2
20181211.2
2018-12-19 14:02:48 +01:00
Paulus Schoutsen
b6f7781a87 Bumped version to 20181211.2 2018-12-19 13:59:16 +01:00
Paulus Schoutsen
16a147f389 Fix incorrect state display being cached (#2356)
* Fix incorrect state display being cached

* Remove test for cache
2018-12-19 13:59:10 +01:00
Zack Arnett
79b71ed753 Update UI and Add Move Card to a Dialogue (#2282)
* Update UI

* REmove useless import

* Add Notification if saved is done

* Move Register Dialog

* Updates from suggestions

* Updates box shadow on card options

* Update color and viewselection

* Add pointer

* Update Cursor

* Update check next to save

* Comment Updates

* Text area
2018-12-19 13:08:17 +01:00
Paulus Schoutsen
49fa74cc07 Fix incorrect state display being cached (#2356)
* Fix incorrect state display being cached

* Remove test for cache
2018-12-19 13:05:39 +01:00
Ian Richardson
0a2eaec884 Cleanup of picture card editor (#2364)
* Cleanup of `picture` card editor

* Fix `action-editor`
2018-12-18 20:40:51 +01:00
Ian Richardson
4c5d3138c1 UI Editor for picture card (#2240)
* UI Editor for `picture` card

This is a WIP.
* How should I handle service data? It's kind of freeform and I don't really have a good idea on what I should do.
* in action-editor I have two issues for `_navigation_path` and `_service` have TS errors saying the property doesn't exist on `ToggleActionConfig`. Not sure why that is the only type it is looking at. Should I be checking the type somewhere?

* Remove `id`

* Cleanup. Service-data still WIP

* Could use some help on service_data

* Perhaps a better/more structured method?

* Revert "Perhaps a better/more structured method?"

This reverts commit 1e1a1e44c16a18c5ffc380347cffd01e7fad52f9.

* Just playing around

* MVP doesn't include service data

* Address review comments

* Address review comments

* Name chunk and remove when unused

* Remove `more-info` action option

* Address review comments
2018-12-18 17:25:56 +01:00
Paulus Schoutsen
5e1cd389b3 Name all chunks (#2363) 2018-12-18 17:14:33 +01:00
Ian Richardson
7ced08a899 UI Editor for weather-forecast card (#2285)
*  UI Editor for `weather-forecast` card

* Missed name

* Fix typo

* Address review comments

* Remove unused optional configs with no defaults

* cleanup

* Name chunk
2018-12-18 14:26:34 +01:00
Malte Franken
603cf7ba0f two new geo location map demo cards (#2349) 2018-12-18 11:29:12 +01:00
Ian Richardson
c47ba65c3b 🧹 remove unused configs when empty (#2347)
* Remove unused configs when empty

* Handle unused numbers & fix glance entities

* Sneak in the chunk names
2018-12-18 11:15:32 +01:00
Ian Richardson
849ed80e78 🚫 render error-entity-row when state not available (#2352) 2018-12-18 11:14:55 +01:00
quthla
b78c48ecec Fix android external auth (#2319)
* Fix android external auth

* Update external_auth.ts

* Update external_auth.ts

* Update external_auth.ts
2018-12-17 20:37:10 +01:00
Paulus Schoutsen
8d2da9c5a6 Merge branch 'master' into dev 2018-12-17 10:51:19 +01:00
Ian Richardson
9664e8258c UI Editor for plant-status card (#2286)
*  UI Editor for `plant-status` card

* Address review comments

* Require entity

* Cleanup

* Cleanup

* Address review comments

* Update hui-plant-status-card-editor.ts
2018-12-17 10:45:23 +01:00
Paulus Schoutsen
5f5bf17df0 Merge pull request #2345 from home-assistant/20181211-1
20181211 1
2018-12-17 10:20:35 +01:00
Paulus Schoutsen
ca7674cd15 Bumped version to 20181211.1 2018-12-17 10:17:32 +01:00
Paulus Schoutsen
3f5f5bb1ee Fix service button element (#2343) 2018-12-17 10:17:18 +01:00
Paulus Schoutsen
e7ee9c7054 Fix undefined on plant/weather card (#2339) 2018-12-17 10:17:18 +01:00
Ian Richardson
4f6ecf5c21 🔨 Fix for element positioning (#2335)
* Fix for element positioning

* Address comments
2018-12-17 10:17:17 +01:00
Zack Arnett
87eac4cdee remove Animation for thermostat and light (#2303)
* Update Animation

* Update light
2018-12-17 10:17:16 +01:00
Paulus Schoutsen
d267196bff Call super updated (#2293) 2018-12-17 10:16:50 +01:00
Paulus Schoutsen
f683337cbe Fix opening edit dialog twice when closed by clicking on overlay (#2290) 2018-12-17 10:13:19 +01:00
Paulus Schoutsen
1a6226270f Fix setting aspect ratio in percentage (#2289)
* Fix setting aspect ratio in percentage

* Use endsWith

* Fix invalid test
2018-12-17 10:13:19 +01:00
Paulus Schoutsen
64714c64c7 Fix notifications drawer (#2344) 2018-12-17 10:08:23 +01:00
Ian Richardson
b7c34c483a 🔨 Fix for element positioning (#2335)
* Fix for element positioning

* Address comments
2018-12-17 10:07:59 +01:00
Paulus Schoutsen
e5bf842801 Fix service button element (#2343) 2018-12-17 09:36:55 +01:00
Ian Richardson
d1a56d6acc UI Editor for media-control card (#2336)
*  UI Editor for `media-control` card

* Address review comments

* Note on LL interface
2018-12-17 09:31:58 +01:00
Malte Franken
cac7f8d1ab Extended map to support geo location entities (#2337)
* initial version of geo location map

* configuring entities not required but source is

* extending existing map instead of adding a new one

* renamed source to geo_location_source; clearer handling of geo location entities vs defined entities

* geo location sources must now be an array

* code cleanup
2018-12-17 09:04:58 +01:00
Ian Richardson
9d2b37c9f2 Remove unnecessary editor types (#2342) 2018-12-17 07:50:27 +01:00
Paulus Schoutsen
e20a02c52c Fix undefined on plant/weather card (#2339) 2018-12-16 13:52:13 -05:00
Zack Arnett
c46d04eaa6 Alarm Card Conversion (#2259)
* Alarm Card COnversion

* Map buttons

* Reducing margin under alarm code

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

Co-Authored-By: zsarnett <arnett.zackary@gmail.com>

* Comment Updates

* Review updates

* Reorder css

* Update actions

* TSlint

* TS LINT

* Idk whats going on here

* Fix import
2018-12-14 11:16:43 +01:00
Zack Arnett
2ec8b97378 remove Animation for thermostat and light (#2303)
* Update Animation

* Update light
2018-12-13 21:45:21 -05:00
Paulus Schoutsen
b3b9ca9c3f Fix setting aspect ratio in percentage (#2289)
* Fix setting aspect ratio in percentage

* Use endsWith

* Fix invalid test
2018-12-13 23:36:01 +01:00
Paulus Schoutsen
71ed83ef07 Fix opening edit dialog twice when closed by clicking on overlay (#2290) 2018-12-13 21:46:57 +01:00
Paulus Schoutsen
47635055d0 Clear view cache edit mode (#2291)
* Clear view cache when canceling edit mode

* Fix enabling edit mode on not first view
2018-12-13 21:46:50 +01:00
Paulus Schoutsen
0dfca2f33b Call super updated (#2293) 2018-12-13 09:26:23 -05:00
Ian Richardson
18de427145 🧹 domain-filter as attribute as it does not change (#2271)
* 🔨 `domain-filter` as attribute as it does not change

* Address review comments
2018-12-13 09:40:57 +01:00
Bram Kragten
118f28285e 📷 Add camera card on generate (#2279) 2018-12-12 08:00:00 -06:00
Paulus Schoutsen
6a9cfbfa1c TS create element functions (#2276)
* TS create element functions

* Name chunk
2018-12-12 14:21:34 +01:00
Bram Kragten
8c61624a9c Change edit mode + add edit lovelace (#2277)
* Change edit mode + add edit lovelace

* Comments

* Fix (yes it was used :-)

* Raw edit header changed
2018-12-12 13:59:19 +01:00
Paulus Schoutsen
d277571735 Generalize errors (#2275) 2018-12-12 09:47:32 +01:00
Paulus Schoutsen
a6f3684846 Fix gauge (#2274) 2018-12-12 09:29:46 +01:00
Paulus Schoutsen
edef4ba2f5 Speed things up (#2270)
* Speed things up

* Fixes
2018-12-12 08:51:59 +01:00
Matthias Dötsch
7cd6619a79 Added some missing translate tags for cards (#2188)
* Added some missing translate tags for cards

more-info of:
 - sun
 - script
 - updater

* Added some missing translate tags for cards

* Added some missing translate tags for cards
2018-12-12 08:27:56 +01:00
Ian Richardson
2059e36dd6 🔨 Update value attributes to properties (#2269) 2018-12-12 08:12:57 +01:00
Ian Richardson
4a455e9147 🔧 Organize editor imports (#2272) 2018-12-12 08:09:23 +01:00
Ian Richardson
fe0b131480 UI Editor for iframe card (#2230)
* UI Editor for `iframe` card

* Remove `id`

* Address review comments

* Cleaned up some inconsistencies
2018-12-11 17:05:01 -06:00
Paulus Schoutsen
b1b78c2bb7 Fix stack trace in badge 2018-12-11 21:43:43 +01:00
Paulus Schoutsen
99395360c7 Convert HA-STATE-LABEL-BADGE to lit (#2268) 2018-12-11 21:41:38 +01:00
Ian Richardson
bd46e3b8e0 UI Editor for sensor card (#2267) 2018-12-11 21:39:18 +01:00
Paulus Schoutsen
80dd15306e Convert ha-label-badge to lit (#2266)
* Convert ha-label-badge to lit

* Add class to TypeScript map
2018-12-11 21:23:19 +01:00
Ian Richardson
88f0ebf75d UI editor for shopping-list card (#2227)
* Add UI editor for `shopping-list` card

* Cleanup

* Export config

* Remove `id`
2018-12-11 21:23:10 +01:00
Ian Richardson
8679f10f10 UI Editor for light card (#2232)
* UI Editor for `light` card

* Remove `id`
2018-12-11 21:22:21 +01:00
Ian Richardson
db4c1e45f5 UI Editor for gauge card (#2229)
* UI editor for `gauge` card

Need to develop a severity input method still

* Config works well but no preview showing

* Add `sensor` domain filter

* Remove `id`
2018-12-11 21:22:08 +01:00
Paulus Schoutsen
65cf2feb7a UI Editor for markdown card (#2231)
* UI Editor for `markdown` card

* Remove `id`
2018-12-11 21:21:38 +01:00
Ian Richardson
97da26eba7 UI Editor for alarm-panel card (#2257)
* UI Editor for `alarm-panel` card

* Adding of states

Can't get the last available state to be recognized as being a selection change

* Ability to remove states

* Clean up

* Clean up

* Remove id
2018-12-11 21:15:29 +01:00
Ian Richardson
8e7d7c5188 UI Editor for thermostat card (#2258) 2018-12-11 21:15:18 +01:00
Paulus Schoutsen
767307ef47 Convert hui-view to Lit (#2265)
* Convert hui-view to Lit

* Add super call to updated

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

Co-Authored-By: balloob <paulus@home-assistant.io>

* Apply suggestions from code review

Co-Authored-By: balloob <paulus@home-assistant.io>

* Address comments"

* Fix things
2018-12-11 19:47:19 +01:00
Paulus Schoutsen
ccc6262026 Convert HUI-ROOT to Lit Element (#2264)
* Convert HUI-ROOT to Lit Element

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

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

Co-Authored-By: balloob <paulus@home-assistant.io>

* Apply suggestions from code review

Co-Authored-By: balloob <paulus@home-assistant.io>

* Address comments
2018-12-11 19:39:40 +01:00
Bram Kragten
2cdb542112 Add move card to view (#2262)
* Add move card to view

* Fix style

* Fix style and tests

* last style change

* update tests
2018-12-11 19:38:57 +01:00
Paulus Schoutsen
4e232e58ce Address comments 2018-12-11 19:26:26 +01:00
Ian Richardson
27bb175624 Apply suggestions from code review
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:05:01 +01:00
Ian Richardson
5a5a7dad1e Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:03:58 +01:00
Ian Richardson
2d1cf421ef Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:03:48 +01:00
Ian Richardson
8be25f2020 Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:03:33 +01:00
Ian Richardson
0a8f853a8e Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:03:01 +01:00
Ian Richardson
a46f5e3d4e Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:02:44 +01:00
Ian Richardson
5de36f9579 Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:02:32 +01:00
Ian Richardson
9b5e79f42a Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:02:18 +01:00
Ian Richardson
a824599a37 Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:02:07 +01:00
Ian Richardson
884b24da0e Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:01:32 +01:00
Ian Richardson
76325a384c Update src/panels/lovelace/hui-root.ts
Co-Authored-By: balloob <paulus@home-assistant.io>
2018-12-11 19:00:57 +01:00
Paulus Schoutsen
e2218f1e6e Extract component for editing out of hui-view (#2263) 2018-12-11 17:17:59 +01:00
Paulus Schoutsen
758b686684 Convert HUI-ROOT to Lit Element 2018-12-11 17:06:05 +01:00
Paulus Schoutsen
3a50d47dd2 Merge pull request #2261 from home-assistant/dev
20181211.0
2018-12-11 10:29:13 +01:00
Paulus Schoutsen
b4d4591273 Bumped version to 20181211.0 2018-12-11 10:28:16 +01:00
Paulus Schoutsen
432fdd628c Update translations 2018-12-11 10:28:12 +01:00
Paulus Schoutsen
bc23dd37be Handle webhook component not loaded (#2255)
* Handle webhook component not loaded

* Fix text
2018-12-11 10:25:16 +01:00
Ian Richardson
0319fd23c5 Remove id 2018-12-10 23:13:26 -06:00
Ian Richardson
d7e5993501 UI Editor for markdown card 2018-12-10 23:12:22 -06:00
Paulus Schoutsen
46a9b90ed0 Add webhook trigger to automation editor (#2252) 2018-12-10 19:45:48 +01:00
Bram Kragten
b0c68e58c5 Small fixes (#2248)
* Small fixes

* remove returning on undefined in statename

* 16px -> 4px

* Styling
2018-12-10 19:44:34 +01:00
Paulus Schoutsen
7063ced7fd Merge pull request #2247 from home-assistant/dev
20181210.1
2018-12-10 12:49:26 +01:00
Paulus Schoutsen
7d2444868d Bumped version to 20181210.1 2018-12-10 12:48:37 +01:00
Paulus Schoutsen
0899d42967 Add raw config editor for storage mode (#2246)
* Add raw config editor for storage mode

* Lint

* Allow inserting spaces by pressing tab
2018-12-10 12:48:05 +01:00
Bram Kragten
d57bcc2701 Disable edit in yaml mode (#2244) 2018-12-10 11:40:34 +01:00
Paulus Schoutsen
d9d92c8766 Simplify Lovelace mode (#2243) 2018-12-10 11:38:18 +01:00
Paulus Schoutsen
7a16ed5400 Merge pull request #2242 from home-assistant/dev
20181210.0
2018-12-10 09:51:55 +01:00
Paulus Schoutsen
07e35ff81a Update translations 2018-12-10 09:50:25 +01:00
Paulus Schoutsen
0398944cab Bumped version to 20181210.0 2018-12-10 09:49:42 +01:00
Paulus Schoutsen
a33ff7479a Allow moving cards (#2241) 2018-12-10 09:48:59 +01:00
Paulus Schoutsen
f9182e5453 Revert lovelace as default (#2237)
* Revert lovelace as default

* Better text
2018-12-10 08:57:51 +01:00
Bram Kragten
4f0a965573 Changes for new storage (#2228)
* Changes for new storage

* Fix lint

* Use indexes for editing

* Use lovelace object

* Use lovelace object

* Lit conversion panel lovelace

* Lovelace obj

* Fix edit cards

* Fix some bugs

* Fix last bugs

* Lint

* Don't drop view content

* Move file

* Add skip button to card picker

* Correctly set lovelace mode
2018-12-10 08:57:29 +01:00
Paulus Schoutsen
2a23487163 Fix long press in FF (#2226)
* Fix long press in FF

* Better fix
2018-12-09 10:09:34 +01:00
Ian Richardson
6b9ba7367d Remove alarm-panel UI editor (#2235)
Remove the editor that I inadvertidently committed to dev so that we can give it a proper review before merging.
2018-12-09 10:09:10 +01:00
Paulus Schoutsen
a17ae5546f Lint 2018-12-08 10:54:00 +01:00
Ian Richardson
47de0e9156 UI Editor for alarm-panel card
Still need to get states sorted out
2018-12-07 22:54:18 -06:00
Bram Kragten
baeda622de Save columns as string (#2214)
* save columns as string

* set type to number to convert to number before save
2018-12-07 14:14:29 +01:00
Bram Kragten
48220b67ed Fix edit view and layout (#2213)
* Fix edit view and layout

* Colors
2018-12-07 13:20:24 +01:00
Ian Richardson
9ba232249b grammer tweaks (#2209) 2018-12-07 11:44:55 +01:00
Bram Kragten
b0580e70d2 Fix horizontal stack (#2210) 2018-12-07 11:43:49 +01:00
Paulus Schoutsen
6c5b274792 Merge branch 'dev' 2018-12-07 07:11:43 +01:00
Paulus Schoutsen
3094b08c5f Bumped version to 20181207.0 2018-12-07 07:11:30 +01:00
Paulus Schoutsen
181539baac Merge pull request #2208 from home-assistant/dev
20181207.0
2018-12-07 07:10:32 +01:00
Bram Kragten
2289773e36 Force refresh on manual refresh (#2200) 2018-12-07 07:09:13 +01:00
Thomas Lovén
1ecb138ec5 Better display of sensor values. E.g. with units. (#2205) 2018-12-07 07:05:15 +01:00
Ian Richardson
3d67d9eba3 Update gallery demos (#2206)
* Update gallery demos

* Update demo-hui-glance-card.ts
2018-12-07 07:04:02 +01:00
Zack Arnett
0fd3c03764 UI Design Update (#2207) 2018-12-07 07:02:17 +01:00
Zack Arnett
6aca1d0d54 fix (#2204) 2018-12-06 20:22:16 +01:00
Zack Arnett
601bbfd88e Light Tap/Hold Action fix (#2191)
* Light action fix

* oops copy pasta
2018-12-06 19:17:41 +01:00
Zack Arnett
f600b0522c Add id = 0 handling (#2201)
* Add id = 0 handling

* Add check in view creation
2018-12-06 19:16:06 +01:00
Ian Richardson
647a33ea61 Fix picture-element card elements (#2202) 2018-12-06 19:15:03 +01:00
Paulus Schoutsen
64d59fedc8 Fix Lovelace weather card (#2199) 2018-12-06 17:59:36 +01:00
Bram Kragten
51d592ba0d Fix no or just 1 view (#2194) 2018-12-06 14:38:16 +01:00
Paulus Schoutsen
eaaf841a87 Code split edit dialog (#2198)
* Code split edit dialog

* Code split edit view dialog
2018-12-06 14:12:00 +01:00
Paulus Schoutsen
c6542e383c Code split jQuery roundslider (#2197) 2018-12-06 14:10:37 +01:00
Paulus Schoutsen
6e3c2bfd6a Code split Leaflet (#2196) 2018-12-06 13:30:05 +01:00
Paulus Schoutsen
be3bfc7aa4 Add script to show stats (#2195) 2018-12-06 12:43:43 +01:00
Bram Kragten
bbe90c1683 Fix view without badges (#2192) 2018-12-06 11:49:33 +01:00
Ian Richardson
2fe1d04eb0 Action tooltips (#2193)
* Fix tooltips for tap actions and add hold actions

* Cleanup logic
2018-12-06 11:49:19 +01:00
Paulus Schoutsen
f5022f4e1e Merge pull request #2190 from home-assistant/dev
20181205.0
2018-12-05 22:40:18 +01:00
Paulus Schoutsen
fdbb06de19 Merge remote-tracking branch 'origin/master' into dev 2018-12-05 22:08:30 +01:00
Paulus Schoutsen
0cd4980f44 Bumped version to 20181205.0 2018-12-05 22:07:41 +01:00
Paulus Schoutsen
2d0f14d078 Update translations 2018-12-05 22:07:34 +01:00
Thomas Lovén
9711068f8b Handle mouse events on touchscreens. Fix #2085 (#2170)
* Handle mouse events on touchscreens. Fix #2085

* Some more fixes. Makes listeners non-passive!

* Only prevent preventable events.

* Different approach

* Some travis fixes

* Try to avoid clicking while scrolling
2018-12-05 22:05:51 +01:00
Ian Richardson
fb180c7b9b Align picture card tap/hold actions (#2186)
Also removed unneccessary `navigation_path` config from `picture-entity`
2018-12-05 22:00:52 +01:00
Ian Richardson
5947bd6d74 Align picture-glance card tap/hold actions (#2187)
Removed unneccessary `force_dialog` config.
Updated `handleClick` to use `entity` or `camera_image` for `more-info` dialog
2018-12-05 22:00:04 +01:00
Paulus Schoutsen
7e584402ea Add gallery demo for Long Press directive (#2189)
* Convert gallery to TS

* Add long press demo
2018-12-05 20:32:36 +01:00
Bram Kragten
3f113da056 Fix: View should have cards (#2184)
* View should have cards

* Don't need CardConfig yet
2018-12-05 17:25:43 +01:00
Bram Kragten
bfef3a96c8 Catch errors in preview and fix entity picker (#2183)
* Catch errors in preview and fix entity picker

* Replace add button with entity-picker
2018-12-05 16:42:22 +01:00
Zack Arnett
de3a467697 Update Hold/Tap Actions to Objects (#2182)
* Update Action to Object for entity-button

* Update Actions to Object for Glance Card

* Update to use HandleClick function

* Add navigation

* Updating to use navigation path from Action Config

* Type handle events in glance

* Update Service checks

* Update Picture elements

* oops

* Adding nav back until we convert picture

* Extend typing
2018-12-05 14:27:22 +01:00
Bram Kragten
0f895fd3a1 Make Lovelace the default! (#2181)
* Make Lovelace the default!

* Move states panel to panels
2018-12-05 14:14:53 +01:00
Bram Kragten
16cc3adcff Add badges to view editor (#2176)
* Badges

* Fix entity picker

* Make editor own element
2018-12-05 12:39:04 +01:00
Zack Arnett
e2e002b9a9 Fixes Color not being overwritten by themes for cards (#2179) 2018-12-05 12:31:54 +01:00
Zack Arnett
8274284294 Theme Addition (#2178) 2018-12-04 19:49:46 +01:00
Bram Kragten
1d7f574b9b Fix process entity (#2177) 2018-12-04 12:43:03 -05:00
Bram Kragten
f680832f78 Add edit/add/delete view (#2172)
* Add edit/add/delete view

* Add delete

* Comments

* Lint

* Fix delete with numeric ids

* fix translations

* add translations
2018-12-04 16:49:12 +01:00
Ian Richardson
77711ea711 Stop MouseEvent from reach parent element (#2174) 2018-12-04 12:57:43 +01:00
Ian Richardson
f1a6122699 Convert hui-unused-entities to TypeScript/LitElement (#2173) 2018-12-04 12:57:15 +01:00
Ian Richardson
5fec881c39 Convert hui-picture-entity-card to TypeScript/LitElement (#2168)
* Convert hui-picture-entity-card to TypeScript/LitElement

click/hold are not working on my Chrome dev env

* typo

* Address review comments

Still having issues with clicks/holds on lots of cards on my system

* Add explicit navigate option

* Fixed after testing with touchevents

* Simplify
2018-12-04 10:04:59 +01:00
Ian Richardson
5dc05129ef Convert process-config-entities to TypeScript (#2113)
* Convert process-config-entities to TypeScript

* Address review comments but have issues

* Resolve merge conflicts

* Address review comments

Still a typing error which seems like it shouldn't exist

* Hack to bypass type assertion
2018-12-04 10:01:13 +01:00
Bram Kragten
f461ad6d31 Add card functionality (#2160)
* MVP add card

* WIP

* Added stub config

* Little bit of cleanup

* Add all card

* At least it works now...

* clean up and bug fixes

* Fix for switching editor
2018-12-03 14:11:46 +01:00
Ronen Hayun
57b5db4f43 Move computeRTL to a separate file and add RTL support in state-info (#2165)
* RTL support (POC)

* restore yarn.lock

* Move computeRTL to a separate file and add RTL support in state-info

* Move import to top after failed CI

* Replace var to const

* Change HassEntity to HomeAssistant object

* Support RTL in state info and state card display

* Support for RTL in more-info-controls:

* Update src/common/util/compute_rtl.ts

Co-Authored-By: rhayun <ronen.hayun@gmail.com>

* remove default from export

* prefix all computeRTL with underscroe for privately uses
2018-12-03 10:54:29 +01:00
Ian Richardson
d015fe5160 Remove .ts extensions (#2169) 2018-12-02 20:35:05 -06:00
Otto Winter
f7e3f4a828 Hass.io: Show ANSI color codes in logs (#2155)
* Hass.io: Show ANSI color codes in logs

* Use innerHTML and color classes

* Refactor ANSI function

* Readability

* Make green text black in supervisor logs

* Use assigning while loop
2018-12-02 10:26:32 +01:00
Aephir
f3b8d66f4f Update demo-hui-glance-card.js (#2157)
* Update demo-hui-glance-card.js

Fixed updated syntax

* Update gallery/src/demos/demo-hui-glance-card.js

Co-Authored-By: Aephir <waldenb@gmail.com>
2018-12-01 09:41:52 +01:00
Zack Arnett
8ae03dd1ff Convert Sensor Card to Typescript (#2140)
* Sensor Convert

* Types

* Forgot to check stateobj

* Review updates

* lint

* Update for hass and add error handling

* Review Updates

* Graph only shown if graph: line - Breaking Change

* Only rendering when updated

* Date.Now()

* Forgot to reset the date

* Lint

* Review updates

* Forgot to take this out

* Bram the god

* Update to render right things

* Check if line is being drawn

* Make graph if's more readable
2018-11-30 22:50:21 -05:00
Bram Kragten
0e6f6ddbda Add own types + add config validation to glances (#2150)
* Add own types + add config validation to glances

* Cleanup
2018-11-30 13:35:55 -05:00
Ian Richardson
882c503fa9 Extract Supported features into own funciton (#2151)
* External function to check if entity supports a feature

* Typo

* Update hass-media-player-model.js
2018-11-30 16:37:44 +01:00
Paulus Schoutsen
22eb6c6a8d Remove special char 2018-11-30 16:24:13 +01:00
Ian Richardson
8e9ff46bab Extract media-player constants to src/data (#2149) 2018-11-30 14:17:52 +01:00
Paulus Schoutsen
023d8ad893 Upgrade deps (#2156)
* Update TypeScript

* Update webcomponentsjs and shadycss

* Upgrade vaadin
2018-11-30 13:45:50 +01:00
Ian Richardson
6b730b7c40 Allow for state_image and camera_image in picture-elements card (#2143)
* Allow for state_image and camera_image in picture-elements card

* Address review comments

* Remove unneccesary div
2018-11-29 16:44:39 +01:00
Bram Kragten
90cea56a1e Check if config is compatible with UI editor (#2137)
* Check config on loading UI editor

* Clean up

* Add theme and reload editor on reopen

* Reload config element on card type change

* Clean

* Clean

* Add superstruct for config validation

* Take content logic out off return edit-card

* Reverse  logic to prevent unnecessary yaml load
2018-11-29 15:00:30 +01:00
Ian Richardson
5e43d9b6b7 Convert hui-media-player-entity-row to TypeScript/LitElement (#2136)
* Convert hui-media-player-entity-row to TypeScript/LitElement

* Add types

* Address review comments
2018-11-29 11:57:29 +01:00
Ian Richardson
e4cac86690 Convert shopping-list clear to WebSockets (#2141) 2018-11-29 11:53:30 +01:00
Jan Castermans
a249289211 Display alert entity row as a toggle (#2138)
* Update create-row-element.js

In the default UI alerts where displayed as toggles, this change makes this also the default behaviour in Lovelace

* Change the order to keep the alphabetic order
2018-11-29 11:53:04 +01:00
Ian Richardson
8ecfd9780f Convert hui-entities-toggle to TypeScript/LitElement (#2144)
* Convert hui-entities-toggle to TypeScript/LitElement

* Assign types to _callService
2018-11-29 11:52:22 +01:00
Paulus Schoutsen
913cd2b3d4 Automatically detect plants (#2146) 2018-11-29 11:24:30 +01:00
Ian Richardson
c02b7a33fe Resolve warning (#2142) 2018-11-28 21:59:14 -05:00
Bram Kragten
7a0b2060d4 Add delete card func (#2116)
* Add delete card button

* Add delete card button

* fix the non-sense

* lovelace data was moved
2018-11-28 12:34:53 +01:00
Ian Richardson
afe9056725 Combine edit and complete shopping-list calls (#2135) 2018-11-28 10:14:45 +01:00
Zack Arnett
49be2ad013 Unit PAtch (#2134) 2018-11-27 20:49:22 -06:00
Zack Arnett
230ec51de5 Fixes: #2084 : Fix for extra padding (#2133)
* Fix for extra padding

* Easy fix
2018-11-27 21:35:41 +01:00
Zack Arnett
b37ea482d3 Add name variable - Weather Card (#2131)
* Add name variable

* Update to state name
2018-11-27 15:06:51 -05:00
Zack Arnett
bf69c8ce46 Title to Name (#2127) 2018-11-27 20:30:22 +01:00
Zack Arnett
d2741af24b Add name to config (#2128) 2018-11-27 20:26:56 +01:00
Zack Arnett
f04f58ac88 Change Title to Name (#2129) 2018-11-27 20:25:52 +01:00
Zack Arnett
8757dbb664 Plant add name (#2130) 2018-11-27 20:22:53 +01:00
Zack Arnett
ffc7f9706d Update Aspect Ratio on Map card (#2126)
* Update Aspect Ratio

* Update Card Size
2018-11-27 11:48:41 -05:00
Zack Arnett
4487c3dc1a Removes Height, Light Width and Line Color from Sensor (#2122)
* Sensor fix

* SVG prettified - Use Accent Color only - Dont Calc height

* Prettier
2018-11-27 17:17:59 +01:00
Bram Kragten
97f5d8e7e2 Move lovelace data to /src/data/lovelace.ts (#2119)
* move lovelace data

* move types

* change imports from cards
2018-11-27 10:05:33 +01:00
cdce8p
1cc6e09953 Add html to LitElement.prototype (#2120) 2018-11-26 23:27:00 +01:00
Zack Arnett
3752530f96 Update UI in the config Elements (#2117)
* UpdateUI

* Updating continues

* Update name of file
2018-11-26 21:01:07 +01:00
Ian Richardson
21be35bc46 Conert shopping-list update to WebSockets (#2114) 2018-11-26 14:46:40 +01:00
Paulus Schoutsen
bb8ec4b2ef Bumped version to 20181126.0 2018-11-26 14:39:06 +01:00
Paulus Schoutsen
278ea184cc Update translations 2018-11-26 14:39:01 +01:00
Paulus Schoutsen
0b17a85c3b Bumped version to 20181121.1 2018-11-26 14:11:11 +01:00
Paulus Schoutsen
0be0e9792f Convert authorize page to lit (#2115)
* Convert authorize page to lit

* Don't use ha-markdown

* Simplify CSS
2018-11-26 14:11:02 +01:00
Paulus Schoutsen
14409ff5b7 Use overrideIcon via data binding (#2078) 2018-11-26 14:11:01 +01:00
Bram Kragten
d24bc3c07c Dont change config on init (#2044)
* dont change config on init

* set default title empty

* used firstUpdated instead of updated

* prevent double events

* check if val changed

* typing

* clean

* lint

* clean

* prettier is having a fight
2018-11-26 14:11:00 +01:00
Paulus Schoutsen
5ab419534c Convert authorize page to lit (#2115)
* Convert authorize page to lit

* Don't use ha-markdown

* Simplify CSS
2018-11-26 14:10:01 +01:00
Paulus Schoutsen
07b65f37db Add Cloud Webhook management (#2102)
* Add Cloud Webhook support

* Lint

* Tweak text

* Rename it to cloudhook

* Fix final type

* fix type

* Catch null
2018-11-26 14:09:27 +01:00
Paulus Schoutsen
8ad5280501 Document types in fireEvent (#2108)
* Document types in fireEvent

* Fix more types for fireEvent

* Adjust new code to new fireEvent
2018-11-25 20:47:29 +01:00
Bram Kragten
69df6179bb Add dialog to save config (#2100)
* Add dialog to save config

* Change types

* Helper funcs for register dialog

* Clean up

* Migrate config after save

* Clean up

* Unused imports

* Comments

* Missed half...

* cardConfig cant be undefined
2018-11-25 20:09:32 +01:00
Paulus Schoutsen
b939ae6ab4 Fix wrong import (#2106) 2018-11-25 15:18:41 +01:00
Bram Kragten
101a364a83 Type LovelaceConfig -> LovelaceCardConfig (#2103)
* LovelaceConfig -> LovelaceCardConfig

* Typo
2018-11-24 14:03:54 +01:00
Paulus Schoutsen
412b7595d2 Handle non existing states (#2098) 2018-11-23 20:42:41 +01:00
Paulus Schoutsen
a7ab652dd3 Add support for timestamp device class (#2087) 2018-11-23 19:38:28 +01:00
Paulus Schoutsen
d41a4cf78b Generate Lovelace config on the fly (#2091)
* Generate Lovelace config on the fly

* Disable editing

* Fix domain name title rendering
2018-11-23 17:39:50 +01:00
Simon Holzmayer
785ed6f9db add "for" input field to numeric_state in trigger editor (#2081) 2018-11-23 11:52:48 +01:00
Ian Richardson
6885abd234 Convert shopping-list add item call to websockets (#2080) 2018-11-23 08:56:35 +01:00
Ian Richardson
3497cb892e Convert toggle functions to TypeScript (#2082)
* Convert toggle functions to TypeScript

* Update hui-picture-glance-card.ts

* Update hui-picture-glance-card.ts
2018-11-22 12:48:12 +01:00
Bram Kragten
a82561355c Dont change config on init (#2044)
* dont change config on init

* set default title empty

* used firstUpdated instead of updated

* prevent double events

* check if val changed

* typing

* clean

* lint

* clean

* prettier is having a fight
2018-11-21 22:15:22 -05:00
Paulus Schoutsen
f054cdc9ef Use overrideIcon via data binding (#2078) 2018-11-21 21:11:00 +01:00
Paulus Schoutsen
463c7eae54 Merge pull request #2077 from home-assistant/dev
20181121.0
2018-11-21 20:14:51 +01:00
Paulus Schoutsen
cbb703e5c1 Bumped version to 20181121.0 2018-11-21 20:04:19 +01:00
Paulus Schoutsen
e4dc1884f8 Update translations 2018-11-21 20:04:10 +01:00
Paulus Schoutsen
f72a2b7ef8 Merge remote-tracking branch 'origin/master' into dev 2018-11-21 20:02:56 +01:00
Bram Kragten
39819c5c58 Hassio: Fix download snapshot (#2071)
* Fix download snapshot

* async + helper

* pretty

* move catch

* fix

* Update data.ts
2018-11-21 19:28:48 +01:00
Zack Arnett
49542c49fa Entities Card UI Editor (#2072)
* Entity Card ui + extras

* Travis Fix

* Bram already has this in  another PR
2018-11-21 09:29:06 +01:00
Ian Richardson
86e501f0aa Convert retrieval of items in shopping-list to websockets (#2041)
* Convert shopping-list to websockets

* Update shopping-list.ts

* Scale back to just retrieval of items to WS
2018-11-21 09:25:30 +01:00
Paulus Schoutsen
2ca3a784e2 Allow Google unlocking locks (#2073)
* Allow Google unlocking locks

* Fix missing type
2018-11-20 23:23:12 +01:00
Paulus Schoutsen
c01bd57ba5 Fix compression hassio build 2018-11-20 17:07:26 +01:00
Paulus Schoutsen
ba5d224080 Gen MDI icons during hassio build 2018-11-20 16:16:54 +01:00
Ronen Hayun
5da16db81b RTL support (POC) (#2014)
* RTL support (POC)

* restore yarn.lock
2018-11-20 13:10:40 +01:00
Ian Richardson
a9704b110d Convert hui-input-select-entity-row to TypeScript/LitElement (#2048)
* Convert hui-input-select-entity-row to TypeScript/LitElement

* Address Travis issues

* Address review comments

* Return callService promise

* Remove _selected
2018-11-20 13:09:52 +01:00
Ian Richardson
b8f048d96a Convert hui-input-text-entity-row to TypeScript/LitElement (#2050)
* Convert hui-input-text-entity-row to TypeScript/LitElement

* Address review comments

* Address review comments

Does anyone know why line 74 seemingly doesn't fire when the value hasn't changed? I think we should blur regardless if the value changed or not on "enter" but only seems to work when setValue is called

* Return promise from call service
2018-11-20 13:09:45 +01:00
Paulus Schoutsen
c20a285003 Pin Lit-HTML and Lit-Element (#2070) 2018-11-20 13:01:21 +01:00
Karl Kihlström
07cf1141c5 Improve sensor graph algorithm (#2069) 2018-11-20 12:41:47 +01:00
Zack Arnett
ef2aa2ea6f Picture Glance Conversion to TS (#2029)
* First Commit

* Convert to TS

* Extract entity render to own function

* Making it one function like not an idiot

* Addressing Reviews
2018-11-20 12:24:30 +01:00
Pascal Vizeli
2058e0d3fb Fix gz build 2018-11-20 11:36:45 +01:00
Bram Kragten
773711a2d5 Update Hassio with security options (#2067)
* new security options

* Add more info modal

Added more info modal and moved the security components.

* fixes

* show apparmor only if not default

* add colors to apparmor
2018-11-20 10:14:46 +01:00
Bram Kragten
0bb85bc895 Added migrate dialog when card has no ID (#2008)
* Added migrate dialog when card has no ID

* typos

* Fix error messages

* cardId should be a string

* Add translation

* Only load yaml in yaml editor

* revert name change

* Combine migrate and edit in one dialog

* lint

* fixes + inlude and secret yaml

* resize after toggle preview -> value>config

* add loading spinners

* only create preview when type changes

* loader on yaml editor

* Fixed loading spinner not disappearing

* moved dialog

* disable toggle if not avail

* address comments

* cleanup showDialog
2018-11-19 22:19:50 +01:00
Petro31
9a9986cf17 Flatline fix for Sensor Cards (#2064)
* Update hui-sensor-card.js

This fixes issues where the first history item was repeated many times at the start of the graph resulting in a flat line that does not represent the data.

Also, the graph now has the ability to reach a 1 to 1 history graph to sensor graph point representation.  Before 2 to 1 was the highest resolution possible.  This was due to using history.length - 1 as the denominator in cases where the user set the accuracy larger than the total number of points in the history.

* Update hui-sensor-card.js

* Update hui-sensor-card.js

* Update hui-sensor-card.js

* Update hui-sensor-card.js

* Update hui-sensor-card.js
2018-11-19 16:47:30 +01:00
Jack Wilsdon
1bb62bfc05 Only add a separating colon if there is a valid prefix and suffix (#2060)
This commit changes the media player entity row to only add a colon
separator to the status line when there is both a prefix and a suffix.
2018-11-19 11:59:10 +01:00
Ian Richardson
f92f89e8e8 Convert compute functions to TypeScript (#2055)
* Convert compute functions to TypeScript

* Address review comment

* Address Travis complaint

* Attempt to not be dumb :)
2018-11-18 14:50:21 +01:00
Paulus Schoutsen
b1a50aa0e0 Fix hassio develop script 2018-11-18 12:25:16 +01:00
Jack Wilsdon
8c2a2fc043 Remove extra > (#2062) 2018-11-17 12:08:52 -05:00
callifo
adb39fd820 Update Lovelace Thermostat Card to include all supported thermostat m… (#2039)
* Update Lovelace Thermostat Card to include all supported thermostat modes

* Update hui-thermostat-card.ts

* Update en.json to include

* Update en.json

* Revert "Update en.json"

This reverts commit a71ed49b78.

* Revert "Update en.json to include"

This reverts commit 68b95ca456.

* Removed manual mode, covered in PR #2036

* Update dry colour to #efbd07
2018-11-14 10:16:53 -05:00
TomMini
8a9762dd93 Add icon for manual mode (#2036)
* Add icon for manual mode

* Added state.climate.manual to translations

* Added manual color blue
2018-11-13 22:32:50 -05:00
Paulus Schoutsen
4407da9364 Remove extra > 2018-11-13 17:04:15 +01:00
Paulus Schoutsen
b533e4d093 Bumped version to 20181112.0 2018-11-12 10:17:51 +01:00
Ian Richardson
239ec5fb53 Ability to add items to shopping-list-card (#2035)
* Ability to add items to shopping-list-card

* Address review comments
2018-11-12 10:17:19 +01:00
Thomas Lovén
d974d5dc52 Fix hold_action not working on chrome for android (#2011) 2018-11-12 10:17:18 +01:00
Zack Arnett
2076949289 Glance fix (#2040) 2018-11-12 08:05:05 +01:00
Paulus Schoutsen
65bd7fd64f Update translations 2018-11-11 22:54:04 +01:00
Paulus Schoutsen
1f0c7297ce Upgrade Lit (#2032) 2018-11-10 20:36:25 +01:00
Ian Richardson
efbd97f9a4 Add clearing of checked items to shopping-list-card (#2034) 2018-11-10 20:34:31 +01:00
Paulus Schoutsen
2ccfccc23f Bumped version to 20181103.3 2018-11-09 15:39:07 +01:00
Paulus Schoutsen
acbcb6bd45 Fix int in ha-form (#2033) 2018-11-09 15:38:56 +01:00
Paulus Schoutsen
e580dbe7f2 Fix int in ha-form (#2033) 2018-11-09 15:38:30 +01:00
Nikolay Vasilchuk
9f55678cb3 Timezone support for displaying header with date in logbook (#2026)
* Fixed display header with date in logbook

* prettier fix
2018-11-09 10:59:03 +01:00
Ian Richardson
9c2b85dd6e Add checked item section to shopping-list-card (#2005)
* Add checked item section to shopping-list-card

* Not getting value back from `this.localize`? at line 109

* Alignment of label

* Address review comment

* Address review comments

* Address review comment and fix Travis errors

* Address review comments

* Hide checked label when empty

* Address review comment
2018-11-09 10:13:44 +01:00
Ian Richardson
cb640c2e71 Convert hui-picture-card to TypeScript/LitElement (#2030) 2018-11-09 10:10:17 +01:00
Paulus Schoutsen
56bdb6e352 Fix fetching sub 2018-11-08 19:32:58 +01:00
Paulus Schoutsen
81e1e5be8f Fix link color in persistent notification (#2023) 2018-11-08 10:31:49 -05:00
Ian Richardson
6c44a92e2c Convert hui-group-entity-row to TypeScript/LitElement (#2015)
* Convert hui-group-entity-row to TypeScript/LitElement

* Address review comment and Travis errors
2018-11-08 09:41:46 +01:00
Ian Richardson
9596f737e8 Convert hui-text-entity-row to TypeScript/LitElement (#2017)
* Convert hui-text-entity-row to TypeScript/LitElement

* Address review comments

* Fix Travis errors
2018-11-08 09:41:25 +01:00
Ian Richardson
ad5f815273 Convert hui-scene-entity-row to TypeScript/LitElement (#2021)
* Convert hui-scene-entity-row to TypeScript/LitElement

script-entity-row and this could probably extend a common base class. The only thing that differs them is the domain used in the callService and the button text.

* Stop more-info
2018-11-08 09:39:02 +01:00
Ian Richardson
4a893d96a0 Convert hui-script-entity-row to TypeScript/LitElement (#2020)
* Convert hui-script-entity-row to TypeScript/LitElement

* Stop more-info
2018-11-08 09:37:22 +01:00
Ian Richardson
59a681fcb7 Convert hui-lock-entity-row to TypeScript/LitElement (#2022) 2018-11-08 09:35:55 +01:00
Paulus Schoutsen
787ea885cc Add text color to error row (#2007)
Fixes #2002
2018-11-07 11:37:04 -05:00
Paulus Schoutsen
a26a37233b Use non-forked version of fecha 2018-11-07 10:48:49 +01:00
Paulus Schoutsen
c1e3259b08 Bumped version to 20181107.0 2018-11-07 10:31:22 +01:00
Paulus Schoutsen
9c735bb088 Merge branch 'master' into dev 2018-11-07 10:28:10 +01:00
Paulus Schoutsen
10092dcadf Bumped version to 2018117.0 2018-11-07 10:27:07 +01:00
Paulus Schoutsen
d31cea70bc Update translations 2018-11-07 10:26:19 +01:00
Paulus Schoutsen
b04ab6faa1 Bumped version to 20181103.2 2018-11-07 10:11:47 +01:00
Ian Richardson
23163b3095 Fix iframe aspect_ratio (#2004) 2018-11-07 10:11:35 +01:00
Thomas Lovén
849d7d2d95 Make conditional not take up space in stacks when hidden (#1999)
* Make conditional not take up space in stacks when hidden

* Update hui-conditional-card.ts
2018-11-07 10:11:35 +01:00
Paulus Schoutsen
a58a324073 Format html (#2006)
* Upgrade prettier

* Format files with prettier
2018-11-07 09:56:43 +01:00
Ian Richardson
7c2135f444 Fix iframe aspect_ratio (#2004) 2018-11-07 09:42:46 +01:00
Ian Richardson
f9719957b0 Convert call-service to TypeScript (#1985)
* Convert call-service to TypeScript

* Address Travis error

* Address review comments
2018-11-07 09:31:01 +01:00
Ian Richardson
9ce74e2da1 Convert hui-toggle-entity-row to TypeScript/LitElement (#1939)
* Convert hui-toggle-entity-row to TypeScript/LitElement

* Properly set generic-entity properties

* Addressed review comments

* Address review comments
2018-11-07 09:30:05 +01:00
Paulus Schoutsen
14b959b91b Fix cover showing error 2018-11-06 15:17:01 +01:00
Paulus Schoutsen
e2b9893b17 Expose entities for Google/Alexa (#680)
* Add entity filter

* Show exposed entities on cloud panel

* Fix tests

* Revert some testing changes

* Cursor: pointer

* Fix

* Update tests to TS
2018-11-06 14:22:59 +01:00
Thomas Lovén
54e43758d3 Make conditional not take up space in stacks when hidden (#1999)
* Make conditional not take up space in stacks when hidden

* Update hui-conditional-card.ts
2018-11-06 12:39:00 +01:00
Paulus Schoutsen
92af45d7fd Lint 2018-11-06 11:51:33 +01:00
Michael Scherer
5891a6ee7d Fix for thermostats without current temperature (#1979)
* fix for thermostats without current temperature

* make linter happy

* always render value, uom only when there is a value

* Update hui-thermostat-card.ts
2018-11-06 11:25:25 +01:00
Ian Richardson
c10e409634 Convert cover-row to TypeScript/LitElement (#1933)
* Convert cover-row to TypeScript/LitElement

* Extract `supports` methods from cover model

* Address review comments

* Revert line endings mixup

I suck at vs code apparently...

* Address review comments

* Address review comments: error-row not working
2018-11-06 11:07:15 +01:00
Ian Richardson
6432207bf1 New Card: Shopping List (#1970)
* New Card: Shopping List

Following features:
- Add item
- Edit item
- Complete item
- Clear items

* Address Travis complaint

* Addressed review comments

* Update translation variable name

* Line up input row text

* Taking MVP to heart

Addressed review comments and scaled this back to just get a simple shopping list card out there and we can discuss/debate how best to add the additional pieces with smaller PRs

* Remove calling connected in set hass
2018-11-06 10:47:24 +01:00
Zack Arnett
935639e5e0 Add getElementConfig to Glance + Add Form UI for updating YAML (#1944)
* Working version

* Working kind of

* Some more changes

* More review changes

* Progress

* Review updates

* Adding new changes

* Remove un-needed code

* Adding Types

* Updating UI Editor a bit

* Updating from missed reviews

* Updates from Reviews

* Yaml is not update each time. Instead stored as LovelaceConfig.

* Update to not pull config from preview but store it each time it changed

* Updating from Reviews

* Try catch fix

* Update hui-dialog-edit-card.ts
2018-11-06 10:09:28 +01:00
Paulus Schoutsen
cdb2093ea6 Ts all the tests (#1998)
* Convert tests to TypeScript

* Add types for tests

* Rename files to TS

* Fix up test imports

* Fix TSC errors

* Liiiint

* Add types to util method signatures

* Some more types
2018-11-06 10:09:09 +01:00
Karl Kihlström
856ef34964 <path> error fix & literals (#1993) 2018-11-05 19:46:41 +01:00
Karl Kihlström
cf19ceb193 Filter out non number states instead of assigning them 0 (#1987) 2018-11-05 16:28:36 +01:00
Paulus Schoutsen
e5fe2950af Split up cloud card (#1983)
* Split up cloud card

* Fix quotes
2018-11-05 09:59:19 +01:00
Paulus Schoutsen
1ca242405b Convert auth to TS (#1976)
* Convert auth to TS

* Lint

* Update HA-JS-WS to 3.2.0

* Migrate ws collections to TS

* Upgrade to latest HAWS

* Bump HAWS

* Lint

* Add types to WS calls
2018-11-04 10:01:33 +01:00
Michael Scherer
bcbf0ba75a add vscode extension recommondations (#1978) 2018-11-03 22:57:45 +01:00
Michael Scherer
4810042373 Hide state if its unknown e.g. the climate entity does not have one (#1977)
* Hide state if its unknown e.g. the climate entity does not have one

* state can not be null

* better comparsion

* use double quotes
2018-11-03 21:51:55 +01:00
Paulus Schoutsen
e1c90d74e3 Bumped version to 20181103.1 2018-11-03 19:17:48 +01:00
Zack Arnett
984570c55b Revert RTL PR (#1975)
* Revert RTL PR

* Missed this change
2018-11-03 19:16:55 +01:00
Zack Arnett
f489d88be4 Revert RTL PR (#1975)
* Revert RTL PR

* Missed this change
2018-11-03 19:15:51 +01:00
Paulus Schoutsen
6a84395303 Merge pull request #1974 from home-assistant/dev
20181103.0
2018-11-03 13:40:00 +01:00
Paulus Schoutsen
a3847ddd2a Merge remote-tracking branch 'origin/master' into dev 2018-11-03 13:26:48 +01:00
Paulus Schoutsen
dc0f023754 Version bump to 20181103.0 2018-11-03 13:24:36 +01:00
Paulus Schoutsen
89677577ef Update translations 2018-11-03 13:24:20 +01:00
Ronen Hayun
0922314134 POC for RTL support (#1966)
* POC for RTL support

* POC for RTL support

* POC for RTL support
2018-11-03 13:21:58 +01:00
Paulus Schoutsen
c68604d1fe Mark edit as alpha (#1973) 2018-11-03 13:19:44 +01:00
Zack Arnett
372cfdecf4 Light, Thermostat, and Gauge - Theme Addition (#1947)
* Update to have theme options

* Make ShouldUpdate into a help function

* Adding types to changed function
2018-11-02 20:59:14 +01:00
Paulus Schoutsen
ef40a0ceea Update frontend to 20181026.4 2018-11-02 20:08:01 +01:00
Paulus Schoutsen
343d18241b Update translations 2018-11-02 20:07:46 +01:00
Paulus Schoutsen
2ecb6e0f9e pic 2018-11-02 20:07:32 +01:00
Paulus Schoutsen
38b8e5e7b7 Update translations 2018-11-02 20:07:06 +01:00
Zack Arnett
058f8d178e Adding div arround each row to deal with spacing 2018-11-02 19:48:13 +01:00
Paulus Schoutsen
fbc1a722bd Normalize all line endings 2018-11-02 16:00:25 +01:00
Paulus Schoutsen
727cfe92e3 Force correct line endings 2018-11-02 15:59:48 +01:00
Paulus Schoutsen
4bcb13486e Version bump to 20181026.3 2018-11-02 14:06:02 +01:00
Paulus Schoutsen
4aa8603ebf Stop release if TypeScript throws 2018-11-02 14:05:49 +01:00
Paulus Schoutsen
ba33c8a456 Fix broken shit 2018-11-02 14:05:36 +01:00
Paulus Schoutsen
6f4cd88988 Merge branch 'master' into dev 2018-11-02 12:44:44 +01:00
Paulus Schoutsen
1f2deff6f0 Version bump to 20181026.2 2018-11-02 12:29:51 +01:00
Paulus Schoutsen
575882be5a Fix conditional card with undefined hass (#1927)
* Fix conditional card with undefined hass

* Conditional appendChild

* Allow updating card properly
2018-11-02 12:27:56 +01:00
Paulus Schoutsen
d591c45e4d Propagate hass correctly (#1918) 2018-11-02 12:27:55 +01:00
Paulus Schoutsen
f9b06adc9f Handle no operation mode (#1901)
* Handle no operation mode

* Upgrade HAWS so we can use correct types

* Lint
2018-11-02 12:27:55 +01:00
Ian Richardson
6cc67dc790 New warning row for non-existent entities (#1946)
* New warning row for non-existent entities

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

* Address my own comments
2018-11-02 12:26:20 +01:00
Ian Richardson
c0c7c0f41a Remove .js from imports (#1948)
* Remove .js from lovelace

Also cleaned up some trailing whitespace

* Go big or go home

* More removals

* Revert changes to gallery webpack

* Revert changes to webpack.config.js
2018-11-02 12:26:03 +01:00
Paulus Schoutsen
eb505d4bd7 don't compress for CI runs (#1949) 2018-11-02 12:15:45 +01:00
Zack Arnett
aebd1a1be1 Remove side effects of render and Add types - Button/Glance (#1919)
* Remove side effects of render and Add types

* Addressing changes

* Updating when to apply theme

* Review Updates

* Fixing last reviews

* Updates from travis
2018-11-02 10:13:49 +01:00
Bram Kragten
447c06d817 Fix overlapping device cards (#1940) 2018-11-01 09:18:12 +01:00
Ian Richardson
ce78131258 Remove unused mixin (#1934) 2018-10-31 09:49:11 +01:00
Ian Richardson
acab465c96 Move click and tooltip function to LL folder (#1935) 2018-10-31 09:45:38 +01:00
Zack Arnett
4ea83b8bd5 Fixes: #1902 - Thermostat/Light Background (#1931)
* Thermostat theme fixes

* Might as well put the light card in there
2018-10-31 09:38:56 +01:00
Zack Arnett
094eb632f2 Update Types for various cards (#1920)
* Update Types

* Travis fix

* Travis change

* Add HTMLElementTagNameMap for gauge

* Review Changes

* Formatting values as string to be accepted into format temp
2018-10-31 09:34:12 +01:00
Paulus Schoutsen
03b1e40593 Add preview to edit LL card (#1929) 2018-10-30 20:31:56 +01:00
Bram Kragten
2164b629cf catch translation errors (#1928) 2018-10-30 19:16:20 +01:00
Thomas Lovén
a081047008 Fix calculation of glance card size (#1930) 2018-10-30 15:57:08 +01:00
Paulus Schoutsen
2e395c1b0d Update version to 20181030.0 2018-10-30 11:33:37 +01:00
Paulus Schoutsen
520e03a612 Update translations 2018-10-30 11:33:20 +01:00
Paulus Schoutsen
a771a44557 Fix conditional card with undefined hass (#1927)
* Fix conditional card with undefined hass

* Conditional appendChild

* Allow updating card properly
2018-10-30 11:30:57 +01:00
Ian Richardson
38bfe8c8de Convert hui-call-service-row to TypeScript/LitElement (#1894)
* Convert hui-call-service-row to TypeScript/LitElement

* Update on _config change

* Addressed review comments

* Made `service_data` optional and verified that `callService` does use `entity` if it is available in the passed config.
* Removed check in `entities-card` for `service-data` and will remove the full config check once other PRs have been applied to avoid merge conflicts
* Will create a docs PR to update the docs

* Addressed review comments

* Removed entity config check in entities-card
* Made `icon` optional. Default now `remote`
* Made `action_name` optional. Default now 'Run'

* `.icon`
2018-10-30 11:15:12 +01:00
Zack Arnett
7ca2ef4c4c Addition to Edit Love Lace Cards (#1885)
* Initial Commit

* Removing old code

* Switching to litlement and ts

* remove .ts from extension

* Addressing a few reviews

* Added ShowDialog Still no whammy

* Fix some things

* Extract one more data method

* Add more types

* Clean up imports

* Call super

* Finishing touches

* Fix typescript check
2018-10-30 11:15:02 +01:00
Paulus Schoutsen
de5f02d706 Update TypeScript to latest (#1924) 2018-10-30 11:05:56 +01:00
Paulus Schoutsen
6f7ddef4a4 Update mdi icons (#1922) 2018-10-30 11:04:25 +01:00
Paulus Schoutsen
d78b5fac73 Update vaadin components (#1923) 2018-10-30 11:04:17 +01:00
Paulus Schoutsen
7cf65ba066 Update testing tools (#1925) 2018-10-30 11:04:06 +01:00
Paulus Schoutsen
91966f676a Update polymer (#1921) 2018-10-30 09:25:04 +01:00
Paulus Schoutsen
5a1ca3855b Propagate hass correctly (#1918) 2018-10-30 08:38:27 +01:00
Zack Arnett
226203143b Merge pull request #1874 from zsarnett/light-card
LoveLace Light Card
2018-10-29 23:39:12 -04:00
Zack Arnett
a5304115f0 UPdate Type for color 2018-10-29 22:24:23 -04:00
Zack Arnett
f7458b8d41 Add Hs_color to types 2018-10-29 20:12:20 -04:00
Zack Arnett
410b66d40f Adding more types 2018-10-29 18:15:35 -04:00
Zack Arnett
1fcf510278 Adding Type to Returns 2018-10-29 18:11:34 -04:00
Zack Arnett
bb4ce278b0 Fixing Gallery and updating timeout type 2018-10-29 18:07:57 -04:00
Zack Arnett
6dac48e5b8 Merge pull request #1913 from home-assistant/entities-padding-fix
Fixes #1910 - Entities Margin correction
2018-10-29 15:45:15 -04:00
Zack Arnett
7178d208d3 Light Card addition 2018-10-29 14:33:38 -04:00
Zack Arnett
00935c86d0 Add themes to entities (#1909) 2018-10-29 19:29:27 +01:00
Zack Arnett
3dde78cadf Adding div arround each row to deal with spacing 2018-10-29 13:54:10 -04:00
Zack Arnett
630214ddb9 Merge pull request #1911 from home-assistant/add-bypass
add arm_custom_bypass
2018-10-29 13:25:56 -04:00
Zack Arnett
b3f8781646 Merge pull request #1912 from home-assistant/prettier-fix
Fixes errors in PRs based on dev
2018-10-29 11:54:36 -04:00
Zack Arnett
b717402d26 Fixes errors in PRs based on dev 2018-10-29 11:37:23 -04:00
Zack Arnett
0d339e0cba add arm_custom_bypass 2018-10-29 11:33:01 -04:00
Ian Richardson
1d014bf6e3 Convert hui-section-row to TypeScript/LitElement (#1897)
* Convert hui-section-row to TypeScript/LitElement

* Address review comments
2018-10-29 10:53:17 +01:00
Zack Arnett
5ab15dc27f add arm night (#1908) 2018-10-29 08:59:33 +01:00
Timmo
c347be6f35 Lovelace - Weather Card: Adjust margins and move text (#1880)
* Thermostat fix (#1867)

* Thermostat fix

* Fix for unknown operation mode

* No title for you

* I suck at removing unnecessary things

* Fixing pointless Ternary operator

* Add version bump script

* Fix hass setting on stack (#1868)

* Fix hass setting on stack

* Don't set hass on pic elements if undefined

* Don't set hass on entity rows if undefined

* prefix config prop

* Pic elements set hass yoooo

* Remove interface

* Make stack config private

* Fix import

* Lint

* Bumped version to 20181026.1

* 🔨 move not text to header and name to secondary text

* 🔨 reduce some other margins
2018-10-29 08:30:56 +01:00
Paulus Schoutsen
d47c2a6fe0 Merge remote-tracking branch 'origin/master' into dev 2018-10-29 08:29:44 +01:00
Ian Richardson
82eb33a7d4 Convert hui-climate-entity-row to TypeScript/LitElement (#1899)
* Convert hui-climate-entity-row to TypeScript/LitElement

* Address review comments

* Address review comments
2018-10-28 21:09:41 +01:00
Ian Richardson
4f6bae193d Convert hui-weblink-row to TypeScript/LitElement (#1898)
* Convert hui-weblink-row to TypeScript/LitElement

* Addressed review comments

* Made name and icon fields optional. Will create a corresponding PR to the docs to update them as Optional
* Updated entities-card to not check weblink config as that is the job of the element (entities-card should be updated to not check service-call config either as that is the job of the row element as well)

* Addressed review comments

That's cool!
2018-10-28 21:08:58 +01:00
Ian Richardson
b8752c4158 Convert hui-divider-row to TypeScript/LitElement (#1896)
* Convert hui-divider-row to TypeScript/LitElement

* Add return types

* Fixed style issues

* Address review comments
2018-10-28 21:07:30 +01:00
Zack Arnett
b6d0d777bf Gauge convert and fix issues (#1886)
* Gauge convert and fix issues

* Fixing Unit_of_Measurement

* Addressing Reviews

* Updating typing
2018-10-28 20:47:21 +01:00
Ian Richardson
8c155d4d0e Convert hui-state-badge-element to TypeScript/LitElement (#1892)
* Convert hui-state-badge-element to TypeScript/LitElement

The state image is not updating. This is true with the image-element as well. There's probably something simple between them both that I'm missing in my syntax for defining them

* Added htmlmap interface

* Addressed review comments
2018-10-28 20:36:53 +01:00
Ian Richardson
bdf5d0f5c6 Convert hui-state-icon-element to TypeScript/LitElement (#1891)
* Convert hui-state-icon-element to TypeScript/LitElement

The icon is not currently dispalying and therefor cannot determine if changing as well. Everything else is working. Should probably create a base class for all these elements to extend for simple things like checking for the entity and setting hass correctly once I get that sorted out.

* Fixed setting of properties

* Remove ! on this._config

* Addressed review comments

* Moved event handlers to private functions to avoid arrow function creation on each render
* Check for `hass` before looking for state
* Movevd absolute import above relative imports
2018-10-28 20:35:48 +01:00
Ian Richardson
4959b861bd Convert hui-image-element to TypeScript/LitElement (#1890)
* Convert hui-image-element to TypeScript/LitElement

WIP
Some of the state portions passed down to ha-image are not updating the view.

* Fixed setting of properties

* Add tagnamemap interface

* Address review comments
2018-10-28 20:25:12 +01:00
Paulus Schoutsen
a4fa0ae64b Handle no operation mode (#1901)
* Handle no operation mode

* Upgrade HAWS so we can use correct types

* Lint
2018-10-28 20:07:05 +01:00
Ian Richardson
7cd5f36c7a Convert hui-service-button-element to TypeScript/LitElement (#1888)
* Convert hui-service-button-element to TypeScript/LitElement

Will need to rebase once hui-icon-element PR is merged

* Added return types

* Convert ha-call-service-button attributes to properties

* Re-order imports
2018-10-28 13:57:52 +01:00
Ian Richardson
ecfdb16957 Convert hui-state-label-element to TypeScript/LitElement (#1893) 2018-10-28 09:09:43 +01:00
Ian Richardson
d31195fc87 Add to HTML tag name map (#1900) 2018-10-28 09:00:37 +01:00
eyager1
f3ef4cef74 Change icon to set operation_mode to "off" (#1883)
The choice of the "fan-off" icon to set the climate operation_mode to off should be changed . Many HVAC thermostats have a manual fan control.   The use of the fan icon in this card would imply that it would change or toggle the fan_mode of a climate component, not the operation_mode.  If setting the fan_mode of a climate component via this card were to be implemented in the future the use of "fan" and "fan-off" icons would be the most logical choices for icons.   I propose changing the icon that would set the operation_mode to off to generic power icon such as "power", or "power-off" to avoid confusion and to reserve the "fan" and "fan-off" icons for future use to change the the state of fan_mode.
2018-10-28 08:57:54 +01:00
Ian Richardson
ec6db9c8ca Merge pull request #1895 from iantrich/row-types
Make hass optional within EntityRow as it may not be defined
2018-10-27 21:17:45 -05:00
Ian Richardson
bb483c9d72 Make hass optional within EntityRow as it may not be defined 2018-10-27 21:00:06 -05:00
Zack Arnett
414448137a Merge pull request #1871 from iantrich/typescript-icon-element
Convert hui-icon-element to TypeScript/LitElement
2018-10-27 21:50:09 -04:00
Ian Richardson
d0acef3ecb Add return types 2018-10-27 18:01:07 -05:00
Ian Richardson
5617416932 cleaned up elementconfig type order 2018-10-27 16:54:27 -05:00
Ian Richardson
bf0eb798d9 Implemented navigate function
Fix linting error
2018-10-27 16:51:44 -05:00
Ian Richardson
8afc3812b7 Remove element-click refactor
Was unable to get computeTooltip to work for non-converted elements. There are not many elements that use this so will just remove this mixin once conversion of all to TS has been completed.
2018-10-27 16:40:43 -05:00
Ian Richardson
5a7841e6bf Pass correct node to handleClick 2018-10-27 15:51:34 -05:00
Ian Richardson
2758e86fab Convert hui-icon-element to TypeScript/LitElement and extract ElementClick mixin functions 2018-10-27 15:51:34 -05:00
Paulus Schoutsen
d9935a714e Bumped version to 20181026.1 2018-10-27 12:05:14 +02:00
Paulus Schoutsen
d6a9d6829b Fix hass setting on stack (#1868)
* Fix hass setting on stack

* Don't set hass on pic elements if undefined

* Don't set hass on entity rows if undefined

* prefix config prop

* Pic elements set hass yoooo

* Remove interface

* Make stack config private

* Fix import

* Lint
2018-10-27 12:05:06 +02:00
Paulus Schoutsen
8b02371786 Fix hass setting on stack (#1868)
* Fix hass setting on stack

* Don't set hass on pic elements if undefined

* Don't set hass on entity rows if undefined

* prefix config prop

* Pic elements set hass yoooo

* Remove interface

* Make stack config private

* Fix import

* Lint
2018-10-27 12:00:40 +02:00
Paulus Schoutsen
9a5b692204 Add version bump script 2018-10-27 11:40:52 +02:00
Zack Arnett
0b504c7df2 Thermostat fix (#1867)
* Thermostat fix

* Fix for unknown operation mode

* No title for you

* I suck at removing unnecessary things

* Fixing pointless Ternary operator
2018-10-27 11:16:10 +02:00
Zack Arnett
6cab3bbc8e Thermostat fix (#1867)
* Thermostat fix

* Fix for unknown operation mode

* No title for you

* I suck at removing unnecessary things

* Fixing pointless Ternary operator
2018-10-27 10:59:33 +02:00
Paulus Schoutsen
35194cf345 Extract navigate mixin (#1865)
* Extract navigate to function

* Remove eventsmixin properly and side effects
2018-10-27 10:56:03 +02:00
Paulus Schoutsen
13c5724d7c Fix version extract (#1866) 2018-10-26 23:22:45 +02:00
Zack Arnett
156ea62ffa Merge pull request #1863 from home-assistant/states-sensor-badges
States Sensor badges Padding fix
2018-10-26 15:24:10 -04:00
Zack Arnett
22693bcbcc Updating to meet Material Design standards 2018-10-26 14:11:11 -04:00
Zack Arnett
ba70220659 Prettier fixes (#1864) 2018-10-26 19:40:53 +02:00
Gabriel Oliveira
fc96d33d6a 1813 - Improve Hass.io System cards UI (#1862)
* 1813 - Improve Hass.io System cards UI

* 1813 - Move margin to proper card file
2018-10-26 19:38:02 +02:00
Zack Arnett
685915e13c Fix States Badges Padding 2018-10-26 12:39:40 -04:00
Paulus Schoutsen
c39b17f12c Version bump to 20181026.0 2018-10-26 10:10:27 +02:00
Paulus Schoutsen
110d9a4cc1 update translations 2018-10-26 10:07:24 +02:00
Piotr Dobrowolski
8902328b30 Allow custom UI on "card" entities (#1824)
This will always render entities with `custom_ui_state_card` attribute
set in `entities` state card. Fixes custom UI on `media_player` objects.
2018-10-26 10:06:17 +02:00
Ian Richardson
7ff9211dfc Convert hui-picture-elements-card to TypeScript/LitElement (#1853)
* Convert hui-picture-elements-card to TypeScript/LitElement

Elements are not showing currently

* Address review comments

* Update demo
2018-10-26 10:04:04 +02:00
Paulus Schoutsen
17b4f873e7 Enforce prettier for TypeScript (#1860) 2018-10-26 09:37:47 +02:00
Zack Arnett
741c0c08b9 Thermostat Card LoveLace (#1814)
* POC/WIP: Thermostat Card

* Fix jQuery imports

* Cleaning out testing code and working on reviews

* Colors Dynamic + mode dynamic

* Minor changes

* adding html prefix

* Dynamic Text size and colors - getting somwhere slowly.

* Review Changes - Working version (i think)

* Updating Gallery Entry

* Travies Review

* Remove provide plugin, move CSS to JS

* Add provideHass to demo

* Demo fixes

* tweak margins

* Travis changes

* Style Tweaks

* Update to client Width range
2018-10-26 09:30:58 +02:00
Ian Richardson
c42d9385d1 Convert hui-horizontal-stack-card to TypeScript/LitElement (#1851)
* Convert horizontal-stack to TypeScript/LitElement

Base stack-card class for horizontal and vertical stack cards to extend as their code overlaps a lot. Not sure if it should be in `common` or not

* rename locale hass variable

* Address review comments

* Abstract getCardSize
2018-10-26 09:27:57 +02:00
Thomas Lovén
8cbd667286 Lovelace - Long Press for everything (#1848)
* Long-press controller and lit-directive

* Enable long-press for glance card

* Enable long-press for entity-button

* Use new long-press for picture-elements

* Enable long-press for picture-entity card
2018-10-26 09:27:39 +02:00
Paulus Schoutsen
8bf60d502a Allow mocking websocket commands in the gallery (#1859)
* Allow mocking rest/websocket commands in the gallery

* typo
2018-10-26 09:27:10 +02:00
Zack Arnett
9f60499a3f Fix for input slider on states ui (#1861) 2018-10-25 20:15:31 -04:00
Paulus Schoutsen
56a9ff2b35 Gallery: fix notifs, fake config/services, glance demo (#1857)
* Gallery: fix notifs, fake config/services, glance demo

* Fix missing glance states

* Fill in more missing entities

* Add controller to entity filter card
2018-10-25 14:05:22 +02:00
Paulus Schoutsen
8c7b62509b Fix stack card getSize (#1856) 2018-10-25 12:29:05 +02:00
Ian Richardson
5e61065b64 Convert hui-error-card to TypeScript/LitElement (#1852) 2018-10-25 09:28:15 +02:00
Zack Arnett
39dd0524f8 Merge pull request #1792 from zsarnett/entities-card-lit
Updating Entities Card to TS+lit
2018-10-24 21:12:08 -04:00
Zack Arnett
25c6a4d3a6 Switching to new version of localize 2018-10-24 16:34:20 -04:00
Zack Arnett
0b9a4c56a9 Review Update 2018-10-24 16:34:20 -04:00
Zack Arnett
faa08f9e1f Review Update 2018-10-24 16:34:20 -04:00
Zack Arnett
7fbe0937df Review updates 2018-10-24 16:34:20 -04:00
Zack Arnett
66f5e34d52 Removing log console 2018-10-24 16:34:19 -04:00
Zack Arnett
ddf59c8d5c Readding Homeasistant call in types 2018-10-24 16:34:19 -04:00
Zack Arnett
0856073e85 Changing element to unknown 2018-10-24 16:34:19 -04:00
Zack Arnett
06aef18d0c addressiing reviews 2018-10-24 16:34:19 -04:00
Zack Arnett
be63648238 Review Update 2018-10-24 16:34:19 -04:00
Zack Arnett
56e01e66fb Review fix 2018-10-24 16:34:18 -04:00
Zack Arnett
772153e58a Update entities 2018-10-24 16:34:18 -04:00
Zack Arnett
3882a5aa89 Fix for Click Event 2018-10-24 16:34:18 -04:00
Zack Arnett
edf8027bf4 Updating repeat array 2018-10-24 16:34:18 -04:00
Zack Arnett
2fd459381d Updating from Reviews 2018-10-24 16:34:17 -04:00
Zack Arnett
76e67d27e7 Update to Togglable entities 2018-10-24 16:34:17 -04:00
Zack Arnett
18be134ad8 Updating Entities Card to TS+lit 2018-10-24 16:34:17 -04:00
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
791 changed files with 60572 additions and 27540 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
}
}

11
.gitattributes vendored Normal file
View File

@@ -0,0 +1,11 @@
# Ensure Docker script files uses LF to support Docker for Windows.
# Ensure "git config --global core.autocrlf input" before you clone
* text eol=lf
*.ts whitespace=error
*.js whitespace=error
*.ico binary
*.jpg binary
*.png binary
*.zip binary
*.mp3 binary

4
.gitignore vendored
View File

@@ -21,6 +21,10 @@ lib
bin
dist
# vscode
.vscode/*
!.vscode/extensions.json
# Secrets
.lokalise_token
yarn-error.log

7
.vscode/extensions.json vendored Executable file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"eg2.tslint",
"esbenp.prettier-vscode"
]
}

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",
},
],
],
},
},
};
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,12 +1,13 @@
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import { demoConfig } from "../data/demo_config";
import { demoServices } from "../data/demo_services";
import demoResources from "../data/demo_resources";
import demoStates from "../data/demo_states";
import { createCardElement } from "../../../src/panels/lovelace/common/create-card-element";
class DemoCard extends PolymerElement {
static get template() {
@@ -37,9 +38,9 @@ class DemoCard extends PolymerElement {
}
</style>
<h2>[[config.heading]]</h2>
<div class='root'>
<div class="root">
<div id="card"></div>
<template is='dom-if' if='[[showConfig]]'>
<template is="dom-if" if="[[showConfig]]">
<pre>[[_trim(config.config)]]</pre>
</template>
</div>
@@ -50,11 +51,11 @@ class DemoCard extends PolymerElement {
return {
hass: {
type: Object,
observer: '_hassChanged',
observer: "_hassChanged",
},
config: {
type: Object,
observer: '_configChanged'
observer: "_configChanged",
},
showConfig: Boolean,
};
@@ -73,9 +74,14 @@ class DemoCard extends PolymerElement {
} else {
const hass = new HomeAssistant(demoStates);
hass.config = demoConfig;
hass.services = demoServices;
hass.resources = demoResources;
hass.language = 'en';
hass.language = "en";
hass.states = demoStates;
hass.themes = {
default_theme: "default",
themes: {},
};
el.hass = hass;
}
@@ -92,4 +98,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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-toggle-button/paper-toggle-button";
import './demo-card.js';
import "./demo-card";
class DemoCards extends PolymerElement {
static get template() {
@@ -25,18 +25,18 @@ class DemoCards extends PolymerElement {
}
</style>
<app-toolbar>
<div class='filters'>
<paper-toggle-button
checked='{{_showConfig}}'
>Show config</paper-toggle-button>
<div class="filters">
<paper-toggle-button checked="{{_showConfig}}"
>Show config</paper-toggle-button
>
</div>
</app-toolbar>
<div class='cards'>
<template is='dom-repeat' items='[[configs]]'>
<div class="cards">
<template is="dom-repeat" items="[[configs]]">
<demo-card
config='[[item]]'
show-config='[[_showConfig]]'
hass='[[hass]]'
config="[[item]]"
show-config="[[_showConfig]]"
hass="[[hass]]"
></demo-card>
</template>
</div>
@@ -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,64 +1,63 @@
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/state-summary/state-card-content";
import "../../../src/dialogs/more-info/controls/more-info-content";
import "../../../src/components/ha-card";
class DemoMoreInfo extends PolymerElement {
static get template() {
return html`
<style>
<style>
:host {
display: flex;
align-items: start;
}
ha-card {
width: 333px;
}
state-card-content {
display: block;
padding: 16px;
}
more-info-content {
padding: 0 16px;
}
pre {
width: 400px;
margin: 16px;
overflow: auto;
}
@media only screen and (max-width: 800px) {
:host {
display: flex;
align-items: start;
flex-direction: column;
}
ha-card {
width: 333px;
}
state-card-content {
display: block;
padding: 16px;
}
more-info-content {
padding: 0 16px;
}
pre {
width: 400px;
margin: 16px;
overflow: auto;
margin-left: 0;
}
}
</style>
<ha-card>
<state-card-content
state-obj="[[_stateObj]]"
hass="[[hass]]"
in-dialog
></state-card-content>
@media only screen and (max-width: 800px) {
:host {
flex-direction: column;
}
pre {
margin-left: 0;
}
}
</style>
<ha-card>
<state-card-content
state-obj="[[_stateObj]]"
hass="[[hass]]"
in-dialog
></state-card-content>
<more-info-content
hass='[[hass]]'
state-obj='[[_stateObj]]'
></more-info-content>
</ha-card>
<template is='dom-if' if='[[showConfig]]'>
<pre>[[_jsonEntity(_stateObj)]]</pre>
</template>
`;
<more-info-content
hass="[[hass]]"
state-obj="[[_stateObj]]"
></more-info-content>
</ha-card>
<template is="dom-if" if="[[showConfig]]">
<pre>[[_jsonEntity(_stateObj)]]</pre>
</template>
`;
}
static get properties() {
@@ -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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-toggle-button/paper-toggle-button";
import './demo-more-info.js';
import "./demo-more-info";
class DemoMoreInfos extends PolymerElement {
static get template() {
@@ -25,18 +25,18 @@ class DemoMoreInfos extends PolymerElement {
}
</style>
<app-toolbar>
<div class='filters'>
<paper-toggle-button
checked='{{_showConfig}}'
>Show entity</paper-toggle-button>
<div class="filters">
<paper-toggle-button checked="{{_showConfig}}"
>Show entity</paper-toggle-button
>
</div>
</app-toolbar>
<div class='cards'>
<template is='dom-repeat' items='[[entities]]'>
<div class="cards">
<template is="dom-repeat" items="[[entities]]">
<demo-more-info
entity-id='[[item]]'
show-config='[[_showConfig]]'
hass='[[hass]]'
entity-id="[[item]]"
show-config="[[_showConfig]]"
hass="[[hass]]"
></demo-more-info>
</template>
</div>
@@ -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

@@ -1,175 +1,11 @@
export default {
core: {
elevation: 300,
latitude: 51.5287352,
longitude: -0.381773,
unit_system: {
length: 'km',
mass: 'kg',
temperature: '°C',
volume: 'L'
}
export const demoConfig = {
elevation: 300,
latitude: 51.5287352,
longitude: -0.381773,
unit_system: {
length: "km",
mass: "kg",
temperature: "°C",
volume: "L",
},
services: {
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'
],
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'
],
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'
],
fan: [
'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'
],
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'
],
homeassistant: [
'stop',
'check_config',
'reload_core_config',
'turn_on',
'turn_off',
'restart',
'toggle'
],
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",
},
};

View File

@@ -0,0 +1,96 @@
export const demoServices = {
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"],
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"],
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"],
fan: [
"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"],
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"],
homeassistant: [
"stop",
"check_config",
"reload_core_config",
"turn_on",
"turn_off",
"restart",
"toggle",
],
light: ["turn_off", "toggle", "turn_on"],
input_text: ["set_value"],
};

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,27 @@ 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);
let { brightness, hs_color, brightness_pct } = data;
// eslint-disable-next-line
brightness = (255 * brightness_pct) / 100;
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 +81,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,28 +93,46 @@ 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 ClimateEntity extends Entity {
async handleService(domain, service, data) {
if (domain !== this.domain) return;
if (service === "set_operation_mode") {
this.update(
data.operation_mode === "heat" ? "heat" : data.operation_mode,
Object.assign(this.attributes, {
operation_mode: data.operation_mode,
})
);
}
}
}
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");
}
}
const TYPES = {
climate: ClimateEntity,
light: LightEntity,
lock: LockEntity,
cover: CoverEntity,

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,49 +1,68 @@
import fireEvent from '../../../src/common/dom/fire_event.js';
import { fireEvent } from "../../../src/common/dom/fire_event";
import demoConfig from './demo_config.js';
import demoResources from './demo_resources.js';
import { demoConfig } from "./demo_config";
import { demoServices } from "./demo_services";
import demoResources from "./demo_resources";
const ensureArray = val => (Array.isArray(val) ? val : [val]);
const ensureArray = (val) => (Array.isArray(val) ? val : [val]);
export default (elements, { initialStates = {} } = {}) => {
elements = ensureArray(elements);
const wsCommands = {};
const restResponses = {};
let hass;
const entities = {};
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',
services: demoServices,
language: "en",
resources: demoResources,
states: initialStates,
themes: {},
connection: {
subscribeEvents: async (callback, event) => {
console.log("subscribeEvents", event);
return () => console.log("unsubscribeEvents", event);
},
},
// Mock properties
mockEntities: entities,
// 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 +73,15 @@ export default (elements, { initialStates = {} } = {}) => {
} else {
console.error(`Unknown command: ${msg.type}`);
}
console.log('sendWS', msg);
console.log("sendWS", msg);
},
async callApi(method, path, parameters) {
const callback = restResponses[path];
return callback
? callback(method, path, parameters)
: Promise.reject(`Mock for {path} is not implemented`);
},
// Mock functions
@@ -72,7 +99,13 @@ export default (elements, { initialStates = {} } = {}) => {
states[ent.entityId] = ent.toState();
});
this.updateStates(states);
}
},
mockWS(type, callback) {
wsCommands[type] = callback;
},
mockAPI(path, callback) {
restResponses[path] = callback;
},
});
return hass;

View File

@@ -0,0 +1,79 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
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,
};
}
public 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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
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
`
`,
},
];
@@ -57,8 +57,8 @@ class DemoConditional extends PolymerElement {
static get template() {
return html`
<demo-cards
id='demos'
hass='[[hass]]'
id="demos"
hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
@@ -68,17 +68,17 @@ class DemoConditional extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
value: CONFIGS,
},
hass: Object,
};
}
ready() {
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
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,24 +156,26 @@ 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
`
`,
},
];
class DemoEntities extends PolymerElement {
static get template() {
return html`
<demo-cards
id='demos'
hass='[[hass]]'
configs="[[_configs]]"
></demo-cards>
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
@@ -190,17 +183,16 @@ class DemoEntities extends PolymerElement {
return {
_configs: {
type: Object,
value: CONFIGS
value: CONFIGS,
},
hass: Object,
};
}
ready() {
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define('demo-hui-entities-card', DemoEntities);
customElements.define("demo-hui-entities-card", DemoEntities);

View File

@@ -0,0 +1,99 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
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,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-entity-button-card", DemoEntityButtonEntity);

View File

@@ -1,74 +0,0 @@
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',
config: `
- type: entity-filter
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
state_filter:
- "on"
- not_home
`
},
{
heading: 'With card config',
config: `
- type: entity-filter
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
state_filter:
- "on"
- not_home
card:
type: glance
show_state: false
`
},
{
heading: 'Showing single entity conditionally',
config: `
- type: entity-filter
entities:
- media_player.lounge_room
state_filter:
- 'playing'
card:
type: media-control
entity: media_player.lounge_room
`
}
];
class DemoFilter 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-entity-filter-card', DemoFilter);

View File

@@ -0,0 +1,115 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "work", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_anne_therese", "school", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Anne Therese",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Home Boy",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "ceiling_lights", "off", {
friendly_name: "Ceiling Lights",
}),
];
const CONFIGS = [
{
heading: "Controller",
config: `
- type: entities
entities:
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
`,
},
{
heading: "Basic",
config: `
- type: entity-filter
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
state_filter:
- "on"
- home
`,
},
{
heading: "With card config",
config: `
- type: entity-filter
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
state_filter:
- "on"
- not_home
card:
type: glance
show_state: false
`,
},
];
class DemoFilter extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-entity-filter-card", DemoFilter);

View File

@@ -0,0 +1,83 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
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,164 +0,0 @@
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: glance
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'With title',
config: `
- type: glance
title: This is glance
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'Custom column width',
config: `
- type: glance
column_width: calc(100% / 7)
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'No name',
config: `
- type: glance
show_name: false
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'No state',
config: `
- type: glance
show_state: false
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'No name and no state',
config: `
- type: glance
show_name: false
show_state: false
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'Custom name, custom icon',
config: `
- type: glance
entities:
- entity: device_tracker.demo_paulus
name: ¯\\_(ツ)_/¯
icon: mdi:home-assistant
- media_player.living_room
- sun.sun
- cover.kitchen_window
- entity: light.kitchen_lights
icon: mdi:alarm-light
- lock.kitchen_door
- light.ceiling_lights
`
},
{
heading: 'Custom tap action',
config: `
- type: glance
entities:
- entity: lock.kitchen_door
tap_action: toggle
- entity: light.ceiling_lights
tap_action: call-service
service: light.turn_on
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
`
},
{
heading: 'Selectively hidden name',
config: `
- type: glance
entities:
- device_tracker.demo_paulus
- entity: media_player.living_room
name:
- sun.sun
- entity: cover.kitchen_window
name:
- light.kitchen_lights
`
},
];
class DemoPicEntity 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-glance-card', DemoPicEntity);

View File

@@ -0,0 +1,244 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("media_player", "living_room", "playing", {
volume_level: 1,
is_volume_muted: false,
media_content_id: "eyU3bRy2x44",
media_content_type: "movie",
media_duration: 300,
media_position: 45.017773,
media_position_updated_at: "2018-07-19T10:44:45.919514+00:00",
media_title: "♥♥ The Best Fireplace Video (3 hours)",
app_name: "YouTube",
sound_mode: "Dummy Music",
sound_mode_list: ["Dummy Music", "Dummy Movie"],
shuffle: false,
friendly_name: "Living Room",
entity_picture:
"/api/media_player_proxy/media_player.living_room?token=e925f8db7f7bd1f317e4524dcb8333d60f6019219a3799a22604b5787f243567&cache=bc2ffb49c4f67034",
supported_features: 115597,
}),
getEntity("sun", "sun", "below_horizon", {
next_dawn: "2018-07-19T20:48:47+00:00",
next_dusk: "2018-07-20T11:46:06+00:00",
next_midnight: "2018-07-19T16:17:28+00:00",
next_noon: "2018-07-20T04:17:26+00:00",
next_rising: "2018-07-19T21:16:31+00:00",
next_setting: "2018-07-20T11:18:22+00:00",
elevation: 67.69,
azimuth: 338.55,
friendly_name: "Sun",
}),
getEntity("cover", "kitchen_window", "open", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "ceiling_lights", "off", {
friendly_name: "Ceiling Lights",
}),
getEntity("lock", "kitchen_door", "locked", {
friendly_name: "Kitchen Door",
}),
];
const CONFIGS = [
{
heading: "Basic example",
config: `
- type: glance
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "With title",
config: `
- type: glance
title: This is glance
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "Custom number of columns",
config: `
- type: glance
columns: 7
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "No name",
config: `
- type: glance
show_name: false
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "No state",
config: `
- type: glance
show_state: false
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "No name and no state",
config: `
- type: glance
show_name: false
show_state: false
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "Custom name, custom icon",
config: `
- type: glance
entities:
- entity: device_tracker.demo_paulus
name: ¯\\_(ツ)_/¯
icon: mdi:home-assistant
- media_player.living_room
- sun.sun
- cover.kitchen_window
- entity: light.kitchen_lights
icon: mdi:alarm-light
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "Custom tap action",
config: `
- type: glance
entities:
- entity: lock.kitchen_door
tap_action:
type: toggle
- entity: light.ceiling_lights
tap_action:
action: call-service
service: light.turn_on
service_data:
entity_id: light.ceiling_lights
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
`,
},
{
heading: "Selectively hidden name",
config: `
- type: glance
entities:
- device_tracker.demo_paulus
- entity: media_player.living_room
name:
- sun.sun
- 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
`,
},
];
class DemoPicEntity extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/demo-cards.js';
import "../components/demo-cards";
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

@@ -0,0 +1,48 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
brightness: 130,
}),
];
const CONFIGS = [
{
heading: "Basic example",
config: `
- type: light
entity: light.bed_light
`,
},
];
class DemoLightEntity extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-light-card", DemoLightEntity);

View File

@@ -1,149 +0,0 @@
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('device_tracker', 'demo_paulus', 'not_home', {
source_type: 'gps',
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: 'Paulus'
}),
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'
}),
getEntity('zone', 'home', 'zoning', {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: 'Home',
icon: 'mdi:home'
})
];
const CONFIGS = [
{
heading: 'Without title',
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- device_tracker.demo_home_boy
- zone.home
`
},
{
heading: 'With title',
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- zone.home
title: Where is Paulus?
`
},
{
heading: 'Height-Width 1:2',
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- zone.home
aspect_ratio: 50%
`
},
{
heading: 'Default Zoom',
config: `
- type: map
default_zoom: 12
entities:
- entity: device_tracker.demo_paulus
- zone.home
`
},
{
heading: 'Default Zoom too High',
config: `
- type: map
default_zoom: 20
entities:
- entity: device_tracker.demo_paulus
- zone.home
`
},
{
heading: 'Single Marker',
config: `
- type: map
entities:
- device_tracker.demo_paulus
`
},
{
heading: 'Single Marker Default Zoom',
config: `
- type: map
default_zoom: 8
entities:
- device_tracker.demo_paulus
`
},
{
heading: 'No Entities',
config: `
- type: map
entities:
- light.bed_light
`
},
{
heading: 'No Entities, Default Zoom',
config: `
- type: map
default_zoom: 8
entities:
- light.bed_light
`
},
];
class DemoMap 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-map-card', DemoMap);

View File

@@ -0,0 +1,192 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("device_tracker", "demo_paulus", "not_home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
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",
}),
getEntity("zone", "home", "zoning", {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: "Home",
icon: "mdi:home",
}),
getEntity("zone", "bushfire", "zoning", {
latitude: -33.8611,
longitude: 151.203,
radius: 35000,
friendly_name: "Bushfire Zone",
icon: "mdi:home",
}),
getEntity("geo_location", "nelsons_creek", "15", {
source: "bushfire_demo",
latitude: -34.07792,
longitude: 151.03219,
friendly_name: "Nelsons Creek",
}),
getEntity("geo_location", "forest_rd_nowra_hill", "8", {
source: "bushfire_demo",
latitude: -33.69452,
longitude: 151.19577,
friendly_name: "Forest Rd, Nowra Hill",
}),
getEntity("geo_location", "stoney_ridge_rd_kremnos", "20", {
source: "bushfire_demo",
latitude: -33.66584,
longitude: 150.97209,
friendly_name: "Stoney Ridge Rd, Kremnos",
}),
];
const CONFIGS = [
{
heading: "Without title",
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- device_tracker.demo_home_boy
- zone.home
`,
},
{
heading: "With title",
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- zone.home
title: Where is Paulus?
`,
},
{
heading: "Height-Width 1:2",
config: `
- type: map
entities:
- entity: device_tracker.demo_paulus
- zone.home
aspect_ratio: 50%
`,
},
{
heading: "Default Zoom",
config: `
- type: map
default_zoom: 12
entities:
- entity: device_tracker.demo_paulus
- zone.home
`,
},
{
heading: "Default Zoom too High",
config: `
- type: map
default_zoom: 20
entities:
- entity: device_tracker.demo_paulus
- zone.home
`,
},
{
heading: "Single Marker",
config: `
- type: map
entities:
- device_tracker.demo_paulus
`,
},
{
heading: "Single Marker Default Zoom",
config: `
- type: map
default_zoom: 8
entities:
- device_tracker.demo_paulus
`,
},
{
heading: "No Entities",
config: `
- type: map
entities:
- light.bed_light
`,
},
{
heading: "No Entities, Default Zoom",
config: `
- type: map
default_zoom: 8
entities:
- light.bed_light
`,
},
{
heading: "Geo Location Entities",
config: `
- type: map
geo_location_sources:
- bushfire_demo
`,
},
{
heading: "Geo Location Entities with Home Zone",
config: `
- type: map
geo_location_sources:
- bushfire_demo
entities:
- zone.bushfire
`,
},
];
class DemoMap 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,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/demo-cards.js';
import "../components/demo-cards";
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
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,
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows);

View File

@@ -1,73 +0,0 @@
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: 'Card with few elements',
config: `
- type: picture-elements
image: /images/floorplan.png
elements:
- type: service-button
title: Lights Off
style:
top: 97%
left: 90%
padding: 0px
service: light.turn_off
service_data:
entity_id: group.all_lights
- type: icon
icon: mdi:cctv
entity: camera.demo_camera
style:
top: 12%
left: 6%
transform: rotate(-60deg) scaleX(-1)
--iron-icon-height: 30px
--iron-icon-width: 30px
--iron-icon-stroke-color: black
--iron-icon-fill-color: rgba(50, 50, 50, .75)
- type: image
entity: light.bed_light
tap_action: toggle
image: /images/light_bulb_off.png
state_image:
'on': /images/light_bulb_on.png
state_filter:
'on': brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)
'off': brightness(80%) saturate(0.8)
style:
top: 35%
left: 65%
width: 7%
padding: 50px 50px 100px 50px
- type: state-icon
entity: binary_sensor.movement_backyard
style:
top: 8%
left: 35%
`
},
];
class DemoPicElements 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-picture-elements-card', DemoPicElements);

View File

@@ -0,0 +1,153 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("group", "all_lights", "on", {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "All Lights",
}),
getEntity("camera", "demo_camera", "idle", {
access_token:
"2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
friendly_name: "Demo camera",
entity_picture:
"/api/camera_proxy/camera.demo_camera?token=2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
}),
getEntity("binary_sensor", "movement_backyard", "on", {
friendly_name: "Movement Backyard",
device_class: "motion",
}),
];
const CONFIGS = [
{
heading: "Card with few elements",
config: `
- type: picture-elements
image: /images/floorplan.png
elements:
- type: service-button
title: Lights Off
style:
top: 97%
left: 90%
padding: 0px
service: light.turn_off
service_data:
entity_id: group.all_lights
- type: icon
icon: mdi:cctv
entity: camera.demo_camera
style:
top: 12%
left: 6%
transform: rotate(-60deg) scaleX(-1)
--iron-icon-height: 30px
--iron-icon-width: 30px
--iron-icon-stroke-color: black
--iron-icon-fill-color: rgba(50, 50, 50, .75)
- type: image
entity: light.bed_light
tap_action:
action: toggle
image: /images/light_bulb_off.png
state_image:
'on': /images/light_bulb_on.png
state_filter:
'on': brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)
'off': brightness(80%) saturate(0.8)
style:
top: 35%
left: 65%
width: 7%
padding: 50px 50px 100px 50px
- type: state-icon
entity: binary_sensor.movement_backyard
style:
top: 8%
left: 35%
`,
},
{
heading: "Card with header",
config: `
- type: picture-elements
image: /images/floorplan.png
title: My House
elements:
- type: service-button
title: Lights Off
style:
top: 97%
left: 90%
padding: 0px
service: light.turn_off
service_data:
entity_id: group.all_lights
- type: icon
icon: mdi:cctv
entity: camera.demo_camera
style:
top: 12%
left: 6%
transform: rotate(-60deg) scaleX(-1)
--iron-icon-height: 30px
--iron-icon-width: 30px
--iron-icon-stroke-color: black
--iron-icon-fill-color: rgba(50, 50, 50, .75)
- type: image
entity: light.bed_light
tap_action:
action: toggle
image: /images/light_bulb_off.png
state_image:
'on': /images/light_bulb_on.png
state_filter:
'on': brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)
'off': brightness(80%) saturate(0.8)
style:
top: 35%
left: 65%
width: 7%
padding: 50px 50px 100px 50px
- type: state-icon
entity: binary_sensor.movement_backyard
style:
top: 8%
left: 35%
`,
},
];
class DemoPicElements extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/demo-cards.js';
import "../components/demo-cards";
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/demo-cards.js';
import "../components/demo-cards";
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,24 @@ const CONFIGS = [
- entity: switch.decorative_lights
icon: mdi:power
- binary_sensor.basement_floor_wet
`
`,
},
{
heading: "Custom tap action",
config: `
- type: picture-glance
image: /images/living_room.png
title: Living room
entity: light.ceiling_lights
tap_action:
action: toggle
entities:
- entity: switch.decorative_lights
icon: mdi:power
tap_action:
action: toggle
- binary_sensor.basement_floor_wet
`,
},
];
@@ -96,10 +113,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

@@ -0,0 +1,52 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const CONFIGS = [
{
heading: "List example",
config: `
- type: shopping-list
`,
},
{
heading: "List with title example",
config: `
- type: shopping-list
title: Shopping List
`,
},
];
class DemoShoppingListEntity extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.mockAPI("shopping_list", () => [
{ name: "list", id: 1, complete: false },
{ name: "all", id: 2, complete: false },
{ name: "the", id: 3, complete: false },
{ name: "things", id: 4, complete: true },
]);
}
}
customElements.define("demo-hui-shopping-list-card", DemoShoppingListEntity);

View File

@@ -1,76 +0,0 @@
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: 'Vertical Stack',
config: `
- type: vertical-stack
cards:
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
- type: glance
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
`
},
{
heading: 'Horizontal Stack',
config: `
- type: horizontal-stack
cards:
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
- type: glance
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
`
},
{
heading: 'Combination of both',
config: `
- type: vertical-stack
cards:
- type: horizontal-stack
cards:
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
- type: glance
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- type: picture-entity
image: /images/bed.png
entity: light.bed_light
`
},
];
class DemoStack 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-stack-card', DemoStack);

View File

@@ -0,0 +1,117 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Lights",
}),
getEntity("device_tracker", "demo_paulus", "work", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_anne_therese", "school", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Anne Therese",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Home Boy",
}),
];
const CONFIGS = [
{
heading: "Vertical Stack",
config: `
- type: vertical-stack
cards:
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
- type: glance
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
`,
},
{
heading: "Horizontal Stack",
config: `
- type: horizontal-stack
cards:
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
- type: glance
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
`,
},
{
heading: "Combination of both",
config: `
- type: vertical-stack
cards:
- type: horizontal-stack
cards:
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
- type: glance
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- type: picture-entity
image: /images/bed.png
entity: light.bed_light
`,
},
];
class DemoStack extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-stack-card", DemoStack);

View File

@@ -0,0 +1,85 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-cards";
const ENTITIES = [
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"],
friendly_name: "Ecobee",
supported_features: 1014,
}),
getEntity("climate", "nest", "heat", {
current_temperature: 17,
min_temp: 15,
max_temp: 25,
temperature: 19,
fan_mode: "Auto Low",
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
operation_mode: "heat",
operation_list: ["heat", "cool", "auto", "off"],
hold_mode: "home",
swing_mode: "Auto",
swing_list: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Nest",
supported_features: 1014,
}),
];
const CONFIGS = [
{
heading: "Range example",
config: `
- type: thermostat
entity: climate.ecobee
- type: thermostat
entity: climate.nest
`,
},
{
heading: "Single temp example",
config: `
- type: thermostat
entity: climate.nest
`,
},
];
class DemoThermostatEntity extends PolymerElement {
static get template() {
return html`
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-thermostat-card", DemoThermostatEntity);

View File

@@ -1,60 +0,0 @@
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 getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-more-infos.js';
/* eslint-disable no-unused-vars */
const SUPPORT_BRIGHTNESS = 1;
const SUPPORT_COLOR_TEMP = 2;
const SUPPORT_EFFECT = 4;
const SUPPORT_FLASH = 8;
const SUPPORT_COLOR = 16;
const SUPPORT_TRANSITION = 32;
const SUPPORT_WHITE_VALUE = 128;
const ENTITIES = [
getEntity('light', 'bed_light', 'on', {
friendly_name: 'Basic 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`
<demo-more-infos
hass='[[hass]]'
entities='[[_entities]]'
></demo-more-infos>
`;
}
static get properties() {
return {
_entities: {
type: Array,
value: ENTITIES.map(ent => ent.entityId),
},
};
}
ready() {
super.ready();
const hass = provideHass(this);
hass.addEntities(ENTITIES);
}
}
customElements.define('demo-more-info-light', DemoMoreInfoLight);

View File

@@ -0,0 +1,50 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/dialogs/more-info/controls/more-info-content";
import "../../../src/components/ha-card";
import getEntity from "../data/entity";
import provideHass from "../data/provide_hass";
import "../components/demo-more-infos";
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
friendly_name: "Basic 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`
<demo-more-infos
hass="[[hass]]"
entities="[[_entities]]"
></demo-more-infos>
`;
}
static get properties() {
return {
_entities: {
type: Array,
value: ENTITIES.map((ent) => ent.entityId),
},
};
}
public ready() {
super.ready();
const hass = provideHass(this);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-more-info-light", DemoMoreInfoLight);

View File

@@ -0,0 +1,79 @@
import { html, LitElement } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-button/paper-button";
import "../../../src/components/ha-card";
import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive";
export class DemoUtilLongPress extends LitElement {
public render(): TemplateResult {
return html`
${this.renderStyle()}
${
[1, 2, 3].map(
() => html`
<ha-card>
<paper-button
@ha-click="${this._handleTap}"
@ha-hold="${this._handleHold}"
.longPress="${longPress()}"
>
(long) press me!
</paper-button>
<textarea></textarea>
<div>(try pressing and scrolling too!)</div>
</ha-card>
`
)
}
`;
}
private _handleTap(ev: Event) {
this._addValue(ev, "tap");
}
private _handleHold(ev: Event) {
this._addValue(ev, "hold");
}
private _addValue(ev: Event, value: string) {
const area = (ev.currentTarget as HTMLElement)
.nextElementSibling! as HTMLTextAreaElement;
const now = new Date().toTimeString().split(" ")[0];
area.value += `${now}: ${value}\n`;
area.scrollTop = area.scrollHeight;
}
private renderStyle() {
return html`
<style>
ha-card {
width: 200px;
margin: calc(42vh - 140px) auto;
padding: 8px;
text-align: center;
}
ha-card:first-of-type {
margin-top: 16px;
}
ha-card:last-of-type {
margin-bottom: 16px;
}
paper-button {
font-weight: bold;
color: var(--primary-color);
}
textarea {
height: 50px;
}
</style>
`;
}
}
customElements.define("demo-util-long-press", DemoUtilLongPress);

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";
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
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";
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "../../src/components/ha-iconset-svg";
import './ha-gallery.js';
import "./ha-gallery";
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";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../../src/managers/notification-manager.js';
import "../../src/managers/notification-manager";
const DEMOS = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/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() {
@@ -118,6 +118,22 @@ class HaGallery extends PolymerElement {
</a>
</template>
</paper-card>
<paper-card heading="Util demos">
<div class='card-content intro'>
<p>
Test pages for our utility functions.
</p>
</div>
<template is='dom-repeat' items='[[_utilDemos]]'>
<a href='#[[item]]'>
<paper-item>
<paper-item-body>{{ item }}</paper-item-body>
<iron-icon icon="hass:chevron-right"></iron-icon>
</paper-item>
</a>
</template>
</paper-card>
</div>
</template>
</div>
@@ -131,19 +147,23 @@ 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)",
},
_utilDemos: {
type: Array,
computed: "_computeUtil(_demos)",
},
};
}
@@ -151,18 +171,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.showDialog({ message: 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.showDialog({
message: `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) {
@@ -175,27 +198,31 @@ class HaGallery extends PolymerElement {
while (root.lastChild) root.removeChild(root.lastChild);
if (demo) {
DEMOS(`./${demo}.js`);
DEMOS(`./${demo}.ts`);
const el = document.createElement(demo);
root.appendChild(el);
}
}
_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"));
}
_computeUtil(demos) {
return demos.filter((demo) => demo.includes("util"));
}
}
customElements.define('ha-gallery', HaGallery);
customElements.define("ha-gallery", HaGallery);

View File

@@ -1,76 +1,75 @@
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: [
babelLoaderConfig({ latestBuild: true }),
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
plugins: [
// Only support the syntax, Webpack will handle it.
'syntax-dynamic-import',
[
'transform-react-jsx',
{
pragma: 'h'
}
],
],
},
},
test: /\.css$/,
use: "raw-loader",
},
{
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,32 +82,34 @@ 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,
generateIconset,
genMDIIcons,
};

View File

@@ -1,40 +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',
'profile',
'shopping-list',
'page-authorize',
'page-onboarding',
"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];
}
@@ -43,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];
@@ -70,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;
});
@@ -97,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
@@ -119,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

@@ -11,4 +11,4 @@ OUTPUT_DIR=build
rm -rf $OUTPUT_DIR
node script/gen-icons.js
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
NODE_ENV=production CI=false ../node_modules/.bin/webpack -p --config webpack.config.js

View File

@@ -4,6 +4,8 @@
# Stop on errors
set -e
cd "$(dirname "$0")/.."
OUTPUT_DIR=build
rm -rf $OUTPUT_DIR

View File

@@ -1,15 +1,17 @@
#!/usr/bin/env node
const fs = require('fs');
const fs = require("fs");
const {
findIcons,
generateIconset,
} = require('../../gulp/tasks/gen-icons.js');
genMDIIcons,
} = 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));
}
genMDIIcons();
genHassioIcons();

View File

@@ -1,42 +1,60 @@
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";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
import "../components/hassio-card-content";
import "../resources/hassio-style";
import NavigateMixin from "../../../src/mixins/navigate-mixin";
class HassioAddonRepository extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style hassio-style">
paper-card {
cursor: pointer;
}
a.repo {
display: block;
color: var(--primary-text-color);
}
</style>
<template is="dom-if" if="[[addons.length]]">
<div class="card-group">
<div class="title">
[[repo.name]]
<div class="description">
Maintained by [[repo.maintainer]]
<a class="repo" href="[[repo.url]]" target="_blank">[[repo.url]]</a>
</div>
</div>
<template is="dom-repeat" items="[[addons]]" as="addon" sort="sortAddons">
<paper-card on-click="addonTapped">
<div class="card-content">
<hassio-card-content hass="[[hass]]" title="[[addon.name]]" description="[[addon.description]]" icon="[[computeIcon(addon)]]" icon-title="[[computeIconTitle(addon)]]" icon-class="[[computeIconClass(addon)]]"></hassio-card-content>
<style include="iron-flex ha-style hassio-style">
paper-card {
cursor: pointer;
}
.not_available {
opacity: 0.6;
}
a.repo {
display: block;
color: var(--primary-text-color);
}
</style>
<template is="dom-if" if="[[addons.length]]">
<div class="card-group">
<div class="title">
[[repo.name]]
<div class="description">
Maintained by [[repo.maintainer]]
<a class="repo" href="[[repo.url]]" target="_blank"
>[[repo.url]]</a
>
</div>
</paper-card>
</template>
</div>
</template>
`;
</div>
<template
is="dom-repeat"
items="[[addons]]"
as="addon"
sort="sortAddons"
>
<paper-card class$="[[computeClass(addon)]]" on-click="addonTapped">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[addon.name]]"
description="[[addon.description]]"
available="[[addon.available]]"
icon="[[computeIcon(addon)]]"
icon-title="[[computeIconTitle(addon)]]"
icon-class="[[computeIconClass(addon)]]"
></hassio-card-content>
</div>
</paper-card>
</template>
</div>
</template>
`;
}
static get properties() {
@@ -48,21 +66,33 @@ 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 addon.available
? "Add-on is not installed"
: "Add-on is not available on your system";
}
computeIconClass(addon) {
if (addon.installed) return addon.installed !== addon.version ? 'update' : 'installed';
return '';
if (addon.installed)
return addon.installed !== addon.version ? "update" : "installed";
return !addon.available ? "not_available" : "";
}
computeClass(addon) {
return !addon.available ? "not_available" : "";
}
addonTapped(ev) {
@@ -70,4 +100,4 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
}
}
customElements.define('hassio-addon-repository', HassioAddonRepository);
customElements.define("hassio-addon-repository", HassioAddonRepository);

View File

@@ -1,23 +1,30 @@
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import './hassio-addon-repository.js';
import './hassio-repositories-editor.js';
import "./hassio-addon-repository";
import "./hassio-repositories-editor";
class HassioAddonStore extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
hassio-addon-repository {
margin-top: 24px;
}
</style>
<hassio-repositories-editor hass="[[hass]]" repos="[[repos]]"></hassio-repositories-editor>
<style include="iron-flex ha-style">
hassio-addon-repository {
margin-top: 24px;
}
</style>
<hassio-repositories-editor
hass="[[hass]]"
repos="[[repos]]"
></hassio-repositories-editor>
<template is="dom-repeat" items="[[repos]]" as="repo" sort="sortRepos">
<hassio-addon-repository hass="[[hass]]" repo="[[repo]]" addons="[[computeAddons(repo.slug)]]"></hassio-addon-repository>
</template>
`;
<template is="dom-repeat" items="[[repos]]" as="repo" sort="sortRepos">
<hassio-addon-repository
hass="[[hass]]"
repo="[[repo]]"
addons="[[computeAddons(repo.slug)]]"
></hassio-addon-repository>
</template>
`;
}
static get properties() {
@@ -30,7 +37,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 +48,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,58 +1,83 @@
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";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import "../components/hassio-card-content";
import "../resources/hassio-style";
class HassioRepositoriesEditor extends PolymerElement {
static get template() {
return html`
<style include="ha-style hassio-style">
.add {
padding: 12px 16px;
}
iron-icon {
color: var(--secondary-text-color);
margin-right: 16px;
display: inline-block;
}
paper-input {
width: calc(100% - 49px);
display: inline-block;
}
</style>
<div class="card-group">
<div class="title">
Repositories
<div class="description">
Configure which add-on repositories to fetch data from:
<style include="ha-style hassio-style">
.add {
padding: 12px 16px;
}
iron-icon {
color: var(--secondary-text-color);
margin-right: 16px;
display: inline-block;
}
paper-input {
width: calc(100% - 49px);
display: inline-block;
}
</style>
<div class="card-group">
<div class="title">
Repositories
<div class="description">
Configure which add-on repositories to fetch data from:
</div>
</div>
</div>
<template id="list" is="dom-repeat" items="[[repoList]]" as="repo" sort="sortRepos">
<template
id="list"
is="dom-repeat"
items="[[repoList]]"
as="repo"
sort="sortRepos"
>
<paper-card>
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[repo.name]]"
description="[[repo.url]]"
icon="hassio:github-circle"
></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/supervisor/options"
data="[[computeRemoveRepoData(repoList, repo.url)]]"
class="warning"
>Remove</ha-call-api-button
>
</div>
</paper-card>
</template>
<paper-card>
<div class="card-content">
<hassio-card-content hass="[[hass]]" title="[[repo.name]]" description="[[repo.url]]" icon="hassio:github-circle"></hassio-card-content>
<div class="card-content add">
<iron-icon icon="hassio:github-circle"></iron-icon>
<paper-input
label="Add new repository by URL"
value="{{repoUrl}}"
></paper-input>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[computeRemoveRepoData(repoList, repo.url)]]" class="warning">Remove</ha-call-api-button>
<ha-call-api-button
hass="[[hass]]"
path="hassio/supervisor/options"
data="[[computeAddRepoData(repoList, repoUrl)]]"
>Add</ha-call-api-button
>
</div>
</paper-card>
</template>
<paper-card>
<div class="card-content add">
<iron-icon icon="hassio:github-circle"></iron-icon>
<paper-input label="Add new repository by URL" value="{{repoUrl}}"></paper-input>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[computeAddRepoData(repoList, repoUrl)]]">Add</ha-call-api-button>
</div>
</paper-card>
</div>
`;
</div>
`;
}
static get properties() {
@@ -60,7 +85,7 @@ class HassioRepositoriesEditor extends PolymerElement {
hass: Object,
repos: {
type: Array,
observer: 'reposChanged',
observer: "reposChanged",
},
repoList: Array,
repoUrl: String,
@@ -68,8 +93,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 +104,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,62 +1,74 @@
import 'web-animations-js/web-animations-next-lite.min.js';
import "web-animations-js/web-animations-next-lite.min";
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";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../../../src/resources/ha-style.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../../../src/resources/ha-style";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioAddonAudio extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
:host,
paper-card,
paper-dropdown-menu {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
paper-item {
width: 450px;
}
.card-actions {
text-align: right;
}
</style>
<paper-card heading="Audio">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<style include="ha-style">
:host,
paper-card,
paper-dropdown-menu {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
paper-item {
width: 450px;
}
.card-actions {
text-align: right;
}
</style>
<paper-card heading="Audio">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<paper-dropdown-menu label="Input">
<paper-listbox slot="dropdown-content" attr-for-selected="device" selected="{{selectedInput}}">
<template is="dom-repeat" items="[[inputDevices]]">
<paper-item device\$="[[item.device]]">[[item.name]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
<paper-dropdown-menu label="Output">
<paper-listbox slot="dropdown-content" attr-for-selected="device" selected="{{selectedOutput}}">
<template is="dom-repeat" items="[[outputDevices]]">
<paper-item device\$="[[item.device]]">[[item.name]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="card-actions">
<paper-button on-click="_saveSettings">Save</paper-button>
</div>
</paper-card>
`;
<paper-dropdown-menu label="Input">
<paper-listbox
slot="dropdown-content"
attr-for-selected="device"
selected="{{selectedInput}}"
>
<template is="dom-repeat" items="[[inputDevices]]">
<paper-item device\$="[[item.device]]"
>[[item.name]]</paper-item
>
</template>
</paper-listbox>
</paper-dropdown-menu>
<paper-dropdown-menu label="Output">
<paper-listbox
slot="dropdown-content"
attr-for-selected="device"
selected="{{selectedOutput}}"
>
<template is="dom-repeat" items="[[outputDevices]]">
<paper-item device\$="[[item.device]]"
>[[item.name]]</paper-item
>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="card-actions">
<paper-button on-click="_saveSettings">Save</paper-button>
</div>
</paper-card>
`;
}
static get properties() {
@@ -64,7 +76,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
hass: Object,
addon: {
type: Object,
observer: 'addonChanged'
observer: "addonChanged",
},
inputDevices: Array,
outputDevices: Array,
@@ -76,40 +88,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,50 +1,61 @@
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";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../../../src/components/buttons/ha-call-api-button.js';
import "../../../src/components/buttons/ha-call-api-button";
class HassioAddonConfig extends PolymerElement {
static get template() {
return html`
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
}
.card-actions {
@apply --layout;
@apply --layout-justified;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: monospace;
}
.syntaxerror {
color: var(--google-red-500);
}
</style>
<paper-card heading="Config">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<iron-autogrow-textarea id="config" value="{{config}}"></iron-autogrow-textarea>
</div>
<div class="card-actions">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/options" data="[[resetData]]">Reset to defaults</ha-call-api-button>
<paper-button on-click="saveTapped" disabled="[[!configParsed]]">Save</paper-button>
</div>
</paper-card>
`;
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
}
.card-actions {
@apply --layout;
@apply --layout-justified;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: monospace;
}
.syntaxerror {
color: var(--google-red-500);
}
</style>
<paper-card heading="Config">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<iron-autogrow-textarea
id="config"
value="{{config}}"
></iron-autogrow-textarea>
</div>
<div class="card-actions">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/options"
data="[[resetData]]"
>Reset to defaults</ha-call-api-button
>
<paper-button on-click="saveTapped" disabled="[[!configParsed]]"
>Save</paper-button
>
</div>
</paper-card>
`;
}
static get properties() {
@@ -52,12 +63,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 +82,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 +98,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,153 +1,408 @@
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";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-toggle-button/paper-toggle-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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/ha-label-badge";
import "../../../src/components/ha-markdown";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/resources/ha-style";
import EventsMixin from "../../../src/mixins/events-mixin";
import '../components/hassio-card-content.js';
import "../components/hassio-card-content";
const PERMIS_DESC = {
rating: {
title: "Addon Security Rating",
description:
"Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an addon requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
},
host_network: {
title: "Host Network",
description:
"Add-ons usually run in their own isolated network layer, which prevents them from accessing the network of the host operating system. In some cases, this network isolation can limit add-ons in providing their services and therefore, the isolation can be lifted by the add-on author, giving the addon full access to the network capabilities of the host machine. This gives the addon more networking capabilities but lowers the security, hence, the security rating of the add-on will be lowered when this option is used by the addon.",
},
homeassistant_api: {
title: "Home Assistant API Access",
description:
"This add-on is allowed to access your running Home Assistant instance directly via the Home Assistant API. This mode handles authentication for the addon as well, which enables an addon to interact with Home Assistant without the need for additional authentication tokens.",
},
full_access: {
title: "Full Hardware Access",
description:
"This addon is given full access to the hardware of your system, by request of the addon author. Access is comparable to the privileged mode in Docker. Since this opens up possible security risks, this feature impacts the addon security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the addon manually. Only disable the protection mode if you know, need AND trust the source of this addon.",
},
hassio_api: {
title: "Hass.io API Access",
description:
"The addon was given access to the Hass.io API, by request of the addon author. By default, the addon can access general version information of your system. When the addon requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Hass.io system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
},
docker_api: {
title: "Full Docker Access",
description:
"The addon author has requested the addon to have management access to the Docker instance running on your system. This mode gives the addon full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the addon security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the addon manually. Only disable the protection mode if you know, need AND trust the source of this addon.",
},
host_pid: {
title: "Host Processes Namespace",
description:
"Usually, the processes the addon runs, are isolated from all other system processes. The addon author has requested the addon to have access to the system processes running on the host system instance, and allow the addon to spawn processes on the host system as well. This mode gives the addon full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the addon security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the addon manually. Only disable the protection mode if you know, need AND trust the source of this addon.",
},
apparmor: {
title: "AppArmor",
description:
"AppArmor ('Application Armor') is a Linux kernel security module that restricts addons capabilities like network access, raw socket access, and permission to read, write, or execute specific files.\n\nAddon authors can provide their security profiles, optimized for the addon, or request it to be disabled. If AppArmor is disabled, it will raise security risks and therefore, has a negative impact on the security score of the addon.",
},
auth_api: {
title: "Home Assistant Authentication",
description:
"An addon can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
},
};
class HassioAddonInfo extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
margin-bottom: 16px;
}
.addon-header {
@apply --paper-font-headline;
}
.light-color {
color: var(--secondary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.description {
margin-bottom: 16px;
}
.logo img {
max-height: 60px;
margin: 16px 0;
display: block;
}
.state div{
width: 150px;
display: inline-block;
}
paper-toggle-button {
display: inline;
}
iron-icon.running {
color: var(--paper-green-400);
}
iron-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
ha-markdown img {
max-width: 100%;
}
</style>
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
<paper-card heading="Update available! 🎉">
<div class="card-content">
<hassio-card-content hass="[[hass]]" title="[[addon.name]] [[addon.last_version]] is available" description="You are currently running version [[addon.version]]" icon="hassio:arrow-up-bold-circle" icon-class="update"></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/update">Update</ha-call-api-button>
<template is="dom-if" if="[[addon.changelog]]">
<paper-button on-click="openChangelog">Changelog</paper-button>
</template>
</div>
</paper-card>
</template>
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
margin-bottom: 16px;
}
paper-card.warning {
background-color: var(--google-red-500);
color: white;
--paper-card-header-color: white;
}
paper-card.warning paper-button {
color: white !important;
}
.warning {
color: var(--google-red-500);
}
.addon-header {
@apply --paper-font-headline;
}
.light-color {
color: var(--secondary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.description {
margin-bottom: 16px;
}
.logo img {
max-height: 60px;
margin: 16px 0;
display: block;
}
.state div {
width: 150px;
display: inline-block;
}
paper-toggle-button {
display: inline;
}
iron-icon.running {
color: var(--paper-green-400);
}
iron-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
ha-markdown img {
max-width: 100%;
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--iron-icon-height: 45px;
}
</style>
<paper-card>
<div class="card-content">
<div class="addon-header">[[addon.name]]
<div class="addon-version light-color">
<template is="dom-if" if="[[addon.version]]">
[[addon.version]]
<template is="dom-if" if="[[isRunning]]">
<iron-icon title="Add-on is running" class="running" icon="hassio:circle"></iron-icon>
</template>
<template is="dom-if" if="[[!isRunning]]">
<iron-icon title="Add-on is stopped" class="stopped" icon="hassio:circle"></iron-icon>
</template>
</template>
<template is="dom-if" if="[[!addon.version]]">
[[addon.last_version]]
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
<paper-card heading="Update available! 🎉">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[addon.name]] [[addon.last_version]] is available"
description="You are currently running version [[addon.version]]"
icon="hassio:arrow-up-bold-circle"
icon-class="update"
></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/update"
>Update</ha-call-api-button
>
<template is="dom-if" if="[[addon.changelog]]">
<paper-button on-click="openChangelog">Changelog</paper-button>
</template>
</div>
</div>
<div class="description light-color">
[[addon.description]].<br>
Visit <a href="[[addon.url]]" target="_blank">[[addon.name]] page</a> for details.
</div>
<template is="dom-if" if="[[addon.logo]]">
<a href="[[addon.url]]" target="_blank" class="logo">
<img src="/api/hassio/addons/[[addonSlug]]/logo">
</a>
</template>
<template is="dom-if" if="[[addon.version]]">
<div class="state">
<div>Start on boot</div>
<paper-toggle-button on-change="startOnBootToggled" checked="[[computeStartOnBoot(addon.boot)]]"></paper-toggle-button>
</div>
<div class="state">
<div>Auto update</div>
<paper-toggle-button on-change="autoUpdateToggled" checked="[[addon.auto_update]]"></paper-toggle-button>
</div>
</template>
</div>
<div class="card-actions">
<template is="dom-if" if="[[addon.version]]">
<paper-button class="warning" on-click="_unistallClicked">Uninstall</paper-button>
<template is="dom-if" if="[[addon.build]]">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/rebuild">Rebuild</ha-call-api-button>
</template>
<template is="dom-if" if="[[isRunning]]">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/restart">Restart</ha-call-api-button>
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/stop">Stop</ha-call-api-button>
</template>
<template is="dom-if" if="[[!isRunning]]">
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/start">Start</ha-call-api-button>
</template>
<template is="dom-if" if="[[computeShowWebUI(addon.webui, isRunning)]]">
<a href="[[pathWebui(addon.webui)]]" tabindex="-1" target="_blank" class="right"><paper-button>Open web UI</paper-button></a>
</template>
</template>
<template is="dom-if" if="[[!addon.version]]">
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/install">Install</ha-call-api-button>
</template>
</div>
</paper-card>
<template is="dom-if" if="[[addon.long_description]]">
</paper-card>
</template>
<paper-card>
<div class="card-content">
<ha-markdown content="[[addon.long_description]]"></ha-markdown>
<div class="addon-header">
[[addon.name]]
<div class="addon-version light-color">
<template is="dom-if" if="[[addon.version]]">
[[addon.version]]
<template is="dom-if" if="[[isRunning]]">
<iron-icon
title="Add-on is running"
class="running"
icon="hassio:circle"
></iron-icon>
</template>
<template is="dom-if" if="[[!isRunning]]">
<iron-icon
title="Add-on is stopped"
class="stopped"
icon="hassio:circle"
></iron-icon>
</template>
</template>
<template is="dom-if" if="[[!addon.version]]">
[[addon.last_version]]
</template>
</div>
</div>
<div class="description light-color">
[[addon.description]].<br />
Visit
<a href="[[addon.url]]" target="_blank">[[addon.name]] page</a> for
details.
</div>
<template is="dom-if" if="[[addon.logo]]">
<a href="[[addon.url]]" target="_blank" class="logo">
<img src="/api/hassio/addons/[[addonSlug]]/logo" />
</a>
</template>
<template is="dom-if" if="[[!addon.protected]]">
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
<div class="card-content">
Protection mode on this addon is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this addon.
</div>
<div class="card-actions">
<paper-button on-click="protectionToggled">Enable Protection mode</paper-button>
</div>
</div>
</paper-card>
</template>
<div class="security">
<h3>Addon Security Rating</h3>
<div class="description light-color">
Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an addon requires on your system, the lower the score, thus raising the possible security risks.
</div>
<ha-label-badge
class$="[[computeSecurityClassName(addon.rating)]]"
on-click="showMoreInfo"
id="rating"
value="[[addon.rating]]"
label="rating"
description=""
></ha-label-badge>
<template is="dom-if" if="[[addon.host_network]]">
<ha-label-badge
on-click="showMoreInfo"
id="host_network"
icon="hassio:network"
label="host"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.full_access]]">
<ha-label-badge
on-click="showMoreInfo"
id="full_access"
icon="hassio:chip"
label="hardware"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.homeassistant_api]]">
<ha-label-badge
on-click="showMoreInfo"
id="homeassistant_api"
icon="hassio:home-assistant"
label="hass"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[computeHassioApi(addon)]]">
<ha-label-badge
on-click="showMoreInfo"
id="hassio_api"
icon="hassio:home-assistant"
label="hassio"
description="[[addon.hassio_role]]"
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.docker_api]]">
<ha-label-badge
on-click="showMoreInfo"
id="docker_api"
icon="hassio:docker"
label="docker"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.host_pid]]">
<ha-label-badge
on-click="showMoreInfo"
id="host_pid"
icon="hassio:pound"
label="host pid"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.apparmor]]">
<ha-label-badge
on-click="showMoreInfo"
class$="[[computeApparmorClassName(addon.apparmor)]]"
id="apparmor"
icon="hassio:shield"
label="apparmor"
description="[[addon.apparmor]]"
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.auth_api]]">
<ha-label-badge
on-click="showMoreInfo"
id="auth_api"
icon="hassio:key"
label="auth"
description=""
></ha-label-badge>
</template>
</div>
<template is="dom-if" if="[[addon.version]]">
<div class="state">
<div>Start on boot</div>
<paper-toggle-button
on-change="startOnBootToggled"
checked="[[computeStartOnBoot(addon.boot)]]"
></paper-toggle-button>
</div>
<div class="state">
<div>Auto update</div>
<paper-toggle-button
on-change="autoUpdateToggled"
checked="[[addon.auto_update]]"
></paper-toggle-button>
</div>
<div class="state">
<div>Protection mode</div>
<paper-toggle-button
on-change="protectionToggled"
checked="[[addon.protected]]"
></paper-toggle-button>
</div>
</template>
</div>
<div class="card-actions">
<template is="dom-if" if="[[addon.version]]">
<paper-button class="warning" on-click="_unistallClicked"
>Uninstall</paper-button
>
<template is="dom-if" if="[[addon.build]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/rebuild"
>Rebuild</ha-call-api-button
>
</template>
<template is="dom-if" if="[[isRunning]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/restart"
>Restart</ha-call-api-button
>
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/stop"
>Stop</ha-call-api-button
>
</template>
<template is="dom-if" if="[[!isRunning]]">
<ha-call-api-button
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/start"
>Start</ha-call-api-button
>
</template>
<template
is="dom-if"
if="[[computeShowWebUI(addon.webui, isRunning)]]"
>
<a
href="[[pathWebui(addon.webui)]]"
tabindex="-1"
target="_blank"
class="right"
><paper-button>Open web UI</paper-button></a
>
</template>
</template>
<template is="dom-if" if="[[!addon.version]]">
<template is="dom-if" if="[[!addon.available]]">
<p class="warning">This addon is not available on your system.</p>
</template>
<ha-call-api-button
disabled="[[!addon.available]]"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/install"
>Install</ha-call-api-button
>
</template>
</div>
</paper-card>
</template>
`;
<template is="dom-if" if="[[addon.long_description]]">
<paper-card>
<div class="card-content">
<ha-markdown content="[[addon.long_description]]"></ha-markdown>
</div>
</paper-card>
</template>
`;
}
static get properties() {
@@ -155,23 +410,42 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
hass: Object,
addon: Object,
addonSlug: String,
isRunning: {
type: Boolean,
computed: 'computeIsRunning(addon)',
},
isRunning: { type: Boolean, 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
);
}
computeHassioApi(addon) {
return (
addon.hassio_api &&
(addon.hassio_role === "manager" || addon.hassio_role === "admin")
);
}
computeApparmorClassName(apparmor) {
if (apparmor === "profile") {
return "green";
}
if (apparmor === "disable") {
return "red";
}
return "";
}
pathWebui(webui) {
return webui && webui.replace('[HOST]', document.location.hostname);
return webui && webui.replace("[HOST]", document.location.hostname);
}
computeShowWebUI(webui, isRunning) {
@@ -179,49 +453,78 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
}
computeStartOnBoot(state) {
return state === 'auto';
return state === "auto";
}
computeSecurityClassName(rating) {
if (rating > 4) {
return "green";
}
if (rating > 2) {
return "yellow";
}
return "red";
}
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);
}
protectionToggled() {
const data = { protected: !this.addon.protected };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/security`, data);
this.set("addon.protected", !this.addon.protected);
}
showMoreInfo(e) {
const id = e.target.getAttribute("id");
this.fire("hassio-markdown-dialog", {
title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description,
});
}
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,31 +1,33 @@
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";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
import '../../../src/resources/ha-style.js';
import "../../../src/resources/ha-style";
class HassioAddonLogs extends PolymerElement {
static get template() {
return html`
<style include="ha-style">
:host,
paper-card {
display: block;
}
pre {
overflow-x: auto;
}
</style>
<paper-card heading="Log">
<div class="card-content">
<pre>[[log]]</pre>
</div>
<div class="card-actions">
<paper-button on-click="refresh">Refresh</paper-button>
</div>
</paper-card>
`;
<style include="ha-style">
:host,
paper-card {
display: block;
}
pre {
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
}
</style>
${ANSI_HTML_STYLE}
<paper-card heading="Log">
<div class="card-content" id="content"></div>
<div class="card-actions">
<paper-button on-click="refresh">Refresh</paper-button>
</div>
</paper-card>
`;
}
static get properties() {
@@ -33,15 +35,16 @@ class HassioAddonLogs extends PolymerElement {
hass: Object,
addonSlug: {
type: String,
observer: 'addonSlugChanged',
observer: "addonSlugChanged",
},
log: String,
};
}
addonSlugChanged(slug) {
if (!this.hass) {
setTimeout(() => { this.addonChanged(slug); }, 0);
setTimeout(() => {
this.addonChanged(slug);
}, 0);
return;
}
@@ -49,11 +52,15 @@ class HassioAddonLogs extends PolymerElement {
}
refresh() {
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/logs`)
.then((info) => {
this.log = info;
this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/logs`)
.then((text) => {
while (this.$.content.lastChild) {
this.$.content.removeChild(this.$.content.lastChild);
}
this.$.content.appendChild(parseTextToColoredPre(text));
});
}
}
customElements.define('hassio-addon-logs', HassioAddonLogs);
customElements.define("hassio-addon-logs", HassioAddonLogs);

View File

@@ -1,60 +1,69 @@
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";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import "../../../src/resources/ha-style";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioAddonNetwork extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.card-actions {
@apply --layout;
@apply --layout-justified;
}
</style>
<paper-card heading="Network">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<table>
<tbody><tr>
<th>Container</th>
<th>Host</th>
</tr>
<template is="dom-repeat" items="[[config]]">
<tr>
<td>
[[item.container]]
</td>
<td>
<paper-input value="{{item.host}}" no-label-float=""></paper-input>
</td>
</tr>
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.card-actions {
@apply --layout;
@apply --layout-justified;
}
</style>
<paper-card heading="Network">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
</tbody></table>
</div>
<div class="card-actions">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/options" data="[[resetData]]">Reset to defaults</ha-call-api-button>
<paper-button on-click="saveTapped">Save</paper-button>
</div>
</paper-card>
`;
<table>
<tbody>
<tr>
<th>Container</th>
<th>Host</th>
</tr>
<template is="dom-repeat" items="[[config]]">
<tr>
<td>[[item.container]]</td>
<td>
<paper-input
value="{{item.host}}"
no-label-float=""
></paper-input>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="card-actions">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/options"
data="[[resetData]]"
>Reset to defaults</ha-call-api-button
>
<paper-button on-click="saveTapped">Save</paper-button>
</div>
</paper-card>
`;
}
static get properties() {
@@ -64,7 +73,7 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
config: Object,
addon: {
type: Object,
observer: 'addonChanged',
observer: "addonChanged",
},
error: String,
resetData: {
@@ -80,29 +89,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,86 +1,119 @@
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";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/app-route/app-route";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import "../../../src/resources/ha-style";
import "../hassio-markdown-dialog";
import "./hassio-addon-audio";
import "./hassio-addon-config";
import "./hassio-addon-info";
import "./hassio-addon-logs";
import "./hassio-addon-network";
class HassioAddonView extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
.content {
padding: 24px 0 32px;
display: flex;
flex-direction: column;
align-items: center;
}
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config {
margin-bottom: 24px;
width: 600px;
}
hassio-addon-logs {
max-width: calc(100% - 8px);
min-width: 600px;
}
@media only screen and (max-width: 600px) {
<style include="iron-flex ha-style">
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
.content {
padding: 24px 0 32px;
display: flex;
flex-direction: column;
align-items: center;
}
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config,
hassio-addon-logs {
max-width: 100%;
min-width: 100%;
hassio-addon-config {
margin-bottom: 24px;
width: 600px;
}
}
</style>
<app-route route="[[route]]" pattern="/addon/:slug" data="{{routeData}}" active="{{routeMatches}}"></app-route>
<app-header-layout has-scrolling-region="">
<app-header fixed="" slot="header">
<app-toolbar>
<ha-menu-button hassio narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
<paper-icon-button icon="hassio:arrow-left" on-click="backTapped"></paper-icon-button>
<div main-title="">Hass.io: add-on details</div>
</app-toolbar>
</app-header>
<div class="content">
<hassio-addon-info hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-info>
hassio-addon-logs {
max-width: calc(100% - 8px);
min-width: 600px;
}
@media only screen and (max-width: 600px) {
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config,
hassio-addon-logs {
max-width: 100%;
min-width: 100%;
}
}
</style>
<app-route
route="[[route]]"
pattern="/addon/:slug"
data="{{routeData}}"
active="{{routeMatches}}"
></app-route>
<app-header-layout has-scrolling-region="">
<app-header fixed="" slot="header">
<app-toolbar>
<ha-menu-button
hassio
narrow="[[narrow]]"
show-menu="[[showMenu]]"
></ha-menu-button>
<paper-icon-button
icon="hassio:arrow-left"
on-click="backTapped"
></paper-icon-button>
<div main-title="">Hass.io: add-on details</div>
</app-toolbar>
</app-header>
<div class="content">
<hassio-addon-info
hass="[[hass]]"
addon="[[addon]]"
addon-slug="[[routeData.slug]]"
></hassio-addon-info>
<template is="dom-if" if="[[addon.version]]">
<hassio-addon-config hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-config>
<template is="dom-if" if="[[addon.version]]">
<hassio-addon-config
hass="[[hass]]"
addon="[[addon]]"
addon-slug="[[routeData.slug]]"
></hassio-addon-config>
<template is="dom-if" if="[[addon.audio]]">
<hassio-addon-audio hass="[[hass]]" addon="[[addon]]"></hassio-addon-audio>
<template is="dom-if" if="[[addon.audio]]">
<hassio-addon-audio
hass="[[hass]]"
addon="[[addon]]"
></hassio-addon-audio>
</template>
<template is="dom-if" if="[[addon.network]]">
<hassio-addon-network
hass="[[hass]]"
addon="[[addon]]"
addon-slug="[[routeData.slug]]"
></hassio-addon-network>
</template>
<hassio-addon-logs
hass="[[hass]]"
addon-slug="[[routeData.slug]]"
></hassio-addon-logs>
</template>
</div>
</app-header-layout>
<template is="dom-if" if="[[addon.network]]">
<hassio-addon-network hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-network>
</template>
<hassio-addon-logs hass="[[hass]]" addon-slug="[[routeData.slug]]"></hassio-addon-logs>
</template>
</div>
</app-header-layout>
<hassio-markdown-dialog title="[[markdownTitle]]" content="[[markdownContent]]"></hassio-markdown-dialog>
`;
<hassio-markdown-dialog
title="[[markdownTitle]]"
content="[[markdownContent]]"
></hassio-markdown-dialog>
`;
}
static get properties() {
@@ -91,7 +124,7 @@ class HassioAddonView extends PolymerElement {
route: Object,
routeData: {
type: Object,
observer: 'routeDataChanged',
observer: "routeDataChanged",
},
routeMatches: Boolean,
addon: Object,
@@ -99,15 +132,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 +150,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 +159,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 +178,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);

203
hassio/src/ansi-to-html.js Normal file
View File

@@ -0,0 +1,203 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
export const ANSI_HTML_STYLE = html`
<style>
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
.underline {
text-decoration: underline;
}
.strikethrough {
text-decoration: line-through;
}
.underline.strikethrough {
text-decoration: underline line-through;
}
.fg-red {
color: rgb(222, 56, 43);
}
.fg-green {
color: rgb(57, 181, 74);
}
.fg-yellow {
color: rgb(255, 199, 6);
}
.fg-blue {
color: rgb(0, 111, 184);
}
.fg-magenta {
color: rgb(118, 38, 113);
}
.fg-cyan {
color: rgb(44, 181, 233);
}
.fg-white {
color: rgb(204, 204, 204);
}
.bg-black {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: rgb(222, 56, 43);
}
.bg-green {
background-color: rgb(57, 181, 74);
}
.bg-yellow {
background-color: rgb(255, 199, 6);
}
.bg-blue {
background-color: rgb(0, 111, 184);
}
.bg-magenta {
background-color: rgb(118, 38, 113);
}
.bg-cyan {
background-color: rgb(44, 181, 233);
}
.bg-white {
background-color: rgb(204, 204, 204);
}
</style>
`;
export function parseTextToColoredPre(text) {
const pre = document.createElement("pre");
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
let i = 0;
const state = {
bold: false,
italic: false,
underline: false,
strikethrough: false,
foregroundColor: null,
backgroundColor: null,
};
const addSpan = (content) => {
const span = document.createElement("span");
if (state.bold) span.classList.add("bold");
if (state.italic) span.classList.add("italic");
if (state.underline) span.classList.add("underline");
if (state.strikethrough) span.classList.add("strikethrough");
if (state.foregroundColor !== null)
span.classList.add(`fg-${state.foregroundColor}`);
if (state.backgroundColor !== null)
span.classList.add(`bg-${state.backgroundColor}`);
span.appendChild(document.createTextNode(content));
pre.appendChild(span);
};
/* eslint-disable no-cond-assign */
let match;
while ((match = re.exec(text)) !== null) {
const j = match.index;
addSpan(text.substring(i, j));
i = j + match[0].length;
if (match[1] === undefined) continue;
for (const colorCode of match[1].split(";")) {
switch (parseInt(colorCode)) {
case 0:
// reset
state.bold = false;
state.italic = false;
state.underline = false;
state.strikethrough = false;
state.foregroundColor = null;
state.backgroundColor = null;
break;
case 1:
state.bold = true;
break;
case 3:
state.italic = true;
break;
case 4:
state.underline = true;
break;
case 9:
state.strikethrough = true;
break;
case 22:
state.bold = false;
break;
case 23:
state.italic = false;
break;
case 24:
state.underline = false;
break;
case 29:
state.strikethrough = false;
break;
case 30:
// foreground black
state.foregroundColor = null;
break;
case 31:
state.foregroundColor = "red";
break;
case 32:
state.foregroundColor = "green";
break;
case 33:
state.foregroundColor = "yellow";
break;
case 34:
state.foregroundColor = "blue";
break;
case 35:
state.foregroundColor = "magenta";
break;
case 36:
state.foregroundColor = "cyan";
break;
case 37:
state.foregroundColor = "white";
break;
case 39:
// foreground reset
state.foregroundColor = null;
break;
case 40:
state.backgroundColor = "black";
break;
case 41:
state.backgroundColor = "red";
break;
case 42:
state.backgroundColor = "green";
break;
case 43:
state.backgroundColor = "yellow";
break;
case 44:
state.backgroundColor = "blue";
break;
case 45:
state.backgroundColor = "magenta";
break;
case 46:
state.backgroundColor = "cyan";
break;
case 47:
state.backgroundColor = "white";
break;
case 49:
// background reset
state.backgroundColor = null;
break;
}
}
}
addSpan(text.substring(i));
return pre;
}

View File

@@ -1,60 +1,74 @@
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";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../../../src/components/ha-relative-time.js';
import "../../../src/components/ha-relative-time";
class HassioCardContent extends PolymerElement {
static get template() {
return html`
<style>
iron-icon {
margin-right: 16px;
margin-top: 16px;
float: left;
color: var(--secondary-text-color);
}
iron-icon.update {
color: var(--paper-orange-400);
}
iron-icon.running,
iron-icon.installed {
color: var(--paper-green-400);
}
iron-icon.hassupdate,
iron-icon.snapshot {
color: var(--paper-item-icon-color);
}
.title {
color: var(--primary-text-color);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.addition {
color: var(--secondary-text-color);
overflow: hidden;
position: relative;
height: 2.4em;
line-height: 1.2em;
}
ha-relative-time {
display: block;
}
</style>
<iron-icon icon="[[icon]]" class\$="[[iconClass]]" title="[[iconTitle]]"></iron-icon>
<div>
<div class="title">[[title]]</div>
<div class="addition">
<template is="dom-if" if="[[description]]">
[[description]]
</template>
<template is="dom-if" if="[[datetime]]">
<ha-relative-time hass="[[hass]]" class="addition" datetime="[[datetime]]"></ha-relative-time>
</template>
<style>
iron-icon {
margin-right: 16px;
margin-top: 16px;
float: left;
color: var(--secondary-text-color);
}
iron-icon.update {
color: var(--paper-orange-400);
}
iron-icon.running,
iron-icon.installed {
color: var(--paper-green-400);
}
iron-icon.hassupdate,
iron-icon.snapshot {
color: var(--paper-item-icon-color);
}
iron-icon.not_available {
color: var(--google-red-500);
}
.title {
color: var(--primary-text-color);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.addition {
color: var(--secondary-text-color);
overflow: hidden;
position: relative;
height: 2.4em;
line-height: 1.2em;
}
ha-relative-time {
display: block;
}
</style>
<iron-icon
icon="[[icon]]"
class\$="[[iconClass]]"
title="[[iconTitle]]"
></iron-icon>
<div>
<div class="title">[[title]]</div>
<div class="addition">
<template is="dom-if" if="[[description]]">
[[description]]
</template>
<template is="dom-if" if="[[!available]]">
(Not available)
</template>
<template is="dom-if" if="[[datetime]]">
<ha-relative-time
hass="[[hass]]"
class="addition"
datetime="[[datetime]]"
></ha-relative-time>
</template>
</div>
</div>
</div>
`;
`;
}
static get properties() {
@@ -62,14 +76,15 @@ class HassioCardContent extends PolymerElement {
hass: Object,
title: String,
description: String,
available: Boolean,
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,37 +1,52 @@
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";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
import "../components/hassio-card-content";
import "../resources/hassio-style";
import NavigateMixin from "../../../src/mixins/navigate-mixin";
class HassioAddons extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style hassio-style">
paper-card {
cursor: pointer;
}
</style>
<div class="content card-group">
<div class="title">Add-ons</div>
<template is="dom-if" if="[[!addons.length]]">
<paper-card>
<div class="card-content">
You don't have any add-ons installed yet. Head over to <a href="#" on-click="openStore">the add-on store</a> to get started!
</div>
</paper-card>
</template>
<template is="dom-repeat" items="[[addons]]" as="addon" sort="sortAddons">
<paper-card on-click="addonTapped">
<div class="card-content">
<hassio-card-content hass="[[hass]]" title="[[addon.name]]" description="[[addon.description]]" icon="[[computeIcon(addon)]]" icon-title="[[computeIconTitle(addon)]]" icon-class="[[computeIconClass(addon)]]"></hassio-card-content>
</div>
</paper-card>
</template>
</div>
`;
<style include="ha-style hassio-style">
paper-card {
cursor: pointer;
}
</style>
<div class="content card-group">
<div class="title">Add-ons</div>
<template is="dom-if" if="[[!addons.length]]">
<paper-card>
<div class="card-content">
You don't have any add-ons installed yet. Head over to
<a href="#" on-click="openStore">the add-on store</a> to get
started!
</div>
</paper-card>
</template>
<template
is="dom-repeat"
items="[[addons]]"
as="addon"
sort="sortAddons"
>
<paper-card on-click="addonTapped">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[addon.name]]"
description="[[addon.description]]"
available="[[addon.available]]"
icon="[[computeIcon(addon)]]"
icon-title="[[computeIconTitle(addon)]]"
icon-class="[[computeIconClass(addon)]]"
></hassio-card-content>
</div>
</paper-card>
</template>
</div>
`;
}
static get properties() {
@@ -46,28 +61,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,23 +1,29 @@
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import './hassio-addons.js';
import './hassio-hass-update.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "./hassio-addons";
import "./hassio-hass-update";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioDashboard extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
margin: 0 auto;
}
</style>
<div class="content">
<hassio-hass-update hass="[[hass]]" hass-info="[[hassInfo]]"></hassio-hass-update>
<hassio-addons hass="[[hass]]" addons="[[supervisorInfo.addons]]"></hassio-addons>
</div>
`;
<style include="iron-flex ha-style">
.content {
margin: 0 auto;
}
</style>
<div class="content">
<hassio-hass-update
hass="[[hass]]"
hass-info="[[hassInfo]]"
></hassio-hass-update>
<hassio-addons
hass="[[hass]]"
addons="[[supervisorInfo.addons]]"
></hassio-addons>
</div>
`;
}
static get properties() {
@@ -29,4 +35,4 @@ class HassioDashboard extends EventsMixin(PolymerElement) {
}
}
customElements.define('hassio-dashboard', HassioDashboard);
customElements.define("hassio-dashboard", HassioDashboard);

View File

@@ -1,45 +1,69 @@
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";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import "../components/hassio-card-content";
import "../resources/hassio-style";
class HassioHassUpdate extends PolymerElement {
static get template() {
return html`
<style include="ha-style hassio-style">
paper-card {
display: block;
margin-bottom: 32px;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
</style>
<template is="dom-if" if="[[computeUpdateAvailable(hassInfo)]]">
<div class="content">
<div class="card-group">
<div class="title">Update available! 🎉</div>
<paper-card>
<div class="card-content">
<hassio-card-content hass="[[hass]]" title="Home Assistant [[hassInfo.last_version]] is available" description="You are currently running version [[hassInfo.version]]" icon="hassio:home-assistant" icon-class="hassupdate"></hassio-card-content>
<template is="dom-if" if="[[error]]">
<div class="error">Error: [[error]]</div>
</template>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/homeassistant/update">Update</ha-call-api-button>
<a href="https://github.com/home-assistant/home-assistant/releases" target="_blank"><paper-button>Release notes</paper-button></a>
</div>
</paper-card>
<style include="ha-style hassio-style">
paper-card {
display: block;
margin-bottom: 32px;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
a {
color: var(--primary-color);
}
</style>
<template is="dom-if" if="[[computeUpdateAvailable(hassInfo)]]">
<div class="content">
<div class="card-group">
<div class="title">Update available! 🎉</div>
<paper-card>
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="Home Assistant [[hassInfo.last_version]] is available"
description="You are currently running version [[hassInfo.version]]"
icon="hassio:home-assistant"
icon-class="hassupdate"
></hassio-card-content>
<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
>
<a
href="https://github.com/home-assistant/home-assistant/releases"
target="_blank"
><paper-button>Release notes</paper-button></a
>
</div>
</paper-card>
</div>
</div>
</div>
</template>
`;
</template>
`;
}
static get properties() {
@@ -52,7 +76,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 +87,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 +99,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,16 +1,21 @@
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";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import './hassio-main.js';
import './resources/hassio-icons.js';
import "./hassio-main";
import "./resources/hassio-icons";
class HassioApp extends PolymerElement {
static get template() {
return html`
<template is="dom-if" if="[[hass]]">
<hassio-main hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-main>
</template>
`;
<template is="dom-if" if="[[hass]]">
<hassio-main
hass="[[hass]]"
narrow="[[narrow]]"
show-menu="[[showMenu]]"
route="[[route]]"
></hassio-main>
</template>
`;
}
static get properties() {
@@ -29,13 +34,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 +48,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";
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,34 +1,61 @@
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";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import "./addon-view/hassio-addon-view";
import "./hassio-data";
import "./hassio-pages-with-tabs";
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";
import EventsMixin from "../../src/mixins/events-mixin";
import NavigateMixin from "../../src/mixins/navigate-mixin";
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>
<hassio-data id="data" hass="[[hass]]" supervisor="{{supervisorInfo}}" homeassistant="{{hassInfo}}" host="{{hostInfo}}"></hassio-data>
<app-route
route="[[route]]"
pattern="/:page"
data="{{routeData}}"
></app-route>
<hassio-data
id="data"
hass="[[hass]]"
supervisor="{{supervisorInfo}}"
homeassistant="{{hassInfo}}"
host="{{hostInfo}}"
></hassio-data>
<template is="dom-if" if="[[!loaded]]">
<hass-loading-screen narrow="[[narrow]]" show-menu="[[showMenu]]"></hass-loading-screen>
</template>
<template is="dom-if" if="[[!loaded]]">
<hass-loading-screen
narrow="[[narrow]]"
show-menu="[[showMenu]]"
></hass-loading-screen>
</template>
<template is="dom-if" if="[[loaded]]">
<template is="dom-if" if="[[!equalsAddon(routeData.page)]]">
<hassio-pages-with-tabs hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" page="[[routeData.page]]" supervisor-info="[[supervisorInfo]]" hass-info="[[hassInfo]]" host-info="[[hostInfo]]"></hassio-pages-with-tabs>
<template is="dom-if" if="[[loaded]]">
<template is="dom-if" if="[[!equalsAddon(routeData.page)]]">
<hassio-pages-with-tabs
hass="[[hass]]"
narrow="[[narrow]]"
show-menu="[[showMenu]]"
page="[[routeData.page]]"
supervisor-info="[[supervisorInfo]]"
hass-info="[[hassInfo]]"
host-info="[[hostInfo]]"
></hassio-pages-with-tabs>
</template>
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
<hassio-addon-view
hass="[[hass]]"
narrow="[[narrow]]"
show-menu="[[showMenu]]"
route="[[route]]"
></hassio-addon-view>
</template>
</template>
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
<hassio-addon-view hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-addon-view>
</template>
</template>
`;
`;
}
static get properties() {
@@ -40,11 +67,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 +79,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
hassInfo: Object,
loaded: {
type: Boolean,
computed: 'computeIsLoaded(supervisorInfo, hostInfo, hassInfo)',
computed: "computeIsLoaded(supervisorInfo, hostInfo, hassInfo)",
},
};
}
@@ -60,7 +87,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 +100,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 +111,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,65 +1,68 @@
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";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../../src/components/ha-markdown.js';
import '../../src/resources/ha-style.js';
import "../../src/components/ha-markdown";
import "../../src/resources/ha-style";
class HassioMarkdownDialog extends PolymerElement {
static get template() {
return html`
<style include="ha-style-dialog">
paper-dialog {
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
app-toolbar [main-title] {
margin-left: 16px;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
<style include="ha-style-dialog">
paper-dialog {
max-height: 100%;
}
paper-dialog::before {
content: "";
position: fixed;
z-index: -1;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: inherit;
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
}
</style>
<paper-dialog id="dialog" with-backdrop="">
<app-toolbar>
<paper-icon-button icon="hassio:close" dialog-dismiss=""></paper-icon-button>
<div main-title="">[[title]]</div>
</app-toolbar>
<paper-dialog-scrollable>
<ha-markdown content="[[content]]"></ha-markdown>
</paper-dialog-scrollable>
</paper-dialog>
`;
app-toolbar [main-title] {
margin-left: 16px;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
paper-dialog {
max-height: 100%;
}
paper-dialog::before {
content: "";
position: fixed;
z-index: -1;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: inherit;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
}
</style>
<paper-dialog id="dialog" with-backdrop="">
<app-toolbar>
<paper-icon-button
icon="hassio:close"
dialog-dismiss=""
></paper-icon-button>
<div main-title="">[[title]]</div>
</app-toolbar>
<paper-dialog-scrollable>
<ha-markdown content="[[content]]"></ha-markdown>
</paper-dialog-scrollable>
</paper-dialog>
`;
}
static get properties() {
@@ -73,4 +76,4 @@ class HassioMarkdownDialog extends PolymerElement {
this.$.dialog.open();
}
}
customElements.define('hassio-markdown-dialog', HassioMarkdownDialog);
customElements.define("hassio-markdown-dialog", HassioMarkdownDialog);

View File

@@ -1,75 +1,107 @@
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";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import "../../src/resources/ha-style";
import "./addon-store/hassio-addon-store";
import "./dashboard/hassio-dashboard";
import "./hassio-markdown-dialog";
import "./snapshots/hassio-snapshot";
import "./snapshots/hassio-snapshots";
import "./system/hassio-system";
import scrollToTarget from '../../src/common/dom/scroll-to-target.js';
import scrollToTarget from "../../src/common/dom/scroll-to-target";
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
import NavigateMixin from "../../src/mixins/navigate-mixin";
class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-positioning ha-style">
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: #FFF;
text-transform: uppercase;
}
</style>
<app-header-layout id="layout" has-scrolling-region>
<app-header fixed slot="header">
<app-toolbar>
<ha-menu-button hassio narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
<div main-title>Hass.io</div>
<template is="dom-if" if="[[showRefreshButton(page)]]">
<paper-icon-button icon="hassio:refresh" on-click="refreshClicked"></paper-icon-button>
</template>
</app-toolbar>
<paper-tabs scrollable="" selected="[[page]]" attr-for-selected="page-name" on-iron-activate="handlePageSelected">
<paper-tab page-name="dashboard">Dashboard</paper-tab>
<paper-tab page-name="snapshots">Snapshots</paper-tab>
<paper-tab page-name="store">Add-on store</paper-tab>
<paper-tab page-name="system">System</paper-tab>
</paper-tabs>
</app-header>
<template is="dom-if" if="[[equals(page, &quot;dashboard&quot;)]]">
<hassio-dashboard hass="[[hass]]" supervisor-info="[[supervisorInfo]]" hass-info="[[hassInfo]]"></hassio-dashboard>
</template>
<style include="iron-flex iron-positioning ha-style">
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: #fff;
text-transform: uppercase;
}
</style>
<app-header-layout id="layout" has-scrolling-region>
<app-header fixed slot="header">
<app-toolbar>
<ha-menu-button
hassio
narrow="[[narrow]]"
show-menu="[[showMenu]]"
></ha-menu-button>
<div main-title>Hass.io</div>
<template is="dom-if" if="[[showRefreshButton(page)]]">
<paper-icon-button
icon="hassio:refresh"
on-click="refreshClicked"
></paper-icon-button>
</template>
</app-toolbar>
<paper-tabs
scrollable=""
selected="[[page]]"
attr-for-selected="page-name"
on-iron-activate="handlePageSelected"
>
<paper-tab page-name="dashboard">Dashboard</paper-tab>
<paper-tab page-name="snapshots">Snapshots</paper-tab>
<paper-tab page-name="store">Add-on store</paper-tab>
<paper-tab page-name="system">System</paper-tab>
</paper-tabs>
</app-header>
<template is="dom-if" if="[[equals(page, &quot;dashboard&quot;)]]">
<hassio-dashboard
hass="[[hass]]"
supervisor-info="[[supervisorInfo]]"
hass-info="[[hassInfo]]"
></hassio-dashboard>
</template>
<template is="dom-if" if="[[equals(page, &quot;snapshots&quot;)]]">
<hassio-snapshots
hass="[[hass]]"
installed-addons="[[supervisorInfo.addons]]"
snapshot-slug="{{snapshotSlug}}"
snapshot-deleted="{{snapshotDeleted}}"
></hassio-snapshots>
</template>
<template is="dom-if" if="[[equals(page, &quot;store&quot;)]]">
<hassio-addon-store hass="[[hass]]"></hassio-addon-store>
</template>
<template is="dom-if" if="[[equals(page, &quot;system&quot;)]]">
<hassio-system
hass="[[hass]]"
supervisor-info="[[supervisorInfo]]"
host-info="[[hostInfo]]"
></hassio-system>
</template>
</app-header-layout>
<hassio-markdown-dialog
title="[[markdownTitle]]"
content="[[markdownContent]]"
></hassio-markdown-dialog>
<template is="dom-if" if="[[equals(page, &quot;snapshots&quot;)]]">
<hassio-snapshots hass="[[hass]]" installed-addons="[[supervisorInfo.addons]]" snapshot-slug="{{snapshotSlug}}" snapshot-deleted="{{snapshotDeleted}}"></hassio-snapshots>
<hassio-snapshot
hass="[[hass]]"
snapshot-slug="{{snapshotSlug}}"
snapshot-deleted="{{snapshotDeleted}}"
></hassio-snapshot>
</template>
<template is="dom-if" if="[[equals(page, &quot;store&quot;)]]">
<hassio-addon-store hass="[[hass]]"></hassio-addon-store>
</template>
<template is="dom-if" if="[[equals(page, &quot;system&quot;)]]">
<hassio-system hass="[[hass]]" supervisor-info="[[supervisorInfo]]" host-info="[[hostInfo]]"></hassio-system>
</template>
</app-header-layout>
<hassio-markdown-dialog title="[[markdownTitle]]" content="[[markdownContent]]"></hassio-markdown-dialog>
<template is="dom-if" if="[[equals(page, &quot;snapshots&quot;)]]">
<hassio-snapshot hass="[[hass]]" snapshot-slug="{{snapshotSlug}}" snapshot-deleted="{{snapshotDeleted}}"></hassio-snapshot>
</template>
`;
`;
}
static get properties() {
@@ -87,18 +119,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 +144,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 +160,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";
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,111 +1,139 @@
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";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getSignedPath } from "../../../src/auth/data";
import '../../../src/resources/ha-style.js';
import "../../../src/resources/ha-style";
class HassioSnapshot extends PolymerElement {
static get template() {
return html`
<style include="ha-style-dialog">
paper-dialog {
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
app-toolbar [main-title] {
margin-left: 16px;
}
paper-dialog-scrollable {
margin: 0;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
<style include="ha-style-dialog">
paper-dialog {
max-height: 100%;
height: 100%;
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
}
.details {
color: var(--secondary-text-color);
}
.download {
color: var(--primary-color);
}
.warning,
.error {
color: var(--google-red-500);
}
</style>
<paper-dialog id="dialog" with-backdrop="" on-iron-overlay-closed="_dialogClosed">
<app-toolbar>
<paper-icon-button icon="hassio:close" dialog-dismiss=""></paper-icon-button>
<div main-title="">[[_computeName(snapshot)]]</div>
</app-toolbar>
<div class="details">
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br>
[[_formatDatetime(snapshot.date)]]
</div>
<div>Home Assistant:</div>
<paper-checkbox checked="{{restoreHass}}">
Home Assistant [[snapshot.homeassistant]]
</paper-checkbox>
<template is="dom-if" if="[[snapshot.addons.length]]">
<div>Folders:</div>
<template is="dom-repeat" items="[[snapshot.folders]]">
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
</paper-checkbox>
</template>
</template>
<template is="dom-if" if="[[snapshot.addons.length]]">
<div>Add-ons:</div>
<paper-dialog-scrollable>
<template is="dom-repeat" items="[[snapshot.addons]]" sort="_sortAddons">
app-toolbar [main-title] {
margin-left: 16px;
}
paper-dialog-scrollable {
margin: 0;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
paper-dialog {
max-height: 100%;
height: 100%;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
}
.details {
color: var(--secondary-text-color);
}
.download {
color: var(--primary-color);
}
.warning,
.error {
color: var(--google-red-500);
}
</style>
<paper-dialog
id="dialog"
with-backdrop=""
on-iron-overlay-closed="_dialogClosed"
>
<app-toolbar>
<paper-icon-button
icon="hassio:close"
dialog-dismiss=""
></paper-icon-button>
<div main-title="">[[_computeName(snapshot)]]</div>
</app-toolbar>
<div class="details">
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br />
[[_formatDatetime(snapshot.date)]]
</div>
<div>Home Assistant:</div>
<paper-checkbox checked="{{restoreHass}}">
Home Assistant [[snapshot.homeassistant]]
</paper-checkbox>
<template is="dom-if" if="[[snapshot.addons.length]]">
<div>Folders:</div>
<template is="dom-repeat" items="[[snapshot.folders]]">
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
<span class="details">([[item.version]])</span>
</paper-checkbox>
</template>
</paper-dialog-scrollable>
</template>
<template is="dom-if" if="[[snapshot.protected]]">
<paper-input autofocus="" label="Password" type="password" value="{{snapshotPassword}}"></paper-input>
</template>
<template is="dom-if" if="[[error]]">
<p class="error">Error: [[error]]</p>
</template>
<div class="buttons">
<paper-icon-button icon="hassio:delete" on-click="_deleteClicked" class="warning" title="Delete snapshot"></paper-icon-button>
<a href="[[_computeDownloadUrl(snapshotSlug)]]" download="[[_computeDownloadName(snapshot)]]">
<paper-icon-button icon="hassio:download" class="download" title="Download snapshot"></paper-icon-button>
</a>
<paper-button on-click="_partialRestoreClicked">Restore selected</paper-button>
<template is="dom-if" if="[[_isFullSnapshot(snapshot.type)]]">
<paper-button on-click="_fullRestoreClicked">Wipe &amp; restore</paper-button>
</template>
</div>
</paper-dialog>
`;
<template is="dom-if" if="[[snapshot.addons.length]]">
<div>Add-ons:</div>
<paper-dialog-scrollable>
<template
is="dom-repeat"
items="[[snapshot.addons]]"
sort="_sortAddons"
>
<paper-checkbox checked="{{item.checked}}">
[[item.name]] <span class="details">([[item.version]])</span>
</paper-checkbox>
</template>
</paper-dialog-scrollable>
</template>
<template is="dom-if" if="[[snapshot.protected]]">
<paper-input
autofocus=""
label="Password"
type="password"
value="{{snapshotPassword}}"
></paper-input>
</template>
<template is="dom-if" if="[[error]]">
<p class="error">Error: [[error]]</p>
</template>
<div class="buttons">
<paper-icon-button
icon="hassio:delete"
on-click="_deleteClicked"
class="warning"
title="Delete snapshot"
></paper-icon-button>
<paper-icon-button
on-click="_downloadClicked"
icon="hassio:download"
class="download"
title="Download snapshot"
></paper-icon-button>
<paper-button on-click="_partialRestoreClicked"
>Restore selected</paper-button
>
<template is="dom-if" if="[[_isFullSnapshot(snapshot.type)]]">
<paper-button on-click="_fullRestoreClicked"
>Wipe &amp; restore</paper-button
>
</template>
</div>
</paper-dialog>
`;
}
static get properties() {
@@ -114,7 +142,7 @@ class HassioSnapshot extends PolymerElement {
snapshotSlug: {
type: String,
notify: true,
observer: '_snapshotSlugChanged',
observer: "_snapshotSlugChanged",
},
snapshotDeleted: {
type: Boolean,
@@ -131,94 +159,144 @@ 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) {
const password = encodeURIComponent(this.hass.connection.options.authToken);
return `/api/hassio/snapshots/${snapshotSlug}/download?api_password=${password}`;
}
_computeDownloadName(snapshot) {
const name = this._computeName(snapshot).replace(/[^a-z0-9]+/gi, '_');
return `Hass_io_${name}.tar`;
async _downloadClicked() {
let signedPath;
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/snapshots/${this.snapshotSlug}/download`
);
} catch (err) {
alert(`Error: ${err.message}`);
return;
}
const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_");
const a = document.createElement("A");
a.href = signedPath.path;
a.download = `Hass_io_${name}.tar`;
this.$.dialog.appendChild(a);
a.click();
this.$.dialog.removeChild(a);
}
_computeName(snapshot) {
@@ -226,11 +304,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 +317,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 +330,4 @@ class HassioSnapshot extends PolymerElement {
this.snapshotSlug = null;
}
}
customElements.define('hassio-snapshot', HassioSnapshot);
customElements.define("hassio-snapshot", HassioSnapshot);

View File

@@ -1,103 +1,133 @@
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";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import '../components/hassio-card-content.js';
import '../resources/hassio-style.js';
import EventsMixin from '../../../src/mixins/events-mixin.js';
import "../components/hassio-card-content";
import "../resources/hassio-style";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioSnapshots extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style hassio-style">
paper-radio-group {
display: block;
}
paper-radio-button {
padding: 0 0 2px 2px;
}
paper-radio-button,
paper-checkbox,
paper-input[type="password"] {
display: block;
margin: 4px 0 4px 48px;
}
.pointer {
cursor: pointer;
}
</style>
<div class="content">
<div class="card-group">
<div class="title">
Create snapshot
<div class="description">
Snapshots allow you to easily backup and
restore all data of your Hass.io instance.
<style include="ha-style hassio-style">
paper-radio-group {
display: block;
}
paper-radio-button {
padding: 0 0 2px 2px;
}
paper-radio-button,
paper-checkbox,
paper-input[type="password"] {
display: block;
margin: 4px 0 4px 48px;
}
.pointer {
cursor: pointer;
}
</style>
<div class="content">
<div class="card-group">
<div class="title">
Create snapshot
<div class="description">
Snapshots allow you to easily backup and restore all data of your
Hass.io instance.
</div>
</div>
</div>
<paper-card>
<div class="card-content">
<paper-input autofocus="" label="Name" value="{{snapshotName}}"></paper-input>
Type:
<paper-radio-group selected="{{snapshotType}}">
<paper-radio-button name="full">
Full snapshot
</paper-radio-button>
<paper-radio-button name="partial">
Partial snapshot
</paper-radio-button>
</paper-radio-group>
<template is="dom-if" if="[[!_fullSelected(snapshotType)]]">
Folders:
<template is="dom-repeat" items="[[folderList]]">
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
</paper-checkbox>
</template>
Add-ons:
<template is="dom-repeat" items="[[addonList]]" sort="_sortAddons">
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
</paper-checkbox>
</template>
</template>
Security:
<paper-checkbox checked="{{snapshotHasPassword}}">Password protection</paper-checkbox>
<template is="dom-if" if="[[snapshotHasPassword]]">
<paper-input label="Password" type="password" value="{{snapshotPassword}}"></paper-input>
</template>
<template is="dom-if" if="[[error]]">
<p class="error">[[error]]</p>
</template>
</div>
<div class="card-actions">
<paper-button disabled="[[creatingSnapshot]]" on-click="_createSnapshot">Create</paper-button>
</div>
</paper-card>
</div>
<div class="card-group">
<div class="title">Available snapshots</div>
<template is="dom-if" if="[[!snapshots.length]]">
<paper-card>
<div class="card-content">You don't have any snapshots yet.</div>
</paper-card>
</template>
<template is="dom-repeat" items="[[snapshots]]" as="snapshot" sort="_sortSnapshots">
<paper-card class="pointer" on-click="_snapshotClicked">
<div class="card-content">
<hassio-card-content hass="[[hass]]" title="[[_computeName(snapshot)]]" description="[[_computeDetails(snapshot)]]" datetime="[[snapshot.date]]" icon="[[_computeIcon(snapshot.type)]]" icon-class="snapshot"></hassio-card-content>
<paper-input
autofocus=""
label="Name"
value="{{snapshotName}}"
></paper-input>
Type:
<paper-radio-group selected="{{snapshotType}}">
<paper-radio-button name="full">
Full snapshot
</paper-radio-button>
<paper-radio-button name="partial">
Partial snapshot
</paper-radio-button>
</paper-radio-group>
<template is="dom-if" if="[[!_fullSelected(snapshotType)]]">
Folders:
<template is="dom-repeat" items="[[folderList]]">
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
</paper-checkbox>
</template>
Add-ons:
<template
is="dom-repeat"
items="[[addonList]]"
sort="_sortAddons"
>
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
</paper-checkbox>
</template>
</template>
Security:
<paper-checkbox checked="{{snapshotHasPassword}}"
>Password protection</paper-checkbox
>
<template is="dom-if" if="[[snapshotHasPassword]]">
<paper-input
label="Password"
type="password"
value="{{snapshotPassword}}"
></paper-input>
</template>
<template is="dom-if" if="[[error]]">
<p class="error">[[error]]</p>
</template>
</div>
<div class="card-actions">
<paper-button
disabled="[[creatingSnapshot]]"
on-click="_createSnapshot"
>Create</paper-button
>
</div>
</paper-card>
</template>
</div>
<div class="card-group">
<div class="title">Available snapshots</div>
<template is="dom-if" if="[[!snapshots.length]]">
<paper-card>
<div class="card-content">You don't have any snapshots yet.</div>
</paper-card>
</template>
<template
is="dom-repeat"
items="[[snapshots]]"
as="snapshot"
sort="_sortSnapshots"
>
<paper-card class="pointer" on-click="_snapshotClicked">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[_computeName(snapshot)]]"
description="[[_computeDetails(snapshot)]]"
datetime="[[snapshot.date]]"
icon="[[_computeIcon(snapshot.type)]]"
icon-class="snapshot"
></hassio-card-content>
</div>
</paper-card>
</template>
</div>
</div>
</div>
`;
`;
}
static get properties() {
@@ -105,16 +135,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 +152,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 +175,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
snapshotDeleted: {
type: Boolean,
notify: true,
observer: '_snapshotDeletedChanged',
observer: "_snapshotDeletedChanged",
},
creatingSnapshot: Boolean,
dialogOpened: Boolean,
@@ -151,7 +185,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 +196,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 +275,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 +291,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
}
_fullSelected(type) {
return type === 'full';
return type === "full";
}
_snapshotDeletedChanged(snapshotDeleted) {
@@ -252,11 +302,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,94 +1,118 @@
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";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioHostInfo extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
paper-card {
display: inline-block;
width: 400px;
margin-left: 8px;
}
.card-content {
height: 200px;
}
@media screen and (max-width: 830px) {
<style include="iron-flex ha-style">
paper-card {
margin-top: 8px;
margin-left: 0;
width: 100%;
display: inline-block;
width: 400px;
margin-left: 8px;
}
.card-content {
height: 100%;
height: 200px;
color: var(--primary-text-color);
}
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
paper-button.info {
max-width: calc(50% - 12px);
}
</style>
<paper-card>
<div class="card-content">
<h2>Host system</h2>
<table class="info">
<tbody><tr>
<td>Hostname</td>
<td>[[data.hostname]]</td>
</tr>
<tr>
<td>System</td>
<td>[[data.operating_system]]</td>
</tr>
<template is="dom-if" if="[[data.deployment]]">
<tr>
<td>Deployment</td>
<td>[[data.deployment]]</td>
</tr>
</template>
</tbody></table>
<paper-button raised on-click="_showHardware" class="info">
Hardware
</paper-button>
<template is="dom-if" if="[[_featureAvailable(data, 'hostname')]]">
<paper-button raised on-click="_changeHostnameClicked" class="info">
Change hostname
@media screen and (max-width: 830px) {
paper-card {
margin-top: 8px;
margin-left: 0;
width: 100%;
}
.card-content {
height: auto;
}
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
paper-button.info {
max-width: calc(50% - 12px);
}
table.info {
margin-bottom: 10px;
}
</style>
<paper-card>
<div class="card-content">
<h2>Host system</h2>
<table class="info">
<tbody>
<tr>
<td>Hostname</td>
<td>[[data.hostname]]</td>
</tr>
<tr>
<td>System</td>
<td>[[data.operating_system]]</td>
</tr>
<template is="dom-if" if="[[data.deployment]]">
<tr>
<td>Deployment</td>
<td>[[data.deployment]]</td>
</tr>
</template>
</tbody>
</table>
<paper-button raised on-click="_showHardware" class="info">
Hardware
</paper-button>
</template>
<template is="dom-if" if="[[errors]]">
<div class="errors">Error: [[errors]]</div>
</template>
</div>
<div class="card-actions">
<template is="dom-if" if="[[_featureAvailable(data, 'reboot')]]">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/host/reboot">Reboot</ha-call-api-button>
</template>
<template is="dom-if" if="[[_featureAvailable(data, 'shutdown')]]">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/host/shutdown">Shutdown</ha-call-api-button>
</template>
<template is="dom-if" if="[[_featureAvailable(data, 'hassos')]]">
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/hassos/config/sync" title="Load HassOS configs or updates from USB">Import from USB</ha-call-api-button>
</template>
<template is="dom-if" if="[[_computeUpdateAvailable(_hassOs)]]">
<ha-call-api-button hass="[[hass]]" path="hassio/hassos/update">Update</ha-call-api-button>
</template>
</div>
</paper-card>
`;
<template is="dom-if" if="[[_featureAvailable(data, 'hostname')]]">
<paper-button raised on-click="_changeHostnameClicked" class="info">
Change hostname
</paper-button>
</template>
<template is="dom-if" if="[[errors]]">
<div class="errors">Error: [[errors]]</div>
</template>
</div>
<div class="card-actions">
<template is="dom-if" if="[[_featureAvailable(data, 'reboot')]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/host/reboot"
>Reboot</ha-call-api-button
>
</template>
<template is="dom-if" if="[[_featureAvailable(data, 'shutdown')]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/host/shutdown"
>Shutdown</ha-call-api-button
>
</template>
<template is="dom-if" if="[[_featureAvailable(data, 'hassos')]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/hassos/config/sync"
title="Load HassOS configs or updates from USB"
>Import from USB</ha-call-api-button
>
</template>
<template is="dom-if" if="[[_computeUpdateAvailable(_hassOs)]]">
<ha-call-api-button hass="[[hass]]" path="hassio/hassos/update"
>Update</ha-call-api-button
>
</template>
</div>
</paper-card>
`;
}
static get properties() {
@@ -96,16 +120,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 +140,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 +166,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 +202,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,80 +1,104 @@
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";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
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";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
paper-card {
display: inline-block;
width: 400px;
}
.card-content {
height: 200px;
}
@media screen and (max-width: 830px) {
<style include="iron-flex ha-style">
paper-card {
width: 100%;
display: inline-block;
width: 400px;
}
.card-content {
height: 100%;
height: 200px;
color: var(--primary-text-color);
}
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
</style>
<paper-card>
<div class="card-content">
<h2>Hass.io supervisor</h2>
<table class="info">
<tbody><tr>
<td>Version</td>
<td>
[[data.version]]
</td>
</tr>
<tr>
<td>Latest version</td>
<td>[[data.last_version]]</td>
</tr>
<template is="dom-if" if="[[!_equals(data.channel, &quot;stable&quot;)]]">
<tr>
<td>Channel</td>
<td>[[data.channel]]</td>
</tr>
@media screen and (max-width: 830px) {
paper-card {
width: 100%;
}
.card-content {
height: auto;
}
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
</style>
<paper-card>
<div class="card-content">
<h2>Hass.io supervisor</h2>
<table class="info">
<tbody>
<tr>
<td>Version</td>
<td>[[data.version]]</td>
</tr>
<tr>
<td>Latest version</td>
<td>[[data.last_version]]</td>
</tr>
<template
is="dom-if"
if="[[!_equals(data.channel, &quot;stable&quot;)]]"
>
<tr>
<td>Channel</td>
<td>[[data.channel]]</td>
</tr>
</template>
</tbody>
</table>
<template is="dom-if" if="[[errors]]">
<div class="errors">Error: [[errors]]</div>
</template>
</tbody></table>
<template is="dom-if" if="[[errors]]">
<div class="errors">Error: [[errors]]</div>
</template>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/reload">Reload</ha-call-api-button>
<template is="dom-if" if="[[computeUpdateAvailable(data)]]">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/update">Update</ha-call-api-button>
</template>
<template is="dom-if" if="[[_equals(data.channel, &quot;beta&quot;)]]">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[leaveBeta]]">Leave beta channel</ha-call-api-button>
</template>
<template is="dom-if" if="[[_equals(data.channel, &quot;stable&quot;)]]">
<paper-button on-click="_joinBeta" class="warning" title="Get beta updates for Home Assistant (RCs), supervisor and host">Join beta channel</paper-button>
</template>
</div>
</paper-card>
`;
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/reload"
>Reload</ha-call-api-button
>
<template is="dom-if" if="[[computeUpdateAvailable(data)]]">
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/update"
>Update</ha-call-api-button
>
</template>
<template
is="dom-if"
if="[[_equals(data.channel, &quot;beta&quot;)]]"
>
<ha-call-api-button
hass="[[hass]]"
path="hassio/supervisor/options"
data="[[leaveBeta]]"
>Leave beta channel</ha-call-api-button
>
</template>
<template
is="dom-if"
if="[[_equals(data.channel, &quot;stable&quot;)]]"
>
<paper-button
on-click="_joinBeta"
class="warning"
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>Join beta channel</paper-button
>
</template>
</div>
</paper-card>
`;
}
static get properties() {
@@ -84,14 +108,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 +126,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 +142,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 +163,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,34 +1,38 @@
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";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
class HassioSupervisorLog extends PolymerElement {
static get template() {
return html`
<style include="ha-style">
paper-card {
display: block;
}
pre {
overflow-x: auto;
}
</style>
<paper-card>
<div class="card-content">
<pre>[[log]]</pre>
</div>
<div class="card-actions">
<paper-button on-click="refreshTapped">Refresh</paper-button>
</div>
</paper-card>
`;
<style include="ha-style">
paper-card {
display: block;
}
pre {
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
}
.fg-green {
color: var(--primary-text-color) !important;
}
</style>
${ANSI_HTML_STYLE}
<paper-card>
<div class="card-content" id="content"></div>
<div class="card-actions">
<paper-button on-click="refresh">Refresh</paper-button>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
log: String,
};
}
@@ -38,17 +42,23 @@ class HassioSupervisorLog extends PolymerElement {
}
loadData() {
this.hass.callApi('get', 'hassio/supervisor/logs')
.then((info) => {
this.log = info;
}, () => {
this.log = 'Error fetching logs';
});
this.hass.callApi("get", "hassio/supervisor/logs").then(
(text) => {
while (this.$.content.lastChild) {
this.$.content.removeChild(this.$.content.lastChild);
}
this.$.content.appendChild(parseTextToColoredPre(text));
},
() => {
this.$.content.innerHTML =
'<span class="fg-red bold">Error fetching logs</span>';
}
);
}
refreshTapped() {
refresh() {
this.loadData();
}
}
customElements.define('hassio-supervisor-log', HassioSupervisorLog);
customElements.define("hassio-supervisor-log", HassioSupervisorLog);

View File

@@ -1,34 +1,40 @@
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";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import './hassio-host-info.js';
import './hassio-supervisor-info.js';
import './hassio-supervisor-log.js';
import "./hassio-host-info";
import "./hassio-supervisor-info";
import "./hassio-supervisor-log";
class HassioSystem extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
margin: 4px;
}
.title {
margin-top: 24px;
color: var(--primary-text-color);
font-size: 2em;
padding-left: 8px;
margin-bottom: 8px;
}
</style>
<div class="content">
<div class="title">Information</div>
<hassio-supervisor-info hass="[[hass]]" data="[[supervisorInfo]]"></hassio-supervisor-info>
<hassio-host-info hass="[[hass]]" data="[[hostInfo]]"></hassio-host-info>
<div class="title">System log</div>
<hassio-supervisor-log hass="[[hass]]"></hassio-supervisor-log>
</div>
`;
<style include="iron-flex ha-style">
.content {
margin: 4px;
}
.title {
margin-top: 24px;
color: var(--primary-text-color);
font-size: 2em;
padding-left: 8px;
margin-bottom: 8px;
}
</style>
<div class="content">
<div class="title">Information</div>
<hassio-supervisor-info
hass="[[hass]]"
data="[[supervisorInfo]]"
></hassio-supervisor-info>
<hassio-host-info
hass="[[hass]]"
data="[[hostInfo]]"
></hassio-host-info>
<div class="title">System log</div>
<hassio-supervisor-log hass="[[hass]]"></hassio-supervisor-log>
</div>
`;
}
static get properties() {
@@ -40,4 +46,4 @@ class HassioSystem extends PolymerElement {
}
}
customElements.define('hassio-system', HassioSystem);
customElements.define("hassio-system", HassioSystem);

View File

@@ -1,69 +1,56 @@
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 isCI = process.env.CI === "true";
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 &&
!isCI &&
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,114 @@
"version": "1.0.0",
"scripts": {
"build": "script/build_frontend",
"lint": "eslint src hassio/src gallery/src test-mocha && polymer lint",
"mocha": "node_modules/.bin/mocha --opts test-mocha/mocha.opts",
"test": "npm run lint && npm run mocha"
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc",
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
"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": "^3.0.39",
"@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",
"@vaadin/vaadin-date-picker": "^3.3.1",
"@webcomponents/shadycss": "^1.6.0",
"@webcomponents/webcomponentsjs": "^2.2.0",
"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.2.4",
"intl-messageformat": "^2.2.0",
"jquery": "^3.3.1",
"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",
"round-slider": "^1.3.2",
"superstruct": "^0.6.0",
"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",
"chai": "^4.1.2",
"compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.5.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.1.0",
"@gfx/zopfli": "^1.0.9",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"babel-eslint": "^10",
"babel-loader": "^8.0.4",
"babel-minify-webpack-plugin": "^0.3.1",
"chai": "^4.2.0",
"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,37 +123,64 @@
"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-webpack-plugin": "^3.2.0",
"husky": "^1.1.0",
"lint-staged": "^8.0.2",
"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",
"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.16.2",
"reify": "^0.18.1",
"require-dir": "^1.0.0",
"sinon": "^6.0.0",
"uglifyjs-webpack-plugin": "^1.2.6",
"sinon": "^7.1.1",
"ts-mocha": "^2.0.0",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.1.4",
"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",
"fecha": "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz"
"@polymer/polymer": "3.1.0",
"@webcomponents/webcomponentsjs": "2.2.1",
"@webcomponents/shadycss": "^1.6.0",
"@vaadin/vaadin-overlay": "3.2.2",
"@vaadin/vaadin-lumo-styles": "1.3.0",
"fecha": "https://github.com/taylorhakes/fecha/archive/5e8fe08d982647fdb19fb403459838b02647813c.tar.gz",
"lit-html": "0.12.0",
"@polymer/lit-element": "0.6.2"
},
"main": "src/home-assistant.js"
"main": "src/home-assistant.js",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"*.{js,ts,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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -4,6 +4,8 @@
# Stop on errors
set -e
cd "$(dirname "$0")/.."
BUILD_DIR=build
OUTPUT_DIR=hass_frontend
OUTPUT_DIR_ES5=hass_frontend_es5

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

@@ -9,9 +9,12 @@ cd "$(dirname "$0")/.."
# Install node modules
yarn install
# Verify everything is ok
tsc
script/build_frontend
rm -rf dist
python3 setup.py sdist
python3 -m twine upload dist/*
python3 -m twine upload dist/*

11
script/size_stats Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
# Analyze stats
# Stop on errors
set -e
cd "$(dirname "$0")/.."
STATS=1 NODE_ENV=production webpack --profile --json > compilation-stats.json
npx webpack-bundle-analyzer compilation-stats.json hass_frontend
rm compilation-stats.json

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 \

54
script/version_bump.js Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
const fs = require("fs");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
function patch(version) {
const parts = version.split(".");
return `${parts[0]}.${Number(parts[1]) + 1}`;
}
function today() {
const now = new Date();
return `${now.getFullYear()}${now.getMonth() + 1}${String(
now.getDate()
).padStart(2, "0")}.0`;
}
const methods = {
patch,
today,
};
async function main(args) {
const method = args.length > 0 && methods[args[0]];
const commit = args.length > 1 && args[1] == "--commit";
if (!method) {
console.error(
"Missing required method. Choose from",
Object.keys(methods).join(", ")
);
return;
}
const setup = fs.readFileSync("setup.py", "utf8");
const version = setup.match(/\d{8}\.\d+/)[0];
const newVersion = method(version);
console.log("Current version:", version);
console.log("New version:", newVersion);
fs.writeFileSync("setup.py", setup.replace(version, newVersion), "utf-8");
if (!commit) {
return;
}
const { stdout } = await exec(
`git commit -am "Bumped version to ${newVersion}"`
);
console.log(stdout);
}
main(process.argv.slice(2));

View File

@@ -1,20 +1,22 @@
from setuptools import setup, find_packages
setup(name='home-assistant-frontend',
version='20180827.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="2019109.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,
)

7
src/auth/data.ts Normal file
View File

@@ -0,0 +1,7 @@
import { HomeAssistant } from "../types";
import { SignedPath } from "./types";
export const getSignedPath = (
hass: HomeAssistant,
path: string
): Promise<SignedPath> => hass.callWS({ type: "auth/sign_path", path });

View File

@@ -1,71 +1,81 @@
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';
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/paper-button/paper-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import "../components/ha-form";
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
class HaAuthFlow extends LocalizeLiteMixin(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: 24px 0 8px;
text-align: center;
}
</style>
<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(_state, &quot;error&quot;)]]">
[[localize('ui.panel.page-authorize.form.unknown_error')]]:
</template>
<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>
<style>
:host {
/* So we can set min-height to avoid jumping during loading */
display: block;
}
.action {
margin: 24px 0 8px;
text-align: center;
}
.error {
color: red;
}
</style>
<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;form&quot;)]]">
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
<ha-markdown content="[[_computeStepDescription(localize, _step)]]" allow-svg></ha-markdown>
<template is="dom-if" if="[[_equals(_state, &quot;error&quot;)]]">
<div class="error">Error: [[_errorMsg]]</div>
</template>
<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>
<ha-form
data="{{_stepData}}"
schema="[[_step.data_schema]]"
error="[[_step.errors]]"
compute-label="[[_computeLabelCallback(localize, _step)]]"
compute-error="[[_computeErrorCallback(localize, _step)]]"
></ha-form>
<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>
`;
</form>
`;
}
static get properties() {
return {
authProvider: {
type: Object,
observer: '_providerChanged',
observer: "_providerChanged",
},
clientId: String,
redirectUri: String,
oauth2State: String,
_state: {
type: String,
value: 'loading'
value: "loading",
},
_stepData: {
type: Object,
@@ -74,14 +84,15 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
_step: {
type: Object,
notify: true,
}
},
_errorMsg: String,
};
}
ready() {
super.ready();
this.addEventListener('keypress', (ev) => {
this.addEventListener("keypress", (ev) => {
if (ev.keyCode === 13) {
this._handleSubmit();
}
@@ -89,40 +100,55 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
}
async _providerChanged(newProvider, oldProvider) {
if (oldProvider && this._step && this._step.type === 'form') {
if (oldProvider && this._step && this._step.type === "form") {
fetch(`/auth/login_flow/${this._step.flow_id}`, {
method: 'DELETE',
credentials: 'same-origin',
method: "DELETE",
credentials: "same-origin",
}).catch(() => {});
}
try {
const response = await fetch('/auth/login_flow', {
method: 'POST',
credentials: 'same-origin',
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 step = await response.json();
this._updateStep(step);
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',
_state: "step",
};
if (this._step && step.step_id !== this._step.step_id) {
if (
this._step &&
(step.flow_id !== this._step.flow_id ||
step.step_id !== this._step.step_id)
) {
props._stepData = {};
}
@@ -134,15 +160,23 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
}
_computeSubmitCaption(stepType) {
return stepType === 'form' ? 'Next' : 'Start over';
return stepType === "form" ? "Next" : "Start over";
}
_computeStepAbortedReason(localize, step) {
return localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`);
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 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);
@@ -153,22 +187,32 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
_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}`);
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}`);
return (error) =>
localize(
`ui.panel.page-authorize.form.providers.${
step.handler[0]
}.error.${error}`
);
}
async _handleSubmit() {
if (this._step.type !== 'form') {
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`);
this.style.setProperty("min-height", `${this.offsetHeight}px`);
const postData = Object.assign({}, this._stepData, {
client_id: this.clientId,
@@ -176,20 +220,20 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
try {
const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify(postData)
method: "POST",
credentials: "same-origin",
body: JSON.stringify(postData),
});
const newStep = await response.json();
if (newStep.type === 'create_entry') {
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)}`;
@@ -204,11 +248,11 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
this._updateStep(newStep);
} catch (err) {
// eslint-disable-next-line
console.error('Error submitting step', err);
this._state = 'error-loading';
console.error("Error submitting step", err);
this._state = "error-loading";
} finally {
this.style.setProperty('min-height', '');
this.style.setProperty("min-height", "");
}
}
}
customElements.define('ha-auth-flow', HaAuthFlow);
customElements.define("ha-auth-flow", HaAuthFlow);

View File

@@ -1,130 +0,0 @@
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 '../components/ha-markdown.js';
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
import '../auth/ha-auth-flow.js';
class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
static get template() {
return html`
<style>
ha-markdown a {
color: var(--primary-color);
}
ha-markdown p:last-child{
margin-bottom: 0;
}
ha-pick-auth-provider {
display: block;
margin-top: 48px;
}
</style>
<template is="dom-if" if="[[!_authProviders]]">
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
</template>
<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>
</template>
`;
}
static get properties() {
return {
_authProvider: String,
_authProviders: Array,
clientId: String,
redirectUri: String,
oauth2State: String,
translationFragment: {
type: String,
value: 'page-authorize',
}
};
}
async ready() {
super.ready();
const query = {};
const values = location.search.substr(1).split('&');
for (let i = 0; i < values.length; i++) {
const value = values[i].split('=');
if (value.length > 1) {
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
}
}
const props = {};
if (query.client_id) props.clientId = query.client_id;
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();
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';
}
}
_computeMultiple(array) {
return array && array.length > 1;
}
async _handleAuthProviderPick(ev) {
this._authProvider = ev.detail;
}
_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);

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