Compare commits

...

78 Commits

Author SHA1 Message Date
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
234 changed files with 7673 additions and 3267 deletions

View File

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

3
.gitignore vendored
View File

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

View File

@@ -1,25 +1,31 @@
FROM node:8.2.1-alpine
FROM node:8.9-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.

View File

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

View File

@@ -43,11 +43,11 @@ class HassioAddonStore extends PolymerElement {
sortRepos(a, b) {
if (a.slug === 'local') {
return -1;
} else if (b.slug === 'local') {
} if (b.slug === 'local') {
return 1;
} else if (a.slug === 'core') {
} if (a.slug === 'core') {
return -1;
} else if (b.slug === 'core') {
} if (b.slug === 'core') {
return 1;
}
return a.name < b.name ? -1 : 1;

View File

@@ -195,8 +195,8 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
openChangelog() {
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/changelog`)
.then(
resp => resp
, () => 'Error getting changelog'
resp => resp,
() => 'Error getting changelog'
).then((content) => {
this.fire('hassio-markdown-dialog', {
title: 'Changelog',

View File

@@ -30,6 +30,7 @@ class HassioHassUpdate extends PolymerElement {
<template is="dom-if" if="[[error]]">
<div class="error">Error: [[error]]</div>
</template>
<p><a href='https://www.home-assistant.io/latest-release-notes/' target='_blank'>Read the release notes</a></p>
</div>
<div class="card-actions">
<ha-call-api-button hass="[[hass]]" path="hassio/homeassistant/update">Update</ha-call-api-button>

View File

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

View File

@@ -8,9 +8,10 @@ import './hassio-data.js';
import './hassio-pages-with-tabs.js';
import applyThemesOnElement from '../../src/common/dom/apply_themes_on_element.js';
import EventsMixin from '../../src/mixins/events-mixin.js';
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
class HassioMain extends NavigateMixin(PolymerElement) {
class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
static get template() {
return html`
<app-route route="[[route]]" pattern="/:page" data="{{routeData}}"></app-route>
@@ -84,15 +85,16 @@ 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);
}
this.fire('iron-resize');
}
equalsAddon(page) {

View File

@@ -166,8 +166,9 @@ class HassioSnapshot extends PolymerElement {
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 folders = this.snapshot.folders.filter(
folder => folder.checked
).map(folder => folder.slug);
const data = {
homeassistant: this.restoreHass,

View File

@@ -17,6 +17,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
}
.card-content {
height: 200px;
color: var(--primary-text-color);
}
@media screen and (max-width: 830px) {
paper-card {
@@ -145,8 +146,8 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
_showHardware() {
this.hass.callApi('get', 'hassio/hardware/info')
.then(
resp => this._objectToMarkdown(resp.data)
, () => 'Error getting hardware info'
resp => this._objectToMarkdown(resp.data),
() => 'Error getting hardware info'
).then((content) => {
this.fire('hassio-markdown-dialog', {
title: 'Hardware',

View File

@@ -16,6 +16,7 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
}
.card-content {
height: 200px;
color: var(--primary-text-color);
}
@media screen and (max-width: 830px) {
paper-card {

View File

@@ -10,96 +10,101 @@
"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"
"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",
"@mdi/svg": "^2.7.94",
"@polymer/app-layout": "^3.0.1",
"@polymer/app-localize-behavior": "^3.0.1",
"@polymer/app-route": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
"@polymer/font-roboto": "^3.0.2",
"@polymer/font-roboto-local": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-iconset-svg": "^3.0.1",
"@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-media-query": "^3.0.1",
"@polymer/iron-pages": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/neon-animation": "^3.0.1",
"@polymer/paper-button": "^3.0.1",
"@polymer/paper-card": "^3.0.1",
"@polymer/paper-checkbox": "^3.0.1",
"@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-drawer-panel": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-fab": "^3.0.1",
"@polymer/paper-icon-button": "^3.0.1",
"@polymer/paper-input": "^3.0.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-menu-button": "^3.0.1",
"@polymer/paper-progress": "^3.0.1",
"@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-scroll-header-panel": "^3.0.1",
"@polymer/paper-slider": "^3.0.1",
"@polymer/paper-spinner": "^3.0.1",
"@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.0.1",
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-toggle-button": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "^3.0.5",
"@vaadin/vaadin-combo-box": "4.2.0-alpha3",
"@vaadin/vaadin-date-picker": "3.3.0-alpha1",
"@webcomponents/shadycss": "^1.5.2",
"@webcomponents/webcomponentsjs": "^2.1.3",
"chart.js": "~2.7.2",
"chartjs-chart-timeline": "^0.2.1",
"es6-object-assign": "^1.1.0",
"eslint-import-resolver-webpack": "^0.10.0",
"eslint-import-resolver-webpack": "^0.10.1",
"fecha": "^2.3.3",
"home-assistant-js-websocket": "^3.0.0",
"home-assistant-js-websocket": "^3.1.4",
"intl-messageformat": "^2.2.0",
"js-yaml": "^3.12.0",
"leaflet": "^1.3.1",
"marked": "^0.4.0",
"mdn-polyfills": "^5.8.0",
"leaflet": "^1.3.4",
"marked": "^0.5.0",
"mdn-polyfills": "^5.12.0",
"moment": "^2.22.2",
"preact": "^8.2.9",
"preact-compat": "^3.18.0",
"react-big-calendar": "^0.19.1",
"regenerator-runtime": "^0.11.1",
"unfetch": "^3.0.0",
"preact": "^8.3.1",
"preact-compat": "^3.18.4",
"react-big-calendar": "^0.19.2",
"regenerator-runtime": "^0.12.1",
"unfetch": "^4.0.1",
"web-animations-js": "^2.3.1",
"xss": "^1.0.3"
},
"devDependencies": {
"@gfx/zopfli": "^1.0.9",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-eslint": "^9.0.0",
"babel-loader": "^7.1.4",
"babel-minify-webpack-plugin": "^0.3.1",
"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",
"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-plugin-import": "^2.14.0",
"eslint-plugin-react": "^7.11.1",
"gulp": "^3.9.1",
"gulp-foreach": "^0.1.0",
"gulp-hash": "^4.2.2",
@@ -107,36 +112,37 @@
"gulp-json-transform": "^0.4.5",
"gulp-jsonminify": "^1.1.0",
"gulp-merge-json": "^1.3.1",
"gulp-rename": "^1.3.0",
"gulp-rename": "^1.4.0",
"html-loader": "^0.5.5",
"html-minifier": "^3.5.16",
"html-minifier": "^3.5.20",
"html-webpack-plugin": "^3.2.0",
"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",
"raw-loader": "^0.5.1",
"reify": "^0.16.2",
"reify": "^0.17.3",
"require-dir": "^1.0.0",
"sinon": "^6.0.0",
"uglifyjs-webpack-plugin": "^1.2.6",
"sinon": "^6.3.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",
"@polymer/polymer": "3.0.5",
"@webcomponents/webcomponentsjs": "2.1.3",
"@webcomponents/shadycss": "^1.5.2",
"@vaadin/vaadin-overlay": "3.2.0-alpha3",
"@vaadin/vaadin-lumo-styles": "1.2.0",
"fecha": "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz"
},
"main": "src/home-assistant.js"

View File

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

View File

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

103
script/docker_run.sh Executable file
View File

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

View File

@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
setup(name='home-assistant-frontend',
version='20180903.0',
version='20180927.0',
description='The Home Assistant frontend',
url='https://github.com/home-assistant/home-assistant-polymer',
author='The Home Assistant Authors',

View File

@@ -137,8 +137,8 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
_state: 'step',
};
if (this._step &&
(step.flow_id !== this._step.flow_id || 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 = {};
}

View File

@@ -7,7 +7,7 @@ import '../components/ha-markdown.js';
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
import '../auth/ha-auth-flow.js';
import './ha-auth-flow.js';
class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
static get template() {
@@ -94,6 +94,12 @@ class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
const response = await window.providersPromise;
const authProviders = await response.json();
// Forward to main screen which will redirect to right onboarding page.
if (response.status === 400 && authProviders.code === 'onboarding_required') {
location.href = '/';
return;
}
if (authProviders.length === 0) {
alert('No auth providers returned. Unable to finish login.');
return;
@@ -119,15 +125,14 @@ class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
}
_computeInactiveProvders(curProvider, providers) {
return providers.filter(prv =>
prv.type !== curProvider.type || prv.id !== curProvider.id);
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)
localize('ui.panel.page-authorize.authorizing_client', 'clientId', clientId)
+ '\n\n'
+ localize('ui.panel.page-authorize.logging_in_with', 'authProviderName', authProvider.name)
);
}
}

View File

@@ -58,8 +58,7 @@ class HaCardChooser extends PolymerElement {
if (!newData) return;
// ha-entities-card is exempt from observer as it doesn't load heavy resources.
// and usually doesn't load external resources (except for entity_picture).
const eligibleToObserver =
(window.IntersectionObserver && newData.cardType !== 'entities');
const eligibleToObserver = (window.IntersectionObserver && newData.cardType !== 'entities');
if (!eligibleToObserver) {
if (this.observer) {
this.observer.unobserve(this);

View File

@@ -134,8 +134,8 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
showGroupToggle(groupEntity, states) {
if (!groupEntity || !states || groupEntity.attributes.control === 'hidden' ||
(groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
if (!groupEntity || !states || groupEntity.attributes.control === 'hidden'
|| (groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
return false;
}

View File

@@ -76,9 +76,9 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
stateObjObserver(stateObj) {
if (!stateObj) return;
if (this.cacheConfig.cacheKey !== stateObj.entity_id ||
this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) ||
this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)) {
if (this.cacheConfig.cacheKey !== stateObj.entity_id
|| this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0)
|| this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)) {
this.cacheConfig = Object.assign({}, {
refresh: stateObj.attributes.refresh || 0,
cacheKey: stateObj.entity_id,

View File

@@ -219,7 +219,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
if (picture !== oldPicture && !picture) {
this.$.cover.style.backgroundImage = '';
return;
} else if (picture === oldPicture) {
} if (picture === oldPicture) {
return;
}
@@ -275,7 +275,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
computePlaybackControlIcon(playerObj) {
if (playerObj.isPlaying) {
return playerObj.supportsPause ? 'hass:pause' : 'hass:stop';
} else if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
} if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
if (playerObj.hasMediaControl && playerObj.supportsPause && !playerObj.isPaused) {
return 'hass:play-pause';
}

View File

@@ -8,6 +8,7 @@ import '../components/ha-markdown.js';
import computeStateName from '../common/entity/compute_state_name.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import computeObjectId from '../common/entity/compute_object_id';
/*
* @appliesMixin LocalizeMixin
@@ -59,13 +60,15 @@ class HaPersistentNotificationCard extends LocalizeMixin(PolymerElement) {
}
computeTitle(stateObj) {
return (stateObj.attributes.title ||
computeStateName(stateObj));
return (stateObj.attributes.title
|| computeStateName(stateObj));
}
dismissTap(ev) {
ev.preventDefault();
this.hass.callApi('DELETE', 'states/' + this.stateObj.entity_id);
this.hass.callService('persistent_notification', 'dismiss', {
notification_id: computeObjectId(this.stateObj.entity_id)
});
}
}
customElements.define('ha-persistent_notification-card', HaPersistentNotificationCard);

View File

@@ -113,7 +113,7 @@ class HaPlantCard extends EventsMixin(PolymerElement) {
if (attr === 'battery') {
if (batLvl <= 5) {
return `${icon}-alert`;
} else if (batLvl < 95) {
} if (batLvl < 95) {
return `${icon}-${Math.round((batLvl / 10) - 0.01) * 10}`;
}
}

View File

@@ -176,7 +176,7 @@ class HaWeatherCard extends
cloudy: 'hass:weather-cloudy',
fog: 'hass:weather-fog',
hail: 'hass:weather-hail',
lightning: 'mid:weather-lightning',
lightning: 'hass:weather-lightning',
'lightning-rainy': 'hass:weather-lightning-rainy',
partlycloudy: 'hass:weather-partlycloudy',
pouring: 'hass:weather-pouring',

View File

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

View File

@@ -10,8 +10,8 @@ function toLocaleDateStringSupportsOptions() {
return false;
}
export default (toLocaleDateStringSupportsOptions() ?
function (dateObj, locales) {
export default (toLocaleDateStringSupportsOptions()
? function (dateObj, locales) {
return dateObj.toLocaleDateString(
locales,
{ year: 'numeric', month: 'long', day: 'numeric' },

View File

@@ -10,8 +10,8 @@ function toLocaleStringSupportsOptions() {
return false;
}
export default (toLocaleStringSupportsOptions() ?
function (dateObj, locales) {
export default (toLocaleStringSupportsOptions()
? function (dateObj, locales) {
return dateObj.toLocaleString(locales, {
year: 'numeric',
month: 'long',

View File

@@ -10,8 +10,8 @@ function toLocaleTimeStringSupportsOptions() {
return false;
}
export default (toLocaleTimeStringSupportsOptions() ?
function (dateObj, locales) {
export default (toLocaleTimeStringSupportsOptions()
? function (dateObj, locales) {
return dateObj.toLocaleTimeString(
locales,
{ hour: 'numeric', minute: '2-digit' }

View File

@@ -7,9 +7,9 @@ export default function secondsToDuration(d) {
if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
} else if (m > 0) {
} if (m > 0) {
return `${m}:${leftPad(s)}`;
} else if (s > 0) {
} if (s > 0) {
return '' + s;
}
return null;

View File

@@ -4,7 +4,7 @@ export default function canToggleDomain(hass, domain) {
if (domain === 'lock') {
return 'lock' in services;
} else if (domain === 'cover') {
} if (domain === 'cover') {
return 'open_cover' in services;
}
return 'turn_on' in services;

View File

@@ -9,8 +9,7 @@ export default function computeStateDisplay(localize, stateObj, language) {
if (domain === 'binary_sensor') {
// Try device class translation, then default binary sensor translation
if (stateObj.attributes.device_class) {
stateObj._stateDisplay =
localize(`state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}`);
stateObj._stateDisplay = localize(`state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}`);
}
if (!stateObj._stateDisplay) {
stateObj._stateDisplay = localize(`state.${domain}.default.${stateObj.state}`);

View File

@@ -3,8 +3,8 @@ import computeObjectId from './compute_object_id';
export default function computeStateName(stateObj) {
if (stateObj._entityDisplay === undefined) {
stateObj._entityDisplay = (
stateObj.attributes.friendly_name ||
computeObjectId(stateObj.entity_id).replace(/_/g, ' '));
stateObj.attributes.friendly_name
|| computeObjectId(stateObj.entity_id).replace(/_/g, ' '));
}
return stateObj._entityDisplay;

View File

@@ -6,6 +6,7 @@
import { DEFAULT_DOMAIN_ICON } from '../const.js';
const fixedIcons = {
alert: 'hass:alert',
automation: 'hass:playlist-play',
calendar: 'hass:calendar',
camera: 'hass:video',
@@ -71,8 +72,8 @@ export default function domainIcon(domain, state) {
return state && state === 'unlocked' ? 'hass:lock-open' : 'hass:lock';
case 'media_player':
return state && state !== 'off' && state !== 'idle' ?
'hass:cast-connected' : 'hass:cast';
return state && state !== 'off' && state !== 'idle'
? 'hass:cast-connected' : 'hass:cast';
case 'zwave':
switch (state) {
@@ -83,7 +84,7 @@ export default function domainIcon(domain, state) {
case 'initializing':
return 'hass:timer-sand';
default:
return 'hass:nfc';
return 'hass:z-wave';
}
default:

View File

@@ -14,7 +14,7 @@ export default function extractViews(entities) {
views.sort((view1, view2) => {
if (view1.entity_id === DEFAULT_VIEW_ENTITY_ID) {
return -1;
} else if (view2.entity_id === DEFAULT_VIEW_ENTITY_ID) {
} if (view2.entity_id === DEFAULT_VIEW_ENTITY_ID) {
return 1;
}
return view1.attributes.order - view2.attributes.order;

View File

@@ -1,4 +1,4 @@
export default function hasLocation(stateObj) {
return ('latitude' in stateObj.attributes &&
'longitude' in stateObj.attributes);
return ('latitude' in stateObj.attributes
&& 'longitude' in stateObj.attributes);
}

View File

@@ -4,7 +4,7 @@ import domainIcon from './domain_icon.js';
export default function inputDateTimeIcon(state) {
if (!state.attributes.has_date) {
return 'hass:clock';
} else if (!state.attributes.has_time) {
} if (!state.attributes.has_time) {
return 'hass:calendar';
}
return domainIcon('input_datetime');

View File

@@ -13,7 +13,7 @@ export default function sensorIcon(state) {
if (dclass in fixedDeviceClassIcons) {
return fixedDeviceClassIcons[dclass];
} else if (dclass === 'battery') {
} if (dclass === 'battery') {
if (isNaN(state.state)) {
return 'hass:battery-unknown';
}

View File

@@ -17,8 +17,9 @@ export default function splitByGroups(entities) {
}
});
groups.forEach(group =>
group.attributes.entity_id.forEach((entityId) => { delete ungrouped[entityId]; }));
groups.forEach(group => group.attributes.entity_id.forEach((entityId) => {
delete ungrouped[entityId];
}));
return { groups, ungrouped };
}

View File

@@ -11,8 +11,8 @@ export default function stateCardType(hass, stateObj) {
if (DOMAINS_WITH_CARD.includes(domain)) {
return domain;
} else if (canToggleState(hass, stateObj) &&
stateObj.attributes.control !== 'hidden') {
} if (canToggleState(hass, stateObj)
&& stateObj.attributes.control !== 'hidden') {
return 'toggle';
}
return 'display';

View File

@@ -19,7 +19,7 @@ const domainIcons = {
export default function stateIcon(state) {
if (!state) {
return DEFAULT_DOMAIN_ICON;
} else if (state.attributes.icon) {
} if (state.attributes.icon) {
return state.attributes.icon;
}

View File

@@ -0,0 +1,10 @@
export default (a, b) => {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
};

View File

@@ -0,0 +1,24 @@
export default function parseAspectRatio(input) {
// Handle 16x9, 16:9, 1.78x1, 1.78:1, 1.78
// Ignore everything else
function parseOrThrow(number) {
const parsed = parseFloat(number);
if (isNaN(parsed)) throw new Error(`${number} is not a number`);
return parsed;
}
try {
if (input) {
const arr = input.replace(':', 'x').split('x');
if (arr.length === 0) {
return null;
}
return arr.length === 1
? { w: parseOrThrow(arr[0]), h: 1 }
: { w: parseOrThrow(arr[0]), h: parseOrThrow(arr[1]) };
}
} catch (err) {
// Ignore the error
}
return null;
}

View File

@@ -219,12 +219,14 @@ class HaChartBase extends mixinBehaviors([
this._resizeTimer = undefined;
}
}
onPropsChange() {
if (!this._isAttached || !this.ChartClass || !this.data) {
return;
}
this.drawChart();
}
_customTooltips(tooltip) {
// Hide if no tooltip
if (tooltip.opacity === 0) {
@@ -285,18 +287,18 @@ class HaChartBase extends mixinBehaviors([
this.set(['metas', index, 'hidden'], this._chart.isDatasetVisible(index) ? null : 'hidden');
this._chart.update();
}
_drawLegend() {
const chart = this._chart;
// New data for old graph. Keep metadata.
const preserveVisibility =
this._oldIdentifier && this.identifier === this._oldIdentifier;
const preserveVisibility = this._oldIdentifier && this.identifier === this._oldIdentifier;
this._oldIdentifier = this.identifier;
this.set('metas', this._chart.data.datasets.map((x, i) => ({
label: x.label,
color: x.color,
bgColor: x.backgroundColor,
hidden: preserveVisibility && i < this.metas.length ?
this.metas[i].hidden : !chart.isDatasetVisible(i),
hidden: preserveVisibility && i < this.metas.length
? this.metas[i].hidden : !chart.isDatasetVisible(i),
})));
let updateNeeded = false;
if (preserveVisibility) {
@@ -311,6 +313,7 @@ class HaChartBase extends mixinBehaviors([
}
this.unit = this.data.unit;
}
_formatTickValue(value, index, values) {
if (values.length === 0) {
return value;
@@ -318,6 +321,7 @@ class HaChartBase extends mixinBehaviors([
const date = new Date(values[index].value);
return formatTime(date);
}
drawChart() {
const data = this.data.data;
const ctx = this.$.chartCanvas;
@@ -493,6 +497,7 @@ class HaChartBase extends mixinBehaviors([
}
return result;
}
static getColorGenerator(staticColors, startIndex) {
// Known colors for static data,
// should add for very common state string manually.

View File

@@ -133,15 +133,15 @@ class HaStateLabelBadge extends
case 'alarm_control_panel':
if (state.state === 'pending') {
return 'hass:clock-fast';
} else if (state.state === 'armed_away') {
} if (state.state === 'armed_away') {
return 'hass:nature';
} else if (state.state === 'armed_home') {
} if (state.state === 'armed_home') {
return 'hass:home-variant';
} else if (state.state === 'armed_night') {
} if (state.state === 'armed_night') {
return 'hass:weather-night';
} else if (state.state === 'armed_custom_bypass') {
} if (state.state === 'armed_custom_bypass') {
return 'hass:security-home';
} else if (state.state === 'triggered') {
} if (state.state === 'triggered') {
return 'hass:alert-circle';
}
// state == 'disarmed'
@@ -151,8 +151,8 @@ class HaStateLabelBadge extends
case 'updater':
return stateIcon(state);
case 'sun':
return state.state === 'above_horizon' ?
domainIcon(domain) : 'hass:brightness-3';
return state.state === 'above_horizon'
? domainIcon(domain) : 'hass:brightness-3';
case 'timer':
return state.state === 'active' ? 'hass:timer' : 'hass:timer-off';
default:
@@ -166,8 +166,8 @@ class HaStateLabelBadge extends
computeLabel(localize, state, _timerTimeRemaining) {
const domain = computeStateDomain(state);
if (state.state === 'unavailable' ||
['device_tracker', 'alarm_control_panel'].includes(domain)) {
if (state.state === 'unavailable'
|| ['device_tracker', 'alarm_control_panel'].includes(domain)) {
// Localize the state with a special state_badge namespace, which has variations of
// the state translations that are truncated to fit within the badge label. Translations
// are only added for device_tracker and alarm_control_panel.

View File

@@ -44,17 +44,15 @@ const PRIORITY = {
mailbox: 7,
};
const getPriority = domain =>
((domain in PRIORITY) ? PRIORITY[domain] : 100);
const getPriority = domain => ((domain in PRIORITY) ? PRIORITY[domain] : 100);
const sortPriority = (domainA, domainB) =>
domainA.priority - domainB.priority;
const sortPriority = (domainA, domainB) => domainA.priority - domainB.priority;
const entitySortBy = (entityA, entityB) => {
const nameA = (entityA.attributes.friendly_name ||
entityA.entity_id).toLowerCase();
const nameB = (entityB.attributes.friendly_name ||
entityB.entity_id).toLowerCase();
const nameA = (entityA.attributes.friendly_name
|| entityA.entity_id).toLowerCase();
const nameB = (entityB.attributes.friendly_name
|| entityB.entity_id).toLowerCase();
if (nameA < nameB) {
return -1;
@@ -187,7 +185,7 @@ class HaCards extends PolymerElement {
this.$.main.parentNode.removeChild(this.$.main);
}
return;
} else if (!this.$.main.parentNode && this.$.main._parentNode) {
} if (!this.$.main.parentNode && this.$.main._parentNode) {
this.$.main._parentNode.appendChild(this.$.main);
}
this._debouncer = Debouncer.debounce(
@@ -284,8 +282,8 @@ class HaCards extends PolymerElement {
const splitted = splitByGroups(states);
if (orderedGroupEntities) {
splitted.groups.sort((gr1, gr2) => orderedGroupEntities[gr1.entity_id] -
orderedGroupEntities[gr2.entity_id]);
splitted.groups.sort((gr1, gr2) => orderedGroupEntities[gr1.entity_id]
- orderedGroupEntities[gr2.entity_id]);
} else {
splitted.groups.sort((gr1, gr2) => gr1.attributes.order - gr2.attributes.order);
}
@@ -332,8 +330,8 @@ class HaCards extends PolymerElement {
cards.badges.push.apply(cards.badges, domain.states);
});
cards.badges.sort((e1, e2) => orderedGroupEntities[e1.entity_id] -
orderedGroupEntities[e2.entity_id]);
cards.badges.sort((e1, e2) => orderedGroupEntities[e1.entity_id]
- orderedGroupEntities[e2.entity_id]);
} else {
iterateDomainSorted(badgesColl, (domain) => {
cards.badges.push.apply(cards.badges, domain.states);

View File

@@ -72,9 +72,11 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
},
};
}
temperatureStateInFlux(inFlux) {
this.$.target_temperature.classList.toggle('in-flux', inFlux);
}
incrementValue() {
const newval = this.value + this.step;
if (this.value < this.max) {
@@ -94,6 +96,7 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
this.value = this.max;
}
}
decrementValue() {
const newval = this.value - this.step;
if (this.value > this.min) {
@@ -106,6 +109,7 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
this.value = this.min;
}
}
valueChanged() {
// when the last_changed timestamp is changed,
// trigger a potential event fire in

View File

@@ -71,15 +71,15 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
computeTarget(hass, stateObj) {
if (!hass || !stateObj) return null;
// We're using "!= null" on purpose so that we match both null and undefined.
if (stateObj.attributes.target_temp_low != null &&
stateObj.attributes.target_temp_high != null) {
if (stateObj.attributes.target_temp_low != null
&& stateObj.attributes.target_temp_high != null) {
return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
} else if (stateObj.attributes.temperature != null) {
} if (stateObj.attributes.temperature != null) {
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
} else if (stateObj.attributes.target_humidity_low != null &&
stateObj.attributes.target_humidity_high != null) {
} if (stateObj.attributes.target_humidity_low != null
&& stateObj.attributes.target_humidity_high != null) {
return `${stateObj.attributes.target_humidity_low} - ${stateObj.attributes.target_humidity_high} %`;
} else if (stateObj.attributes.humidity != null) {
} if (stateObj.attributes.humidity != null) {
return `${stateObj.attributes.humidity} %`;
}

View File

@@ -302,9 +302,9 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
applyHsColor(hs) {
// do nothing is we already have the same color
if (this.hsColor &&
this.hsColor.h === hs.h &&
this.hsColor.s === hs.s) {
if (this.hsColor
&& this.hsColor.h === hs.h
&& this.hsColor.s === hs.s) {
return;
}
this.setMarkerOnColor(hs); // marker is always set on 'raw' hs position

View File

@@ -38,25 +38,31 @@ class HaCoverControls extends PolymerElement {
},
};
}
computeEntityObj(hass, stateObj) {
return new CoverEntity(hass, stateObj);
}
computeOpenDisabled(stateObj, entityObj) {
var assumedState = stateObj.attributes.assumed_state === true;
return (entityObj.isFullyOpen || entityObj.isOpening) && !assumedState;
}
computeClosedDisabled(stateObj, entityObj) {
var assumedState = (stateObj.attributes.assumed_state === true);
return (entityObj.isFullyClosed || entityObj.isClosing) && !assumedState;
}
onOpenTap(ev) {
ev.stopPropagation();
this.entityObj.openCover();
}
onCloseTap(ev) {
ev.stopPropagation();
this.entityObj.closeCover();
}
onStopTap(ev) {
ev.stopPropagation();
this.entityObj.stopCover();

View File

@@ -37,25 +37,31 @@ class HaCoverTiltControls extends PolymerElement {
},
};
}
computeEntityObj(hass, stateObj) {
return new CoverEntity(hass, stateObj);
}
computeOpenDisabled(stateObj, entityObj) {
var assumedState = stateObj.attributes.assumed_state === true;
return entityObj.isFullyOpenTilt && !assumedState;
}
computeClosedDisabled(stateObj, entityObj) {
var assumedState = (stateObj.attributes.assumed_state === true);
return entityObj.isFullyClosedTilt && !assumedState;
}
onOpenTiltTap(ev) {
ev.stopPropagation();
this.entityObj.openCoverTilt();
}
onCloseTiltTap(ev) {
ev.stopPropagation();
this.entityObj.closeCoverTilt();
}
onStopTiltTap(ev) {
ev.stopPropagation();
this.entityObj.stopCoverTilt();

View File

@@ -20,6 +20,10 @@ class HaForm extends EventsMixin(PolymerElement) {
.error {
color: red;
}
paper-checkbox {
display: inline-block;
padding: 22px 0;
}
</style>
<template is="dom-if" if="[[_isArray(schema)]]" restamp="">
<template is="dom-if" if="[[error.base]]">
@@ -96,7 +100,9 @@ class HaForm extends EventsMixin(PolymerElement) {
</template>
<template is="dom-if" if="[[_equals(schema.type, &quot;boolean&quot;)]]" restamp="">
<paper-checkbox checked="{{data}}">[[computeLabel(schema)]]</paper-checkbox>
<div>
<paper-checkbox checked="{{data}}">[[computeLabel(schema)]]</paper-checkbox>
</div>
</template>
<template is="dom-if" if="[[_equals(schema.type, &quot;select&quot;)]]" restamp="">

View File

@@ -10,7 +10,6 @@ class HaLabeledSlider extends PolymerElement {
<style>
:host {
display: block;
padding-bottom: 16px;
}
.title {

View File

@@ -73,8 +73,8 @@ class HaMarkdown extends EventsMixin(PolymerElement) {
const node = walker.currentNode;
// Open external links in a new window
if (node.tagName === 'A' &&
node.host !== document.location.host) {
if (node.tagName === 'A'
&& node.host !== document.location.host) {
node.target = '_blank';
// Fire a resize event when images loaded to notify content resized

View File

@@ -11,8 +11,7 @@ class HaPaperSlider extends PaperSliderClass {
static get template() {
const tpl = document.createElement('template');
tpl.innerHTML = PaperSliderClass.template.innerHTML;
const styleEl = tpl.content.querySelector('style');
styleEl.setAttribute('include', 'paper-slider');
const styleEl = document.createElement('style');
styleEl.innerHTML = `
.pin > .slider-knob > .slider-knob-inner {
font-size: var(--ha-paper-slider-pin-font-size, 10px);
@@ -60,6 +59,7 @@ class HaPaperSlider extends PaperSliderClass {
transform: scale(1) translate(0, -10px);
}
`;
tpl.content.appendChild(styleEl);
return tpl;
}
}

View File

@@ -5,10 +5,10 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import EventsMixin from '../mixins/events-mixin.js';
export const pushSupported = (
'serviceWorker' in navigator && 'PushManager' in window &&
(document.location.protocol === 'https:' ||
document.location.hostname === 'localhost' ||
document.location.hostname === '127.0.0.1'));
'serviceWorker' in navigator && 'PushManager' in window
&& (document.location.protocol === 'https:'
|| document.location.hostname === 'localhost'
|| document.location.hostname === '127.0.0.1'));
/*
* @appliesMixin EventsMixin

View File

@@ -34,7 +34,7 @@ class HaServicePicker extends LocalizeMixin(PolymerElement) {
if (!hass) {
this._services = [];
return;
} else if (oldHass && hass.services === oldHass.services) {
} if (oldHass && hass.services === oldHass.services) {
return;
}
const result = [];

View File

@@ -6,7 +6,7 @@ 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 '../components/ha-icon.js';
import './ha-icon.js';
import '../util/hass-translation.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
@@ -47,7 +47,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
}
paper-listbox {
padding-bottom: 0;
padding: 0;
}
paper-listbox > a {
@@ -59,15 +59,37 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
};
}
paper-icon-item {
margin: 8px;
padding-left: 9px;
border-radius: 4px;
--paper-item-min-height: 40px;
}
.iron-selected paper-icon-item:before {
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
content: "";
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-icon-item[pressed]:before {
opacity: 0.37;
}
paper-icon-item span {
@apply --sidebar-text;
}
a.iron-selected {
--paper-icon-item: {
background-color: var(--sidebar-selected-background-color, var(--paper-grey-200));
};
--paper-item-icon: {
color: var(--sidebar-selected-icon-color);
};
@@ -129,7 +151,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
</app-toolbar>
<paper-listbox attr-for-selected="data-panel" selected="[[hass.panelUrl]]">
<a href='[[_computeUrl(defaultPage)]]' data-panel$="[[defaultPage]]">
<a href='[[_computeUrl(defaultPage)]]' data-panel$="[[defaultPage]]" tabindex="-1">
<paper-icon-item>
<ha-icon slot="item-icon" icon="hass:apps"></ha-icon>
<span class="item-text">[[localize('panel.states')]]</span>
@@ -137,7 +159,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
</a>
<template is="dom-repeat" items="[[panels]]">
<a href='[[_computeUrl(item.url_path)]]' data-panel$='[[item.url_path]]'>
<a href='[[_computeUrl(item.url_path)]]' data-panel$='[[item.url_path]]' tabindex="-1">
<paper-icon-item>
<ha-icon slot="item-icon" icon="[[item.icon]]"></ha-icon>
<span class="item-text">[[_computePanelName(localize, item)]]</span>
@@ -159,14 +181,14 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
<div class="subheader">[[localize('ui.sidebar.developer_tools')]]</div>
<div class="dev-tools layout horizontal justified">
<a href="/dev-service">
<a href="/dev-service" tabindex="-1">
<paper-icon-button
icon="hass:remote"
alt="[[localize('panel.dev-services')]]"
title="[[localize('panel.dev-services')]]"
></paper-icon-button>
</a>
<a href="/dev-state">
<a href="/dev-state" tabindex="-1">
<paper-icon-button
icon="hass:code-tags"
alt="[[localize('panel.dev-states')]]"
@@ -174,7 +196,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
></paper-icon-button>
</a>
<a href="/dev-event">
<a href="/dev-event" tabindex="-1">
<paper-icon-button
icon="hass:radio-tower"
alt="[[localize('panel.dev-events')]]"
@@ -182,7 +204,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
></paper-icon-button>
</a>
<a href="/dev-template">
<a href="/dev-template" tabindex="-1">
<paper-icon-button
icon="hass:file-xml"
alt="[[localize('panel.dev-templates')]]"
@@ -191,7 +213,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
></paper-icon-button>
</a>
<template is="dom-if" if="[[_mqttLoaded(hass)]]">
<a href="/dev-mqtt">
<a href="/dev-mqtt" tabindex="-1">
<paper-icon-button
icon="hass:altimeter"
alt="[[localize('panel.dev-mqtt')]]"
@@ -200,7 +222,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
></paper-icon-button>
</a>
</template>
<a href="/dev-info">
<a href="/dev-info" tabindex="-1">
<paper-icon-button
icon="hass:information-outline"
alt="[[localize('panel.dev-info')]]"
@@ -283,9 +305,9 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
if (aBuiltIn && bBuiltIn) {
return sortValue[a.component_name] - sortValue[b.component_name];
} else if (aBuiltIn) {
} if (aBuiltIn) {
return -1;
} else if (bBuiltIn) {
} if (bBuiltIn) {
return 1;
}
// both not built in, sort by title

View File

@@ -0,0 +1,31 @@
import '@polymer/paper-slider';
const PaperSliderClass = customElements.get('paper-slider');
class HaSlider extends PaperSliderClass {
_calcStep(value) {
if (!this.step) {
return parseFloat(value);
}
const numSteps = Math.round((value - this.min) / this.step);
const stepStr = this.step.toString();
const stepPointAt = stepStr.indexOf('.');
if (stepPointAt !== -1) {
/**
* For small values of this.step, if we calculate the step using
* For non-integer values of this.step, if we calculate the step using
* `Math.round(value / step) * step` we may hit a precision point issue
* eg. 0.1 * 0.2 = 0.020000000000000004
* http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
*
* as a work around we can round with the decimal precision of `step`
*/
const precision = 10 ** (stepStr.length - stepPointAt - 1);
return Math.round((numSteps * this.step + this.min) * precision) / precision;
}
return numSteps * this.step + this.min;
}
}
customElements.define('ha-slider', HaSlider);

View File

@@ -32,8 +32,8 @@ class HaStartVoiceButton extends EventsMixin(PolymerElement) {
}
computeCanListen(hass) {
return ('webkitSpeechRecognition' in window &&
isComponentLoaded(hass, 'conversation'));
return ('webkitSpeechRecognition' in window
&& isComponentLoaded(hass, 'conversation'));
}
handleListenClick() {

View File

@@ -40,4 +40,3 @@ class HaTextarea extends PolymerElement {
}
customElements.define('ha-textarea', HaTextarea);

View File

@@ -74,8 +74,8 @@ class HaVacuumState extends LocalizeMixin(PolymerElement) {
}
_computeLabel(state, interceptable) {
return interceptable ?
this.localize(`ui.card.vacuum.actions.${STATES_INTERCEPTABLE[state].action}`)
return interceptable
? this.localize(`ui.card.vacuum.actions.${STATES_INTERCEPTABLE[state].action}`)
: this.localize(`state.vacuum.${state}`);
}

View File

@@ -42,6 +42,7 @@ class StateHistoryChartLine extends PolymerElement {
}
};
}
static get observers() {
return ['dataChanged(data, endTime, isSingleDevice)'];
}
@@ -85,9 +86,11 @@ class StateHistoryChartLine extends PolymerElement {
return isFinite(parsed) ? parsed : null;
}
endTime = this.endTime ||
new Date(Math.max.apply(null, deviceStates.map(states =>
new Date(states.states[states.states.length - 1].last_changed))));
endTime = this.endTime
// Get the highest date from the last date of each device
|| new Date(Math.max.apply(null, deviceStates.map(devSts =>
new Date(devSts.states[devSts.states.length - 1].last_changed))));
if (endTime > new Date()) {
endTime = new Date();
}
@@ -138,8 +141,8 @@ class StateHistoryChartLine extends PolymerElement {
// range versus ones that have just a target temperature
// Using step chart by step-before so manually interpolation not needed.
const hasTargetRange = states.states.some(state => state.attributes &&
state.attributes.target_temp_high !== state.attributes.target_temp_low);
const hasTargetRange = states.states.some(state => state.attributes
&& state.attributes.target_temp_high !== state.attributes.target_temp_low);
const hasHeat = states.states.some(state => state.state === 'heat');
const hasCool = states.states.some(state => state.state === 'cool');
@@ -208,8 +211,8 @@ class StateHistoryChartLine extends PolymerElement {
const dateTime = date.getTime();
const lastNullDateTime = lastNullDate.getTime();
const lastDateTime = lastDate.getTime();
const tmpValue = ((value - lastValue) *
((lastNullDateTime - lastDateTime) / (dateTime - lastDateTime))) + lastValue;
const tmpValue = ((value - lastValue)
* ((lastNullDateTime - lastDateTime) / (dateTime - lastDateTime))) + lastValue;
pushData(lastNullDate, [tmpValue]);
pushData(new Date(lastNullDateTime + 1), [null]);
pushData(date, [value]);

View File

@@ -83,8 +83,8 @@ class StateHistoryChartTimeline extends PolymerElement {
));
// end time is Math.max(startTime, last_event)
let endTime = this.endTime ||
new Date(stateHistory.reduce((maxTime, stateInfo) => Math.max(
let endTime = this.endTime
|| new Date(stateHistory.reduce((maxTime, stateInfo) => Math.max(
maxTime,
new Date(stateInfo.data[stateInfo.data.length - 1].last_changed)
), startTime));

View File

@@ -67,9 +67,9 @@ class StateHistoryCharts extends LocalizeMixin(PolymerElement) {
}
_computeIsEmpty(isLoadingData, historyData) {
const historyDataEmpty = (!historyData || !historyData.timeline || !historyData.line ||
(historyData.timeline.length === 0 &&
historyData.line.length === 0));
const historyDataEmpty = (!historyData || !historyData.timeline || !historyData.line
|| (historyData.timeline.length === 0
&& historyData.line.length === 0));
return !isLoadingData && historyDataEmpty;
}

View File

@@ -236,8 +236,8 @@ class HaStateHistoryData extends LocalizeMixin(PolymerElement) {
const oldLine = cacheLines.find(cacheLine => cacheLine.unit === unit);
if (oldLine) {
line.data.forEach((entity) => {
const oldEntity =
oldLine.data.find(cacheEntity => entity.entity_id === cacheEntity.entity_id);
const oldEntity = oldLine.data.find(cacheEntity =>
entity.entity_id === cacheEntity.entity_id);
if (oldEntity) {
oldEntity.states = oldEntity.states.concat(entity.states);
} else {
@@ -252,8 +252,8 @@ class HaStateHistoryData extends LocalizeMixin(PolymerElement) {
mergeTimeline(historyTimelines, cacheTimelines) {
historyTimelines.forEach((timeline) => {
const oldTimeline =
cacheTimelines.find(cacheTimeline => cacheTimeline.entity_id === timeline.entity_id);
const oldTimeline = cacheTimelines.find(cacheTimeline =>
cacheTimeline.entity_id === timeline.entity_id);
if (oldTimeline) {
oldTimeline.data = oldTimeline.data.concat(timeline.data);
} else {

View File

@@ -0,0 +1,18 @@
import { createCollection } from 'home-assistant-js-websocket';
const fetchNotifications = conn => conn.sendMessagePromise({
type: 'persistent_notification/get'
});
const subscribeUpdates = (conn, store) => conn.subscribeEvents(
() => fetchNotifications(conn).then(ntf => store.setState(ntf, true)),
'persistent_notifications_updated'
);
export const subscribeNotifications = (conn, onChange) => createCollection(
'_ntf',
fetchNotifications,
subscribeUpdates,
conn,
onChange
);

View File

@@ -1,10 +1,9 @@
import { createCollection } from 'home-assistant-js-websocket';
export const subscribePanels = (conn, onChange) =>
createCollection(
'_pnl',
conn_ => conn_.sendMessagePromise({ type: 'get_panels' }),
null,
conn,
onChange
);
export const subscribePanels = (conn, onChange) => createCollection(
'_pnl',
conn_ => conn_.sendMessagePromise({ type: 'get_panels' }),
null,
conn,
onChange
);

View File

@@ -4,17 +4,15 @@ const fetchThemes = conn => conn.sendMessagePromise({
type: 'frontend/get_themes'
});
const subscribeUpdates = (conn, store) =>
conn.subscribeEvents(
event => store.setState(event.data, true),
'themes_updated'
);
const subscribeUpdates = (conn, store) => conn.subscribeEvents(
event => store.setState(event.data, true),
'themes_updated'
);
export const subscribeThemes = (conn, onChange) =>
createCollection(
'_thm',
fetchThemes,
subscribeUpdates,
conn,
onChange
);
export const subscribeThemes = (conn, onChange) => createCollection(
'_thm',
fetchThemes,
subscribeUpdates,
conn,
onChange
);

View File

@@ -1,10 +1,9 @@
import { createCollection, getUser } from 'home-assistant-js-websocket';
export const subscribeUser = (conn, onChange) =>
createCollection(
'_usr',
conn_ => getUser(conn_),
null,
conn,
onChange
);
export const subscribeUser = (conn, onChange) => createCollection(
'_usr',
conn_ => getUser(conn_),
null,
conn,
onChange
);

View File

@@ -145,8 +145,8 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
this.opened = true;
}));
if (!isComponentLoaded(this.hass, 'config.entity_registry') ||
(oldVal && oldVal.entity_id === newVal.entity_id)) {
if (!isComponentLoaded(this.hass, 'config.entity_registry')
|| (oldVal && oldVal.entity_id === newVal.entity_id)) {
return;
}

View File

@@ -134,8 +134,8 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(EventsMixin(PolymerElement
const props = {
_codeFormat: newVal.attributes.code_format,
_armVisible: state === 'disarmed',
_disarmVisible: this._armedStates.includes(state) ||
state === 'pending' || state === 'triggered' || state === 'arming'
_disarmVisible: this._armedStates.includes(state)
|| state === 'pending' || state === 'triggered' || state === 'arming'
};
props._inputEnabled = props._disarmVisible || props._armVisible;
this.setProperties(props);

View File

@@ -63,9 +63,9 @@ class MoreInfoCamera extends EventsMixin(PolymerElement) {
computeCameraImageUrl(hass, stateObj, isVisible) {
if (hass.demo) {
return '/demo/webcam.jpg';
} else if (stateObj && isVisible) {
return '/api/camera_proxy_stream/' + stateObj.entity_id +
'?token=' + stateObj.attributes.access_token;
} if (stateObj && isVisible) {
return '/api/camera_proxy_stream/' + stateObj.entity_id
+ '?token=' + stateObj.attributes.access_token;
}
// Return an empty image if no stateObj (= dialog not open) or in cleanup mode.
return emptyImageBase64;

View File

@@ -267,8 +267,9 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
// force polymer to recognize selected item change (to update actual label)
this.operationIndex = -1;
if (this.stateObj.attributes.operation_list) {
this.operationIndex =
this.stateObj.attributes.operation_list.indexOf(this.stateObj.attributes.operation_mode);
this.operationIndex = (
this.stateObj.attributes.operation_list.indexOf(this.stateObj.attributes.operation_mode)
);
}
}
@@ -276,8 +277,9 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
// force polymer to recognize selected item change (to update actual label)
this.swingIndex = -1;
if (this.stateObj.attributes.swing_list) {
this.swingIndex =
this.stateObj.attributes.swing_list.indexOf(this.stateObj.attributes.swing_mode);
this.swingIndex = (
this.stateObj.attributes.swing_list.indexOf(this.stateObj.attributes.swing_mode)
);
}
}
@@ -285,23 +287,24 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
// force polymer to recognize selected item change (to update actual label)
this.fanIndex = -1;
if (this.stateObj.attributes.fan_list) {
this.fanIndex =
this.stateObj.attributes.fan_list.indexOf(this.stateObj.attributes.fan_mode);
this.fanIndex = (
this.stateObj.attributes.fan_list.indexOf(this.stateObj.attributes.fan_mode)
);
}
}
computeTemperatureStepSize(hass, stateObj) {
if (stateObj.attributes.target_temp_step) {
return stateObj.attributes.target_temp_step;
} else if (hass.config.unit_system.temperature.indexOf('F') !== -1) {
} if (hass.config.unit_system.temperature.indexOf('F') !== -1) {
return 1;
}
return 0.5;
}
supportsTemperatureControls(stateObj) {
return this.supportsTemperature(stateObj) ||
this.supportsTemperatureRange(stateObj);
return this.supportsTemperature(stateObj)
|| this.supportsTemperatureRange(stateObj);
}
supportsTemperature(stateObj) {

View File

@@ -103,8 +103,8 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
if (newVal) {
this.setProperties({
oscillationToggleChecked: newVal.attributes.oscillating,
speedIndex: newVal.attributes.speed_list ?
newVal.attributes.speed_list.indexOf(newVal.attributes.speed) : -1,
speedIndex: newVal.attributes.speed_list
? newVal.attributes.speed_list.indexOf(newVal.attributes.speed) : -1,
});
}

View File

@@ -6,7 +6,6 @@ import '../../../state-summary/state-card-content.js';
import computeStateDomain from '../../../common/entity/compute_state_domain';
import dynamicContentUpdater from '../../../common/dom/dynamic_content_updater.js';
import stateMoreInfoType from '../../../common/entity/state_more_info_type.js';
class MoreInfoGroup extends PolymerElement {
static get template() {
@@ -70,10 +69,11 @@ class MoreInfoGroup extends PolymerElement {
statesChanged(stateObj, states) {
let groupDomainStateObj = false;
let groupDomain = false;
if (states && states.length > 0) {
const baseStateObj = states.find(s => s.state === 'on') || states[0];
const groupDomain = computeStateDomain(baseStateObj);
groupDomain = computeStateDomain(baseStateObj);
// Groups need to be filtered out or we'll show content of
// first child above the children of the current group
@@ -100,7 +100,7 @@ class MoreInfoGroup extends PolymerElement {
} else {
dynamicContentUpdater(
this.$.groupedControlDetails,
'MORE-INFO-' + stateMoreInfoType(groupDomainStateObj).toUpperCase(),
'MORE-INFO-' + groupDomain.toUpperCase(),
{ stateObj: groupDomainStateObj, hass: this.hass }
);
}

View File

@@ -29,9 +29,6 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
return html`
<style include="iron-flex"></style>
<style>
.effect_list {
padding-bottom: 16px;
}
.effect_list, .brightness, .color_temp, .white_value {
max-height: 0px;
@@ -61,6 +58,18 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
max-height: 84px;
}
.has-brightness
.has-color_temp.is-on,
.has-white_value.is-on {
margin-top: -16px;
}
.has-brightness .brightness,
.has-color_temp.is-on .color_temp,
.has-white_value.is-on .white_value {
padding-top: 16px;
}
.has-color.is-on ha-color-picker {
max-height: 500px;
overflow: visible;
@@ -78,6 +87,7 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
paper-item {
cursor: pointer;
}
</style>
<div class$="[[computeClassNames(stateObj)]]">

View File

@@ -194,7 +194,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
computePlaybackControlIcon(playerObj) {
if (playerObj.isPlaying) {
return playerObj.supportsPause ? 'hass:pause' : 'hass:stop';
} else if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
} if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
if (playerObj.hasMediaControl && playerObj.supportsPause && !playerObj.isPaused) {
return 'hass:play-pause';
}

View File

@@ -25,8 +25,8 @@ class MoreInfoUpdater extends PolymerElement {
}
computeReleaseNotes(stateObj) {
return (stateObj.attributes.release_notes ||
'https://www.home-assistant.io/docs/installation/updating/');
return (stateObj.attributes.release_notes
|| 'https://www.home-assistant.io/docs/installation/updating/');
}
}

View File

@@ -121,7 +121,7 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
cloudy: 'hass:weather-cloudy',
fog: 'hass:weather-fog',
hail: 'hass:weather-hail',
lightning: 'mid:weather-lightning',
lightning: 'hass:weather-lightning',
'lightning-rainy': 'hass:weather-lightning-rainy',
partlycloudy: 'hass:weather-partlycloudy',
pouring: 'hass:weather-pouring',

View File

@@ -55,8 +55,12 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
}
}
paper-dialog-scrollable {
padding-bottom: 16px;
}
:host([domain=camera]) paper-dialog-scrollable {
margin: 0 -24px -5px;
margin: 0 -24px -21px;
}
</style>
@@ -74,7 +78,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
<paper-dialog-scrollable dialog-element="[[dialogElement]]">
<template is="dom-if" if="[[_computeShowHistoryComponent(hass, stateObj)]]" restamp="">
<ha-state-history-data hass="[[hass]]" filter-type="recent-entity" entity-id="[[stateObj.entity_id]]" data="{{_stateHistory}}" is-loading="{{_stateHistoryLoading}}" cache-config="[[_cacheConfig]]"></ha-state-history-data>
<state-history-charts hass="[[hass]]" history-data="[[_stateHistory]]" is-loading-data="[[_stateHistoryLoading]]" up-to-now no-single="[[large]]"></state-history-charts>
<state-history-charts hass="[[hass]]" history-data="[[_stateHistory]]" is-loading-data="[[_stateHistoryLoading]]" up-to-now></state-history-charts>
</template>
<more-info-content state-obj="[[stateObj]]" hass="[[hass]]"></more-info-content>
</paper-dialog-scrollable>
@@ -128,9 +132,9 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
}
_computeShowHistoryComponent(hass, stateObj) {
return hass && stateObj &&
isComponentLoaded(hass, 'history') &&
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeStateDomain(stateObj));
return hass && stateObj
&& isComponentLoaded(hass, 'history')
&& !DOMAINS_MORE_INFO_NO_HISTORY.includes(computeStateDomain(stateObj));
}
_computeDomain(stateObj) {

View File

@@ -9,6 +9,7 @@ import EventsMixin from '../../mixins/events-mixin.js';
import LocalizeMixin from '../../mixins/localize-mixin.js';
import computeStateName from '../../common/entity/compute_state_name.js';
import computeDomain from '../../common/entity/compute_domain.js';
import isComponentLoaded from '../../common/config/is_component_loaded.js';
/*
@@ -44,7 +45,10 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
<app-toolbar>
<paper-icon-button icon="hass:arrow-left" on-click="_backTapped"></paper-icon-button>
<div main-title="">[[_computeStateName(stateObj)]]</div>
<paper-button on-click="_save">[[localize('ui.dialogs.more_info_settings.save')]]</paper-button>
<paper-button
on-click="_save"
disabled='[[_computeInvalid(_entityId)]]'
>[[localize('ui.dialogs.more_info_settings.save')]]</paper-button>
</app-toolbar>
<div class="form">
@@ -55,6 +59,8 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
<paper-input
value="{{_entityId}}"
label="[[localize('ui.dialogs.more_info_settings.entity_id')]]"
error-message="Domain needs to stay the same"
invalid='[[_computeInvalid(_entityId)]]'
></paper-input>
</div>
`;
@@ -90,6 +96,10 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
return isComponentLoaded(hass, 'config.entity_registry');
}
_computeInvalid(entityId) {
return computeDomain(this.stateObj.entity_id) !== computeDomain(entityId);
}
_registryInfoChanged(newVal) {
if (newVal) {
this.setProperties({

View File

@@ -18,8 +18,7 @@ import '../layouts/app/home-assistant.js';
/* polyfill for paper-dropdown */
setTimeout(
() =>
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'),
() => import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'),
2000
);

View File

@@ -8,5 +8,4 @@ import '../resources/roboto.js';
import '../auth/ha-authorize.js';
/* polyfill for paper-dropdown */
setTimeout(() =>
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'), 2000);
setTimeout(() => import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'), 2000);

View File

@@ -4,6 +4,7 @@ import {
subscribeConfig,
subscribeEntities,
subscribeServices,
ERR_INVALID_AUTH,
} from 'home-assistant-js-websocket';
import { loadTokens, saveTokens } from '../common/auth/token_storage.js';
@@ -11,21 +12,45 @@ import { subscribePanels } from '../data/ws-panels.js';
import { subscribeThemes } from '../data/ws-themes.js';
import { subscribeUser } from '../data/ws-user.js';
window.hassAuth = getAuth({
hassUrl: `${location.protocol}//${location.host}`,
saveTokens,
loadTokens: () => Promise.resolve(loadTokens()),
});
const hassUrl = `${location.protocol}//${location.host}`;
const isExternal = location.search.includes('external_auth=1');
window.hassConnection = window.hassAuth.then((auth) => {
if (location.search.includes('auth_callback=1')) {
history.replaceState(null, null, location.pathname);
const authProm = isExternal
? () => import('../common/auth/external_auth.js')
.then(mod => new mod.default(hassUrl))
: () => getAuth({
hassUrl,
saveTokens,
loadTokens: () => Promise.resolve(loadTokens()),
});
const connProm = async (auth) => {
try {
const conn = await createConnection({ auth });
// Clear url if we have been able to establish a connection
if (location.search.includes('auth_callback=1')) {
history.replaceState(null, null, location.pathname);
}
return { auth, conn };
} catch (err) {
if (err !== ERR_INVALID_AUTH) {
throw err;
}
// We can get invalid auth if auth tokens were stored that are no longer valid
// Clear stored tokens.
if (!isExternal) saveTokens(null);
auth = await authProm();
const conn = await createConnection({ auth });
return { auth, conn };
}
return createConnection({ auth });
});
};
window.hassConnection = authProm().then(connProm);
// Start fetching some of the data that we will need.
window.hassConnection.then((conn) => {
window.hassConnection.then(({ conn }) => {
const noop = () => {};
subscribeEntities(conn, noop);
subscribeConfig(conn, noop);

View File

@@ -4,9 +4,9 @@ import createCustomPanelElement from '../util/custom-panel/create-custom-panel-e
import setCustomPanelProperties from '../util/custom-panel/set-custom-panel-properties.js';
const webComponentsSupported = (
'customElements' in window &&
'import' in document.createElement('link') &&
'content' in document.createElement('template'));
'customElements' in window
&& 'import' in document.createElement('link')
&& 'content' in document.createElement('template'));
let es5Loaded = null;

View File

@@ -9,8 +9,7 @@ export default superClass => class extends superClass {
super.ready();
this.addEventListener('hass-logout', () => this._handleLogout());
// HACK :( We don't have a way yet to trigger an update of `subscribeUser`
this.addEventListener('hass-refresh-current-user', () =>
getUser(this.hass.connection).then(user => this._updateHass({ user })));
this.addEventListener('hass-refresh-current-user', () => getUser(this.hass.connection).then(user => this._updateHass({ user })));
}
hassConnected() {
@@ -27,9 +26,16 @@ export default superClass => class extends superClass {
});
}
_handleLogout() {
this.hass.connection.close();
clearState();
document.location.href = '/';
async _handleLogout() {
try {
await this.hass.auth.revoke();
this.hass.connection.close();
clearState();
document.location.href = '/';
} catch (err) {
// eslint-disable-next-line
console.error(err);
alert('Log out failed');
}
}
};

View File

@@ -13,141 +13,135 @@ import EventsMixin from '../../mixins/events-mixin.js';
import { getState } from '../../util/ha-pref-storage.js';
import { getActiveTranslation } from '../../util/hass-translation.js';
import { fetchWithAuth } from '../../util/fetch-with-auth.js';
import hassCallApi from '../../util/hass-call-api.js';
import computeStateName from '../../common/entity/compute_state_name.js';
import { subscribePanels } from '../../data/ws-panels';
export default superClass =>
class extends EventsMixin(LocalizeMixin(superClass)) {
ready() {
super.ready();
this._handleConnProm();
export default superClass => class extends EventsMixin(LocalizeMixin(superClass)) {
ready() {
super.ready();
this._handleConnProm();
}
async _handleConnProm() {
let auth;
let conn;
try {
const result = await window.hassConnection;
auth = result.auth;
conn = result.conn;
} catch (err) {
this._error = true;
return;
}
async _handleConnProm() {
const [auth, conn] = await Promise.all([window.hassAuth, window.hassConnection]);
this.hass = Object.assign({
auth,
connection: conn,
connected: true,
states: null,
config: null,
themes: null,
panels: null,
panelUrl: this.panelUrl,
this.hass = Object.assign({
auth,
connection: conn,
connected: true,
states: null,
config: null,
themes: null,
panels: null,
panelUrl: this.panelUrl,
language: getActiveTranslation(),
// If resources are already loaded, don't discard them
resources: (this.hass && this.hass.resources) || null,
language: getActiveTranslation(),
// If resources are already loaded, don't discard them
resources: (this.hass && this.hass.resources) || null,
translationMetadata: translationMetadata,
dockedSidebar: false,
moreInfoEntityId: null,
callService: async (domain, service, serviceData = {}) => {
try {
await callService(conn, domain, service, serviceData);
translationMetadata: translationMetadata,
dockedSidebar: false,
moreInfoEntityId: null,
callService: async (domain, service, serviceData = {}) => {
try {
await callService(conn, domain, service, serviceData);
let message;
let name;
if (serviceData.entity_id && this.hass.states &&
this.hass.states[serviceData.entity_id]) {
name = computeStateName(this.hass.states[serviceData.entity_id]);
}
if (service === 'turn_on' && serviceData.entity_id) {
message = this.localize(
'ui.notification_toast.entity_turned_on',
'entity', name || serviceData.entity_id
);
} else if (service === 'turn_off' && serviceData.entity_id) {
message = this.localize(
'ui.notification_toast.entity_turned_off',
'entity', name || serviceData.entity_id
);
} else {
message = this.localize(
'ui.notification_toast.service_called',
'service', `${domain}/${service}`
);
}
this.fire('hass-notification', { message });
} catch (err) {
const message = this.localize(
'ui.notification_toast.service_call_failed',
let message;
let name;
if (serviceData.entity_id && this.hass.states
&& this.hass.states[serviceData.entity_id]) {
name = computeStateName(this.hass.states[serviceData.entity_id]);
}
if (service === 'turn_on' && serviceData.entity_id) {
message = this.localize(
'ui.notification_toast.entity_turned_on',
'entity', name || serviceData.entity_id
);
} else if (service === 'turn_off' && serviceData.entity_id) {
message = this.localize(
'ui.notification_toast.entity_turned_off',
'entity', name || serviceData.entity_id
);
} else {
message = this.localize(
'ui.notification_toast.service_called',
'service', `${domain}/${service}`
);
this.fire('hass-notification', { message });
throw err;
}
},
callApi: async (method, path, parameters) => {
const host = window.location.protocol + '//' + window.location.host;
try {
if (auth.expired) await auth.refreshAccessToken();
} catch (err) {
if (err === ERR_INVALID_AUTH) {
// Trigger auth flow
location.reload();
// ensure further JS is not executed
await new Promise(() => {});
}
throw err;
}
return await hassCallApi(host, auth, method, path, parameters);
},
// For messages that do not get a response
sendWS: (msg) => {
// eslint-disable-next-line
if (__DEV__) console.log('Sending', msg);
conn.sendMessage(msg);
},
// For messages that expect a response
callWS: (msg) => {
/* eslint-disable no-console */
this.fire('hass-notification', { message });
} catch (err) {
const message = this.localize(
'ui.notification_toast.service_call_failed',
'service', `${domain}/${service}`
);
this.fire('hass-notification', { message });
throw err;
}
},
callApi: async (method, path, parameters) => hassCallApi(auth, method, path, parameters),
fetchWithAuth: (path, init) => fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
// For messages that do not get a response
sendWS: (msg) => {
// eslint-disable-next-line
if (__DEV__) console.log('Sending', msg);
conn.sendMessage(msg);
},
// For messages that expect a response
callWS: (msg) => {
/* eslint-disable no-console */
if (__DEV__) console.log('Sending', msg);
const resp = conn.sendMessagePromise(msg);
const resp = conn.sendMessagePromise(msg);
if (__DEV__) {
resp.then(
result => console.log('Received', result),
err => console.log('Error', err),
);
}
return resp;
},
}, getState());
if (__DEV__) {
resp.then(
result => console.log('Received', result),
err => console.log('Error', err),
);
}
return resp;
},
}, getState());
this.hassConnected();
}
this.hassConnected();
}
hassConnected() {
super.hassConnected();
hassConnected() {
super.hassConnected();
const conn = this.hass.connection;
const conn = this.hass.connection;
conn.addEventListener('ready', () => this.hassReconnected());
conn.addEventListener('disconnected', () => this.hassDisconnected());
// If we reconnect after losing connection and auth is no longer valid.
conn.addEventListener('reconnect-error', (_conn, err) => {
if (err === ERR_INVALID_AUTH) location.reload();
});
conn.addEventListener('ready', () => this.hassReconnected());
conn.addEventListener('disconnected', () => this.hassDisconnected());
// If we reconnect after losing connection and auth is no longer valid.
conn.addEventListener('reconnect-error', (_conn, err) => {
if (err === ERR_INVALID_AUTH) location.reload();
});
subscribeEntities(conn, states => this._updateHass({ states }));
subscribeConfig(conn, config => this._updateHass({ config }));
subscribeServices(conn, services => this._updateHass({ services }));
subscribePanels(conn, panels => this._updateHass({ panels }));
}
subscribeEntities(conn, states => this._updateHass({ states }));
subscribeConfig(conn, config => this._updateHass({ config }));
subscribeServices(conn, services => this._updateHass({ services }));
subscribePanels(conn, panels => this._updateHass({ panels }));
}
hassReconnected() {
super.hassReconnected();
this._updateHass({ connected: true });
}
hassReconnected() {
super.hassReconnected();
this._updateHass({ connected: true });
}
hassDisconnected() {
super.hassDisconnected();
this._updateHass({ connected: false });
}
};
hassDisconnected() {
super.hassDisconnected();
this._updateHass({ connected: false });
}
};

View File

@@ -1,23 +1,22 @@
export default superClass =>
class extends superClass {
ready() {
super.ready();
this.addEventListener('register-dialog', e => this.registerDialog(e.detail));
}
export default superClass => class extends superClass {
ready() {
super.ready();
this.addEventListener('register-dialog', e => this.registerDialog(e.detail));
}
registerDialog({ dialogShowEvent, dialogTag, dialogImport }) {
let loaded = null;
registerDialog({ dialogShowEvent, dialogTag, dialogImport }) {
let loaded = null;
this.addEventListener(dialogShowEvent, (showEv) => {
if (!loaded) {
loaded = dialogImport().then(() => {
const dialogEl = document.createElement(dialogTag);
this.shadowRoot.appendChild(dialogEl);
this.provideHass(dialogEl);
return dialogEl;
});
}
loaded.then(dialogEl => dialogEl.showDialog(showEv.detail));
});
}
};
this.addEventListener(dialogShowEvent, (showEv) => {
if (!loaded) {
loaded = dialogImport().then(() => {
const dialogEl = document.createElement(dialogTag);
this.shadowRoot.appendChild(dialogEl);
this.provideHass(dialogEl);
return dialogEl;
});
}
loaded.then(dialogEl => dialogEl.showDialog(showEv.detail));
});
}
};

View File

@@ -1,27 +1,26 @@
import LocalizeMixin from '../../mixins/localize-mixin.js';
export default superClass =>
class extends LocalizeMixin(superClass) {
hassConnected() {
super.hassConnected();
export default superClass => class extends LocalizeMixin(superClass) {
hassConnected() {
super.hassConnected();
// Need to load in advance because when disconnected, can't dynamically load code.
import(/* webpackChunkName: "ha-toast" */ '../../components/ha-toast.js');
}
}
hassReconnected() {
super.hassReconnected();
this.__discToast.opened = false;
}
hassReconnected() {
super.hassReconnected();
this.__discToast.opened = false;
}
hassDisconnected() {
super.hassDisconnected();
if (!this.__discToast) {
const el = document.createElement('ha-toast');
el.duration = 0;
el.text = this.localize('ui.notification_toast.connection_lost');
this.__discToast = el;
this.shadowRoot.appendChild(el);
}
this.__discToast.opened = true;
hassDisconnected() {
super.hassDisconnected();
if (!this.__discToast) {
const el = document.createElement('ha-toast');
el.duration = 0;
el.text = this.localize('ui.notification_toast.connection_lost');
this.__discToast = el;
this.shadowRoot.appendChild(el);
}
};
this.__discToast.opened = true;
}
};

View File

@@ -8,9 +8,13 @@ export default superClass => class extends superClass {
// Exists so all methods can safely call super method
hassConnected() {}
hassReconnected() {}
hassDisconnected() {}
panelUrlChanged(newPanelUrl) {}
hassChanged(hass, oldHass) {
this.__provideHass.forEach((el) => {
el.hass = hass;

View File

@@ -5,8 +5,8 @@ import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
import '../../layouts/home-assistant-main.js';
import '../../layouts/ha-init-page.js';
import '../home-assistant-main.js';
import '../ha-init-page.js';
import '../../resources/ha-style.js';
import registerServiceWorker from '../../util/register-service-worker.js';
@@ -51,7 +51,7 @@ class HomeAssistant extends ext(PolymerElement, [
</template>
<template is="dom-if" if="[[!showMain]]" restamp>
<ha-init-page></ha-init-page>
<ha-init-page error='[[_error]]'></ha-init-page>
</template>
`;
}
@@ -73,6 +73,10 @@ class HomeAssistant extends ext(PolymerElement, [
computed: 'computePanelUrl(routeData)',
observer: 'panelUrlChanged',
},
_error: {
type: Boolean,
value: false,
}
};
}

View File

@@ -1,22 +1,20 @@
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
export default superClass =>
class extends superClass {
ready() {
super.ready();
this.addEventListener('hass-more-info', e => this._handleMoreInfo(e));
export default superClass => class extends superClass {
ready() {
super.ready();
this.addEventListener('hass-more-info', e => this._handleMoreInfo(e));
// Load it once we are having the initial rendering done.
afterNextRender(null, () =>
import(/* webpackChunkName: "more-info-dialog" */ '../../dialogs/ha-more-info-dialog.js'));
}
// Load it once we are having the initial rendering done.
afterNextRender(null, () => import(/* webpackChunkName: "more-info-dialog" */ '../../dialogs/ha-more-info-dialog.js'));
}
async _handleMoreInfo(ev) {
if (!this.__moreInfoEl) {
this.__moreInfoEl = document.createElement('ha-more-info-dialog');
this.shadowRoot.appendChild(this.__moreInfoEl);
this.provideHass(this.__moreInfoEl);
}
this._updateHass({ moreInfoEntityId: ev.detail.entityId });
async _handleMoreInfo(ev) {
if (!this.__moreInfoEl) {
this.__moreInfoEl = document.createElement('ha-more-info-dialog');
this.shadowRoot.appendChild(this.__moreInfoEl);
this.provideHass(this.__moreInfoEl);
}
};
this._updateHass({ moreInfoEntityId: ev.detail.entityId });
}
};

View File

@@ -1,11 +1,10 @@
export default superClass =>
class extends superClass {
ready() {
super.ready();
this.registerDialog({
dialogShowEvent: 'hass-notification',
dialogTag: 'notification-manager',
dialogImport: () => import(/* webpackChunkName: "notification-manager" */ '../../managers/notification-manager.js'),
});
}
};
export default superClass => class extends superClass {
ready() {
super.ready();
this.registerDialog({
dialogShowEvent: 'hass-notification',
dialogTag: 'notification-manager',
dialogImport: () => import(/* webpackChunkName: "notification-manager" */ '../../managers/notification-manager.js'),
});
}
};

View File

@@ -1,15 +1,13 @@
import { storeState } from '../../util/ha-pref-storage.js';
export default superClass =>
class extends superClass {
ready() {
super.ready();
this.addEventListener('hass-dock-sidebar', e =>
this._handleDockSidebar(e));
}
export default superClass => class extends superClass {
ready() {
super.ready();
this.addEventListener('hass-dock-sidebar', e => this._handleDockSidebar(e));
}
_handleDockSidebar(ev) {
this._updateHass({ dockedSidebar: ev.detail.dock });
storeState(this.hass);
}
};
_handleDockSidebar(ev) {
this._updateHass({ dockedSidebar: ev.detail.dock });
storeState(this.hass);
}
};

View File

@@ -1,12 +1,8 @@
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-checkbox/paper-checkbox.js';
import '@polymer/paper-input/paper-input.js';
import '@polymer/paper-spinner/paper-spinner.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { ERR_CANNOT_CONNECT, ERR_INVALID_AUTH } from 'home-assistant-js-websocket';
import LocalizeMixin from '../mixins/localize-mixin.js';
import EventsMixin from '../mixins/events-mixin.js';
@@ -26,82 +22,26 @@ class HaInitPage extends EventsMixin(LocalizeMixin(PolymerElement)) {
<div class="layout vertical center center-center fit">
<img src="/static/icons/favicon-192x192.png" height="192">
<paper-spinner active="true"></paper-spinner>
Loading data
<paper-spinner active="[[!error]]"></paper-spinner>
<template is='dom-if' if='[[error]]'>
Unable to connect to Home Assistant.
<paper-button on-click='_retry'>Retry</paper-button>
</template>
<template is='dom-if' if='[[!error]]'>
Loading data
</template>
</div>
`;
}
ready() {
super.ready();
this.addEventListener('keydown', ev => this.passwordKeyDown(ev));
static get properties() {
return {
error: Boolean,
};
}
computeLoadingMsg(isValidating) {
return isValidating ? 'Connecting' : 'Loading data';
}
computeShowSpinner(forceShowLoading, isValidating) {
return forceShowLoading || isValidating;
}
isValidatingChanged(newVal) {
if (!newVal) {
setTimeout(() => {
if (this.$.passwordInput.inputElement.inputElement) {
this.$.passwordInput.inputElement.inputElement.focus();
}
}, 10);
}
}
passwordKeyDown(ev) {
// validate on enter
if (ev.keyCode === 13) {
this.validatePassword();
ev.preventDefault();
// clear error after we start typing again
} else if (this.errorMessage) {
this.errorMessage = '';
}
}
validatePassword() {
var auth = this.password;
this.$.hideKeyboardOnFocus.focus();
const connProm = window.createHassConnection(auth);
this.fire('try-connection', { connProm });
if (this.$.rememberLogin.checked) {
connProm.then(function () {
localStorage.authToken = auth;
});
}
}
handleConnectionPromiseChanged(newVal) {
if (!newVal) return;
var el = this;
this.isValidating = true;
this.connectionPromise.then(
function () {
el.isValidating = false;
el.password = '';
},
function (errCode) {
el.isValidating = false;
if (errCode === ERR_CANNOT_CONNECT) {
el.errorMessage = 'Unable to connect';
} else if (errCode === ERR_INVALID_AUTH) {
el.errorMessage = 'Invalid password';
} else {
el.errorMessage = 'Unknown error: ' + errCode;
}
}
);
_retry() {
location.reload();
}
}

View File

@@ -14,6 +14,7 @@ class HassSubpage extends PolymerElement {
<app-toolbar>
<paper-icon-button icon="hass:arrow-left" on-click="_backTapped"></paper-icon-button>
<div main-title="">[[header]]</div>
<slot name="toolbar-icon"></slot>
</app-toolbar>
</app-header>

View File

@@ -284,8 +284,8 @@ class PartialCards extends EventsMixin(NavigateMixin(PolymerElement)) {
}
isView(currentView, defaultView) {
return (currentView || defaultView) &&
this.hass.states[currentView || DEFAULT_VIEW_ENTITY_ID];
return (currentView || defaultView)
&& this.hass.states[currentView || DEFAULT_VIEW_ENTITY_ID];
}
_defaultViewFilter(hass, entityId) {

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