Compare commits

...

44 Commits

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

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

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

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

* Fix init page

* Lint

* Fix tests

* Update gitignore

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

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

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

* Authorize/Onboarding pimp

* More tweaks

* Comments
2018-08-27 22:10:15 +02:00
Alok Saboo
772208ba22 Fixed typo (#1604) 2018-08-27 21:50:14 +02:00
Paulus Schoutsen
e46a1be5d7 Center svg in markdown 2018-08-26 22:00:57 +02:00
Paulus Schoutsen
d295a9d0e4 Version bump to 20180826.0 2018-08-26 21:24:03 +02:00
Paulus Schoutsen
f0bf34073b Update translations 2018-08-26 21:23:25 +02:00
Jason Hu
73098d106d disableXssFilter ==> allowSvg (#1600)
* disableXssFilter ==> allowSvg

* Move allowSvg patch to _render()

* Add comment
2018-08-26 11:50:37 -07:00
Jason Hu
c8ea4cd85e Disable XSS filter in flow step desc markdown (#1599) 2018-08-26 12:44:50 +02:00
Paulus Schoutsen
f64ddf46e2 Update frontend to 20180825.0 2018-08-25 11:13:29 +02:00
Paulus Schoutsen
5e6e3dd793 Update translations 2018-08-25 11:13:14 +02:00
Paulus Schoutsen
13ff59ec89 Fix hass icons 2018-08-24 21:59:53 +02:00
Jason Hu
7cc3fc728b Add ha-mfa-modules-card and setup flow (#1590)
* Add ha-mfa-modules-card and setup flow

* Add hass-refresh-current-user event

* Address code review comment
2018-08-24 19:35:33 +02:00
Glenn Waters
ec79e12bf3 Fix for missing 'Disarm' button (#1549)
* Fix for missing 'Disarm' button

Disarm button does not show when in `arming` and `disarming` state.

* Update more-info-alarm_control_panel.js

Disarm button visible on arming state.
2018-08-24 16:57:04 +02:00
Paulus Schoutsen
d38b989c02 Version bump to 20180824.0 2018-08-24 15:50:17 +02:00
Paulus Schoutsen
f9a629bf8d Update translations 2018-08-24 15:49:58 +02:00
Jerad Meisner
02edbce460 Notification drawer (#1536)
* Notification drawer MVP concept

* Localization

* Don't use events.
2018-08-24 15:46:58 +02:00
Jerad Meisner
b9f84d012f Add default_zoom option to map card. (#1592) 2018-08-24 10:27:36 +02:00
Jerad Meisner
421478bb49 Hide toolbar on scroll. (#1595) 2018-08-24 10:25:58 +02:00
Jerad Meisner
cde106bd77 Fix custom icon colors in entities and glance cards. (#1587)
* Fix custom icon colors in entities and glance cards.

* Use ha-icon directly in state-badge.
2018-08-23 22:24:21 +02:00
John Arild Berentsen
144f00e2cc reset entity properties on 404 (#1591) 2018-08-23 22:23:00 +02:00
squidwardy
74185f0beb Add support for JS modules in custom panels. (#1588)
* Added support for JS modules in custom panels.

* Update load-custom-panel.js

* Corrected syntax

* Force push

* Fixed EOL

* Delete index.html

* Fixed tempA href missing module_url name
2018-08-23 11:14:47 +02:00
Paulus Schoutsen
09f238162e Cleanup (#1575) 2018-08-21 08:59:54 +02:00
Jerad Meisner
13ece650bc Fix input_number flexbox. (#1586) 2018-08-21 08:59:39 +02:00
121 changed files with 4524 additions and 1362 deletions

View File

@@ -18,10 +18,9 @@
},
"globals": {
"__DEV__": false,
"__DEMO__": false,
"__BUILD__": false,
"__VERSION__": false,
"__PUBLIC_PATH__": false,
"__STATIC_PATH__": false,
"Polymer": true,
"webkitSpeechRecognition": false,
"ResizeObserver": false

View File

@@ -140,7 +140,9 @@ const CONFIGS = [
- entity: group.kitchen
icon: mdi:home-assistant
- lock.kitchen_door
- light.bed_light
- entity: light.bed_light
icon: mdi:alarm-light
name: Bed Light Custom Icon
- climate.ecobee
- input_number.noise_allowance
title: Random group

View File

@@ -105,7 +105,8 @@ const CONFIGS = [
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- entity: light.kitchen_lights
icon: mdi:alarm-light
- lock.kitchen_door
- light.ceiling_lights
`

View File

@@ -1,8 +1,36 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import getEntity from '../data/entity.js';
import provideHass from '../data/provide_hass.js';
import '../components/demo-cards.js';
const ENTITIES = [
getEntity('device_tracker', 'demo_paulus', 'not_home', {
source_type: 'gps',
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: 'Paulus'
}),
getEntity('device_tracker', 'demo_home_boy', 'home', {
source_type: 'gps',
latitude: 32.87334,
longitude: 117.22745,
gps_accuracy: 20,
battery: 53,
friendly_name: 'Home Boy'
}),
getEntity('zone', 'home', 'zoning', {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: 'Home',
icon: 'mdi:home'
})
];
const CONFIGS = [
{
heading: 'Without title',
@@ -10,6 +38,7 @@ const CONFIGS = [
- type: map
entities:
- entity: device_tracker.demo_paulus
- device_tracker.demo_home_boy
- zone.home
`
},
@@ -33,12 +62,70 @@ const CONFIGS = [
aspect_ratio: 50%
`
},
{
heading: 'Default Zoom',
config: `
- type: map
default_zoom: 12
entities:
- entity: device_tracker.demo_paulus
- zone.home
`
},
{
heading: 'Default Zoom too High',
config: `
- type: map
default_zoom: 20
entities:
- entity: device_tracker.demo_paulus
- zone.home
`
},
{
heading: 'Single Marker',
config: `
- type: map
entities:
- device_tracker.demo_paulus
`
},
{
heading: 'Single Marker Default Zoom',
config: `
- type: map
default_zoom: 8
entities:
- device_tracker.demo_paulus
`
},
{
heading: 'No Entities',
config: `
- type: map
entities:
- light.bed_light
`
},
{
heading: 'No Entities, Default Zoom',
config: `
- type: map
default_zoom: 8
entities:
- light.bed_light
`
},
];
class DemoMap extends PolymerElement {
static get template() {
return html`
<demo-cards configs="[[_configs]]"></demo-cards>
<demo-cards
id="demos"
hass="[[hass]]"
configs="[[_configs]]"
></demo-cards>
`;
}
@@ -47,9 +134,16 @@ class DemoMap extends PolymerElement {
_configs: {
type: Object,
value: CONFIGS
}
},
hass: Object
};
}
ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define('demo-hui-map-card', DemoMap);

View File

@@ -22,7 +22,10 @@ const TRANSLATION_FRAGMENTS = [
'history',
'logbook',
'mailbox',
'profile',
'shopping-list',
'page-authorize',
'page-onboarding',
];
const tasks = [];

1
hassio/.gitignore vendored
View File

@@ -1,2 +1 @@
build-es5/*
hassio-icons.html

View File

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

View File

@@ -1,38 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hass.io</title>
<meta name='viewport' content='width=device-width, user-scalable=no'>
<style>
body {
height: 100vh;
margin: 0;
padding: 0;
}
</style>
<!--EXTRA_SCRIPTS-->
</head>
<body>
<hassio-app></hassio-app>
<script>
function addScript(src) {
var e = document.createElement('script');
e.src = src;
document.write(e.outerHTML);
}
var webComponentsSupported = (
'customElements' in window &&
'import' in document.createElement('link') &&
'content' in document.createElement('template'));
if (!webComponentsSupported) {
addScript('/static/webcomponents-lite.js');
}
</script>
<!--
Disabled while we make Home Assistant able to serve the right files.
<script src="./app.js"></script>
-->
<link rel='import' href='./hassio-app.html'>
</body>
</html>

View File

@@ -7,22 +7,8 @@ set -e
cd "$(dirname "$0")/.."
OUTPUT_DIR=build
OUTPUT_DIR_ES5=build-es5
rm -rf $OUTPUT_DIR $OUTPUT_DIR_ES5
rm -rf $OUTPUT_DIR
node script/gen-icons.js
# LEGACY BUILD
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.legacy.config.js
node script/gen-index-html.js
# Temporarily re-create old HTML import
echo "<script>" > $OUTPUT_DIR_ES5/hassio-app.html
cat $OUTPUT_DIR_ES5/app.js >> $OUTPUT_DIR_ES5/hassio-app.html
cat $OUTPUT_DIR_ES5/chunk.*.js >> $OUTPUT_DIR_ES5/hassio-app.html
echo "</script>" >> $OUTPUT_DIR_ES5/hassio-app.html
rm $OUTPUT_DIR_ES5/app.js*
rm $OUTPUT_DIR_ES5/chunk.*
# NEW BUILD
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const config = require('../config.js');
let index = fs.readFileSync('index.html', 'utf-8');
const toReplace = [
[
'<!--EXTRA_SCRIPTS-->',
"<script src='/frontend_es5/custom-elements-es5-adapter.js'></script>"
],
];
for (item of toReplace) {
index = index.replace(item[0], item[1]);
}
fs.writeFileSync(`${config.buildDirLegacy}/index.html`, index);

View File

@@ -1,51 +1,14 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const config = require('./config.js');
const version = fs.readFileSync('../setup.py', 'utf8').match(/\d{8}[^']*/);
if (!version) {
throw Error('Version not found');
}
const VERSION = version[0];
const isProdBuild = process.env.NODE_ENV === 'production'
const chunkFilename = isProdBuild ?
'chunk.[chunkhash].js' : '[name].chunk.js';
const plugins = [
new webpack.DefinePlugin({
__DEV__: JSON.stringify(!isProdBuild),
__VERSION__: JSON.stringify(VERSION),
})
];
if (isProdBuild) {
plugins.push(new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
}
}));
plugins.push(new CompressionPlugin({
cache: true,
exclude: [
/\.js\.map$/,
/\.LICENSE$/,
/\.py$/,
/\.txt$/,
]
}));
}
module.exports = {
mode: isProdBuild ? 'production' : 'development',
// Disabled in prod while we make Home Assistant able to serve the right files.
// Was source-map
devtool: isProdBuild ? 'none' : 'inline-source-map',
devtool: isProdBuild ? 'source-map' : 'inline-source-map',
entry: {
entrypoint: './src/entrypoint.js',
},
@@ -78,10 +41,28 @@ module.exports = {
]
},
plugins,
plugins: [
isProdBuild && new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
}
}),
isProdBuild && new CompressionPlugin({
cache: true,
exclude: [
/\.js\.map$/,
/\.LICENSE$/,
/\.py$/,
/\.txt$/,
]
}),
].filter(Boolean),
output: {
filename: '[name].js',
chunkFilename: chunkFilename,
chunkFilename,
path: config.buildDir,
publicPath: `${config.publicPath}/`,
}

View File

@@ -1,78 +0,0 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const config = require('./config.js');
const version = fs.readFileSync('../setup.py', 'utf8').match(/\d{8}[^']*/);
if (!version) {
throw Error('Version not found');
}
const VERSION = version[0];
const isProdBuild = process.env.NODE_ENV === 'production'
const chunkFilename = isProdBuild ?
'chunk.[chunkhash].js' : '[name].chunk.js';
const plugins = [
new webpack.DefinePlugin({
__DEV__: JSON.stringify(!isProdBuild),
__VERSION__: JSON.stringify(VERSION),
})
];
if (isProdBuild) {
plugins.push(new UglifyJsPlugin({
extractComments: true,
sourceMap: true,
uglifyOptions: {
// Disabling because it broke output
mangle: false,
}
}));
}
module.exports = {
mode: isProdBuild ? 'production' : 'development',
// Disabled in prod while we make Home Assistant able to serve the right files.
// Was source-map
devtool: isProdBuild ? 'none' : 'inline-source-map',
entry: {
app: './src/hassio-app.js',
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[require('babel-preset-env').default, { modules: false }]
],
plugins: [
// Only support the syntax, Webpack will handle it.
"syntax-dynamic-import",
],
},
},
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
exportAsEs6Default: true,
}
}
}
]
},
plugins,
output: {
filename: '[name].js',
chunkFilename: chunkFilename,
path: config.buildDirLegacy,
publicPath: `${config.publicPathLegacy}/`,
}
};

View File

@@ -68,7 +68,7 @@
"es6-object-assign": "^1.1.0",
"eslint-import-resolver-webpack": "^0.10.0",
"fecha": "^2.3.3",
"home-assistant-js-websocket": "^2.1.0",
"home-assistant-js-websocket": "^3.0.0",
"intl-messageformat": "^2.2.0",
"js-yaml": "^3.12.0",
"leaflet": "^1.3.1",
@@ -117,6 +117,7 @@
"polymer-analyzer": "^3.0.1",
"polymer-bundler": "^4.0.1",
"polymer-cli": "^1.7.4",
"raw-loader": "^0.5.1",
"reify": "^0.16.2",
"require-dir": "^1.0.0",
"sinon": "^6.0.0",

File diff suppressed because one or more lines are too long

View File

@@ -1,219 +0,0 @@
/**
@license @nocompile
Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function(){/*
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';var p,q="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this,aa="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};function da(){da=function(){};q.Symbol||(q.Symbol=ea)}var ea=function(){var a=0;return function(b){return"jscomp_symbol_"+(b||"")+a++}}();
function fa(){da();var a=q.Symbol.iterator;a||(a=q.Symbol.iterator=q.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&aa(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return ha(this)}});fa=function(){}}function ha(a){var b=0;return ia(function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}})}function ia(a){fa();a={next:a};a[q.Symbol.iterator]=function(){return this};return a}function la(a){fa();var b=a[Symbol.iterator];return b?b.call(a):ha(a)}
function ma(a){for(var b,c=[];!(b=a.next()).done;)c.push(b.value);return c}
(function(){if(!function(){var a=document.createEvent("Event");a.initEvent("foo",!0,!0);a.preventDefault();return a.defaultPrevented}()){var a=Event.prototype.preventDefault;Event.prototype.preventDefault=function(){this.cancelable&&(a.call(this),Object.defineProperty(this,"defaultPrevented",{get:function(){return!0},configurable:!0}))}}var b=/Trident/.test(navigator.userAgent);if(!window.CustomEvent||b&&"function"!==typeof window.CustomEvent)window.CustomEvent=function(a,b){b=b||{};var c=document.createEvent("CustomEvent");
c.initCustomEvent(a,!!b.bubbles,!!b.cancelable,b.detail);return c},window.CustomEvent.prototype=window.Event.prototype;if(!window.Event||b&&"function"!==typeof window.Event){var c=window.Event;window.Event=function(a,b){b=b||{};var c=document.createEvent("Event");c.initEvent(a,!!b.bubbles,!!b.cancelable);return c};if(c)for(var d in c)window.Event[d]=c[d];window.Event.prototype=c.prototype}if(!window.MouseEvent||b&&"function"!==typeof window.MouseEvent){b=window.MouseEvent;window.MouseEvent=function(a,
b){b=b||{};var c=document.createEvent("MouseEvent");c.initMouseEvent(a,!!b.bubbles,!!b.cancelable,b.view||window,b.detail,b.screenX,b.screenY,b.clientX,b.clientY,b.ctrlKey,b.altKey,b.shiftKey,b.metaKey,b.button,b.relatedTarget);return c};if(b)for(d in b)window.MouseEvent[d]=b[d];window.MouseEvent.prototype=b.prototype}Array.from||(Array.from=function(a){return[].slice.call(a)});Object.assign||(Object.assign=function(a,b){for(var c=[].slice.call(arguments,1),d=0,e;d<c.length;d++)if(e=c[d])for(var f=
a,m=e,n=Object.getOwnPropertyNames(m),t=0;t<n.length;t++)e=n[t],f[e]=m[e];return a})})(window.WebComponents);(function(){function a(){}function b(a,b){if(!a.childNodes.length)return[];switch(a.nodeType){case Node.DOCUMENT_NODE:return t.call(a,b);case Node.DOCUMENT_FRAGMENT_NODE:return B.call(a,b);default:return n.call(a,b)}}var c="undefined"===typeof HTMLTemplateElement,d=!(document.createDocumentFragment().cloneNode()instanceof DocumentFragment),e=!1;/Trident/.test(navigator.userAgent)&&function(){function a(a,b){if(a instanceof DocumentFragment)for(var d;d=a.firstChild;)c.call(this,d,b);else c.call(this,
a,b);return a}e=!0;var b=Node.prototype.cloneNode;Node.prototype.cloneNode=function(a){a=b.call(this,a);this instanceof DocumentFragment&&(a.__proto__=DocumentFragment.prototype);return a};DocumentFragment.prototype.querySelectorAll=HTMLElement.prototype.querySelectorAll;DocumentFragment.prototype.querySelector=HTMLElement.prototype.querySelector;Object.defineProperties(DocumentFragment.prototype,{nodeType:{get:function(){return Node.DOCUMENT_FRAGMENT_NODE},configurable:!0},localName:{get:function(){},
configurable:!0},nodeName:{get:function(){return"#document-fragment"},configurable:!0}});var c=Node.prototype.insertBefore;Node.prototype.insertBefore=a;var d=Node.prototype.appendChild;Node.prototype.appendChild=function(b){b instanceof DocumentFragment?a.call(this,b,null):d.call(this,b);return b};var f=Node.prototype.removeChild,h=Node.prototype.replaceChild;Node.prototype.replaceChild=function(b,c){b instanceof DocumentFragment?(a.call(this,b,c),f.call(this,c)):h.call(this,b,c);return c};Document.prototype.createDocumentFragment=
function(){var a=this.createElement("df");a.__proto__=DocumentFragment.prototype;return a};var g=Document.prototype.importNode;Document.prototype.importNode=function(a,b){b=g.call(this,a,b||!1);a instanceof DocumentFragment&&(b.__proto__=DocumentFragment.prototype);return b}}();var f=Node.prototype.cloneNode,h=Document.prototype.createElement,g=Document.prototype.importNode,k=Node.prototype.removeChild,l=Node.prototype.appendChild,m=Node.prototype.replaceChild,n=Element.prototype.querySelectorAll,
t=Document.prototype.querySelectorAll,B=DocumentFragment.prototype.querySelectorAll,Z=function(){if(!c){var a=document.createElement("template"),b=document.createElement("template");b.content.appendChild(document.createElement("div"));a.content.appendChild(b);a=a.cloneNode(!0);return 0===a.content.childNodes.length||0===a.content.firstChild.content.childNodes.length||d}}();if(c){var P=document.implementation.createHTMLDocument("template"),Ka=!0,ba=document.createElement("style");ba.textContent="template{display:none;}";
var La=document.head;La.insertBefore(ba,La.firstElementChild);a.prototype=Object.create(HTMLElement.prototype);var C=!document.createElement("div").hasOwnProperty("innerHTML");a.H=function(b){if(!b.content){b.content=P.createDocumentFragment();for(var c;c=b.firstChild;)l.call(b.content,c);if(C)b.__proto__=a.prototype;else if(b.cloneNode=function(b){return a.ga(this,b)},Ka)try{va(b),ja(b)}catch(qh){Ka=!1}a.M(b.content)}};var va=function(b){Object.defineProperty(b,"innerHTML",{get:function(){return nb(this)},
set:function(b){P.body.innerHTML=b;for(a.M(P);this.content.firstChild;)k.call(this.content,this.content.firstChild);for(;P.body.firstChild;)l.call(this.content,P.body.firstChild)},configurable:!0})},ja=function(a){Object.defineProperty(a,"outerHTML",{get:function(){return"<template>"+this.innerHTML+"</template>"},set:function(a){if(this.parentNode){P.body.innerHTML=a;for(a=this.ownerDocument.createDocumentFragment();P.body.firstChild;)l.call(a,P.body.firstChild);m.call(this.parentNode,a,this)}else throw Error("Failed to set the 'outerHTML' property on 'Element': This element has no parent node.");
},configurable:!0})};va(a.prototype);ja(a.prototype);a.M=function(c){c=b(c,"template");for(var d=0,e=c.length,f;d<e&&(f=c[d]);d++)a.H(f)};document.addEventListener("DOMContentLoaded",function(){a.M(document)});Document.prototype.createElement=function(){var b=h.apply(this,arguments);"template"===b.localName&&a.H(b);return b};var wa=/[&\u00A0"]/g,ca=/[&\u00A0<>]/g,ka=function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";case "\u00a0":return"&nbsp;"}};
ba=function(a){for(var b={},c=0;c<a.length;c++)b[a[c]]=!0;return b};var gf=ba("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")),xa=ba("style script xmp iframe noembed noframes plaintext noscript".split(" ")),nb=function(a,b){"template"===a.localName&&(a=a.content);for(var c="",d=b?b(a):a.childNodes,e=0,f=d.length,h;e<f&&(h=d[e]);e++){a:{var g=h;var k=a;var l=b;switch(g.nodeType){case Node.ELEMENT_NODE:for(var m=g.localName,n="<"+m,ya=g.attributes,Ma=
0;k=ya[Ma];Ma++)n+=" "+k.name+'="'+k.value.replace(wa,ka)+'"';n+=">";g=gf[m]?n:n+nb(g,l)+"</"+m+">";break a;case Node.TEXT_NODE:g=g.data;g=k&&xa[k.localName]?g:g.replace(ca,ka);break a;case Node.COMMENT_NODE:g="\x3c!--"+g.data+"--\x3e";break a;default:throw window.console.error(g),Error("not implemented");}}c+=g}return c}}if(c||Z){a.ga=function(a,b){var c=f.call(a,!1);this.H&&this.H(c);b&&(l.call(c.content,f.call(a.content,!0)),ya(c.content,a.content));return c};var ya=function(c,d){if(d.querySelectorAll&&
(d=b(d,"template"),0!==d.length)){c=b(c,"template");for(var e=0,f=c.length,h,g;e<f;e++)g=d[e],h=c[e],a&&a.H&&a.H(g),m.call(h.parentNode,Ma.call(g,!0),h)}},Ma=Node.prototype.cloneNode=function(b){if(!e&&d&&this instanceof DocumentFragment)if(b)var c=hf.call(this.ownerDocument,this,!0);else return this.ownerDocument.createDocumentFragment();else c=this.nodeType===Node.ELEMENT_NODE&&"template"===this.localName?a.ga(this,b):f.call(this,b);b&&ya(c,this);return c},hf=Document.prototype.importNode=function(c,
d){d=d||!1;if("template"===c.localName)return a.ga(c,d);var e=g.call(this,c,d);if(d){ya(e,c);c=b(e,'script:not([type]),script[type="application/javascript"],script[type="text/javascript"]');for(var f,k=0;k<c.length;k++){f=c[k];d=h.call(document,"script");d.textContent=f.textContent;for(var l=f.attributes,ka=0,xa;ka<l.length;ka++)xa=l[ka],d.setAttribute(xa.name,xa.value);m.call(f.parentNode,d,f)}}return e}}c&&(window.HTMLTemplateElement=a)})();var na=Array.isArray?Array.isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)};var oa=0,pa,qa="undefined"!==typeof window?window:void 0,ra=qa||{},sa=ra.MutationObserver||ra.WebKitMutationObserver,ta="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel;function ua(){return"undefined"!==typeof pa?function(){pa(za)}:Aa()}function Ba(){var a=0,b=new sa(za),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}
function Ca(){var a=new MessageChannel;a.port1.onmessage=za;return function(){return a.port2.postMessage(0)}}function Aa(){var a=setTimeout;return function(){return a(za,1)}}var Da=Array(1E3);function za(){for(var a=0;a<oa;a+=2)(0,Da[a])(Da[a+1]),Da[a]=void 0,Da[a+1]=void 0;oa=0}var Ea,Fa;
if("undefined"===typeof self&&"undefined"!==typeof process&&"[object process]"==={}.toString.call(process))Fa=function(){return process.sb(za)};else{var Ga;if(sa)Ga=Ba();else{var Ha;if(ta)Ha=Ca();else{var Ia;if(void 0===qa&&"function"===typeof require)try{var Ja=require("vertx");pa=Ja.ub||Ja.tb;Ia=ua()}catch(a){Ia=Aa()}else Ia=Aa();Ha=Ia}Ga=Ha}Fa=Ga}Ea=Fa;function Na(a,b){Da[oa]=a;Da[oa+1]=b;oa+=2;2===oa&&Ea()};function Oa(a,b){var c=this,d=new this.constructor(Pa);void 0===d[Qa]&&Ra(d);var e=c.h;if(e){var f=arguments[e-1];Na(function(){return Sa(e,d,f,c.f)})}else Ta(c,d,a,b);return d};function Ua(a){if(a&&"object"===typeof a&&a.constructor===this)return a;var b=new this(Pa);Va(b,a);return b};var Qa=Math.random().toString(36).substring(16);function Pa(){}var Xa=new Wa;function Ya(a){try{return a.then}catch(b){return Xa.error=b,Xa}}function Za(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function $a(a,b,c){Na(function(a){var d=!1,f=Za(c,b,function(c){d||(d=!0,b!==c?Va(a,c):r(a,c))},function(b){d||(d=!0,u(a,b))});!d&&f&&(d=!0,u(a,f))},a)}function ab(a,b){1===b.h?r(a,b.f):2===b.h?u(a,b.f):Ta(b,void 0,function(b){return Va(a,b)},function(b){return u(a,b)})}
function bb(a,b,c){b.constructor===a.constructor&&c===Oa&&b.constructor.resolve===Ua?ab(a,b):c===Xa?(u(a,Xa.error),Xa.error=null):void 0===c?r(a,b):"function"===typeof c?$a(a,b,c):r(a,b)}function Va(a,b){if(a===b)u(a,new TypeError("You cannot resolve a promise with itself"));else{var c=typeof b;null===b||"object"!==c&&"function"!==c?r(a,b):bb(a,b,Ya(b))}}function cb(a){a.qa&&a.qa(a.f);db(a)}function r(a,b){void 0===a.h&&(a.f=b,a.h=1,0!==a.L.length&&Na(db,a))}
function u(a,b){void 0===a.h&&(a.h=2,a.f=b,Na(cb,a))}function Ta(a,b,c,d){var e=a.L,f=e.length;a.qa=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.h&&Na(db,a)}function db(a){var b=a.L,c=a.h;if(0!==b.length){for(var d,e,f=a.f,h=0;h<b.length;h+=3)d=b[h],e=b[h+c],d?Sa(c,d,e,f):e(f);a.L.length=0}}function Wa(){this.error=null}var eb=new Wa;
function Sa(a,b,c,d){var e="function"===typeof c;if(e){try{var f=c(d)}catch(l){eb.error=l,f=eb}if(f===eb){var h=!0;var g=f.error;f.error=null}else var k=!0;if(b===f){u(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,k=!0;void 0===b.h&&(e&&k?Va(b,f):h?u(b,g):1===a?r(b,f):2===a&&u(b,f))}function fb(a,b){try{b(function(b){Va(a,b)},function(b){u(a,b)})}catch(c){u(a,c)}}var gb=0;function Ra(a){a[Qa]=gb++;a.h=void 0;a.f=void 0;a.L=[]};function hb(a,b){this.Ha=a;this.D=new a(Pa);this.D[Qa]||Ra(this.D);if(na(b))if(this.U=this.length=b.length,this.f=Array(this.length),0===this.length)r(this.D,this.f);else{this.length=this.length||0;for(a=0;void 0===this.h&&a<b.length;a++)ib(this,b[a],a);0===this.U&&r(this.D,this.f)}else u(this.D,Error("Array Methods must be provided an Array"))}
function ib(a,b,c){var d=a.Ha,e=d.resolve;e===Ua?(e=Ya(b),e===Oa&&void 0!==b.h?jb(a,b.h,c,b.f):"function"!==typeof e?(a.U--,a.f[c]=b):d===v?(d=new d(Pa),bb(d,b,e),kb(a,d,c)):kb(a,new d(function(a){return a(b)}),c)):kb(a,e(b),c)}function jb(a,b,c,d){var e=a.D;void 0===e.h&&(a.U--,2===b?u(e,d):a.f[c]=d);0===a.U&&r(e,a.f)}function kb(a,b,c){Ta(b,void 0,function(b){return jb(a,1,c,b)},function(b){return jb(a,2,c,b)})};function lb(a){return(new hb(this,a)).D};function mb(a){var b=this;return na(a)?new b(function(c,d){for(var e=a.length,f=0;f<e;f++)b.resolve(a[f]).then(c,d)}):new b(function(a,b){return b(new TypeError("You must pass an array to race."))})};function ob(a){var b=new this(Pa);u(b,a);return b};function v(a){this[Qa]=gb++;this.f=this.h=void 0;this.L=[];if(Pa!==a){if("function"!==typeof a)throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(this instanceof v)fb(this,a);else throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");}}v.prototype={constructor:v,then:Oa,a:function(a){return this.then(null,a)}};/*
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
window.Promise||(window.Promise=v,v.prototype["catch"]=v.prototype.a,v.prototype.then=v.prototype.then,v.all=lb,v.race=mb,v.resolve=Ua,v.reject=ob);(function(a){function b(a,b){if("function"===typeof window.CustomEvent)return new CustomEvent(a,b);var c=document.createEvent("CustomEvent");c.initCustomEvent(a,!!b.bubbles,!!b.cancelable,b.detail);return c}function c(a){if(B)return a.ownerDocument!==document?a.ownerDocument:null;var b=a.__importDoc;if(!b&&a.parentNode){b=a.parentNode;if("function"===typeof b.closest)b=b.closest("link[rel=import]");else for(;!g(b)&&(b=b.parentNode););a.__importDoc=b}return b}function d(a){var b=m(document,"link[rel=import]:not([import-dependency])"),
c=b.length;c?n(b,function(b){return h(b,function(){0===--c&&a()})}):a()}function e(a){function b(){"loading"!==document.readyState&&document.body&&(document.removeEventListener("readystatechange",b),a())}document.addEventListener("readystatechange",b);b()}function f(a){e(function(){return d(function(){return a&&a()})})}function h(a,b){if(a.__loaded)b&&b();else if("script"===a.localName&&!a.src||"style"===a.localName&&!a.firstChild)a.__loaded=!0,b&&b();else{var c=function(d){a.removeEventListener(d.type,
c);a.__loaded=!0;b&&b()};a.addEventListener("load",c);ja&&"style"===a.localName||a.addEventListener("error",c)}}function g(a){return a.nodeType===Node.ELEMENT_NODE&&"link"===a.localName&&"import"===a.rel}function k(){var a=this;this.a={};this.b=0;this.c=new MutationObserver(function(b){return a.Ua(b)});this.c.observe(document.head,{childList:!0,subtree:!0});this.loadImports(document)}function l(a){n(m(a,"template"),function(a){n(m(a.content,'script:not([type]),script[type="application/javascript"],script[type="text/javascript"]'),
function(a){var b=document.createElement("script");n(a.attributes,function(a){return b.setAttribute(a.name,a.value)});b.textContent=a.textContent;a.parentNode.replaceChild(b,a)});l(a.content)})}function m(a,b){return a.childNodes.length?a.querySelectorAll(b):Z}function n(a,b,c){var d=a?a.length:0,e=c?-1:1;for(c=c?d-1:0;c<d&&0<=c;c+=e)b(a[c],c)}var t=document.createElement("link"),B="import"in t,Z=t.querySelectorAll("*"),P=null;!1==="currentScript"in document&&Object.defineProperty(document,"currentScript",
{get:function(){return P||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null)},configurable:!0});var Ka=/(url\()([^)]*)(\))/g,ba=/(@import[\s]+(?!url\())([^;]*)(;)/g,La=/(<link[^>]*)(rel=['|"]?stylesheet['|"]?[^>]*>)/g,C={Oa:function(a,b){a.href&&a.setAttribute("href",C.aa(a.getAttribute("href"),b));a.src&&a.setAttribute("src",C.aa(a.getAttribute("src"),b));if("style"===a.localName){var c=C.xa(a.textContent,b,Ka);a.textContent=C.xa(c,b,ba)}},xa:function(a,b,c){return a.replace(c,
function(a,c,d,e){a=d.replace(/["']/g,"");b&&(a=C.aa(a,b));return c+"'"+a+"'"+e})},aa:function(a,b){if(void 0===C.fa){C.fa=!1;try{var c=new URL("b","http://a");c.pathname="c%20d";C.fa="http://a/c%20d"===c.href}catch(nb){}}if(C.fa)return(new URL(a,b)).href;c=C.Ea;c||(c=document.implementation.createHTMLDocument("temp"),C.Ea=c,c.oa=c.createElement("base"),c.head.appendChild(c.oa),c.na=c.createElement("a"));c.oa.href=b;c.na.href=a;return c.na.href||a}},va={async:!0,load:function(a,b,c){if(a)if(a.match(/^data:/)){a=
a.split(",");var d=a[1];d=-1<a[0].indexOf(";base64")?atob(d):decodeURIComponent(d);b(d)}else{var e=new XMLHttpRequest;e.open("GET",a,va.async);e.onload=function(){var a=e.responseURL||e.getResponseHeader("Location");a&&0===a.indexOf("/")&&(a=(location.origin||location.protocol+"//"+location.host)+a);var d=e.response||e.responseText;304===e.status||0===e.status||200<=e.status&&300>e.status?b(d,a):c(d)};e.send()}else c("error: href must be specified")}},ja=/Trident/.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent);
k.prototype.loadImports=function(a){var b=this;a=m(a,"link[rel=import]");n(a,function(a){return b.s(a)})};k.prototype.s=function(a){var b=this,c=a.href;if(void 0!==this.a[c]){var d=this.a[c];d&&d.__loaded&&(a.__import=d,this.i(a))}else this.b++,this.a[c]="pending",va.load(c,function(a,d){a=b.Va(a,d||c);b.a[c]=a;b.b--;b.loadImports(a);b.N()},function(){b.a[c]=null;b.b--;b.N()})};k.prototype.Va=function(a,b){if(!a)return document.createDocumentFragment();ja&&(a=a.replace(La,function(a,b,c){return-1===
a.indexOf("type=")?b+" type=import-disable "+c:a}));var c=document.createElement("template");c.innerHTML=a;if(c.content)a=c.content,l(a);else for(a=document.createDocumentFragment();c.firstChild;)a.appendChild(c.firstChild);if(c=a.querySelector("base"))b=C.aa(c.getAttribute("href"),b),c.removeAttribute("href");c=m(a,'link[rel=import],link[rel=stylesheet][href][type=import-disable],style:not([type]),link[rel=stylesheet][href]:not([type]),script:not([type]),script[type="application/javascript"],script[type="text/javascript"]');
var d=0;n(c,function(a){h(a);C.Oa(a,b);a.setAttribute("import-dependency","");"script"===a.localName&&!a.src&&a.textContent&&(a.setAttribute("src","data:text/javascript;charset=utf-8,"+encodeURIComponent(a.textContent+("\n//# sourceURL="+b+(d?"-"+d:"")+".js\n"))),a.textContent="",d++)});return a};k.prototype.N=function(){var a=this;if(!this.b){this.c.disconnect();this.flatten(document);var b=!1,c=!1,d=function(){c&&b&&(a.loadImports(document),a.b||(a.c.observe(document.head,{childList:!0,subtree:!0}),
a.Sa()))};this.Xa(function(){c=!0;d()});this.Wa(function(){b=!0;d()})}};k.prototype.flatten=function(a){var b=this;a=m(a,"link[rel=import]");n(a,function(a){var c=b.a[a.href];(a.__import=c)&&c.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&(b.a[a.href]=a,a.readyState="loading",a.__import=a,b.flatten(c),a.appendChild(c))})};k.prototype.Wa=function(a){function b(e){if(e<d){var f=c[e],g=document.createElement("script");f.removeAttribute("import-dependency");n(f.attributes,function(a){return g.setAttribute(a.name,
a.value)});P=g;f.parentNode.replaceChild(g,f);h(g,function(){P=null;b(e+1)})}else a()}var c=m(document,"script[import-dependency]"),d=c.length;b(0)};k.prototype.Xa=function(a){var b=m(document,"style[import-dependency],link[rel=stylesheet][import-dependency]"),d=b.length;if(d){var e=ja&&!!document.querySelector("link[rel=stylesheet][href][type=import-disable]");n(b,function(b){h(b,function(){b.removeAttribute("import-dependency");0===--d&&a()});if(e&&b.parentNode!==document.head){var f=document.createElement(b.localName);
f.__appliedElement=b;f.setAttribute("type","import-placeholder");b.parentNode.insertBefore(f,b.nextSibling);for(f=c(b);f&&c(f);)f=c(f);f.parentNode!==document.head&&(f=null);document.head.insertBefore(b,f);b.removeAttribute("type")}})}else a()};k.prototype.Sa=function(){var a=this,b=m(document,"link[rel=import]");n(b,function(b){return a.i(b)},!0)};k.prototype.i=function(a){a.__loaded||(a.__loaded=!0,a.import&&(a.import.readyState="complete"),a.dispatchEvent(b(a.import?"load":"error",{bubbles:!1,
cancelable:!1,detail:void 0})))};k.prototype.Ua=function(a){var b=this;n(a,function(a){return n(a.addedNodes,function(a){a&&a.nodeType===Node.ELEMENT_NODE&&(g(a)?b.s(a):b.loadImports(a))})})};var wa=null;if(B)t=m(document,"link[rel=import]"),n(t,function(a){a.import&&"loading"===a.import.readyState||(a.__loaded=!0)}),t=function(a){a=a.target;g(a)&&(a.__loaded=!0)},document.addEventListener("load",t,!0),document.addEventListener("error",t,!0);else{var ca=Object.getOwnPropertyDescriptor(Node.prototype,
"baseURI");Object.defineProperty((!ca||ca.configurable?Node:Element).prototype,"baseURI",{get:function(){var a=g(this)?this:c(this);return a?a.href:ca&&ca.get?ca.get.call(this):(document.querySelector("base")||window.location).href},configurable:!0,enumerable:!0});Object.defineProperty(HTMLLinkElement.prototype,"import",{get:function(){return this.__import||null},configurable:!0,enumerable:!0});e(function(){wa=new k})}f(function(){return document.dispatchEvent(b("HTMLImportsLoaded",{cancelable:!0,
bubbles:!0,detail:void 0}))});a.useNative=B;a.whenReady=f;a.importForElement=c;a.loadImports=function(a){wa&&wa.loadImports(a)}})(window.HTMLImports=window.HTMLImports||{});/*
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
window.WebComponents=window.WebComponents||{flags:{}};var pb=document.querySelector('script[src*="webcomponents-lite.js"]'),qb=/wc-(.+)/,w={};if(!w.noOpts){location.search.slice(1).split("&").forEach(function(a){a=a.split("=");var b;a[0]&&(b=a[0].match(qb))&&(w[b[1]]=a[1]||!0)});if(pb)for(var rb=0,sb;sb=pb.attributes[rb];rb++)"src"!==sb.name&&(w[sb.name]=sb.value||!0);if(w.log&&w.log.split){var tb=w.log.split(",");w.log={};tb.forEach(function(a){w.log[a]=!0})}else w.log={}}
window.WebComponents.flags=w;var ub=w.shadydom;ub&&(window.ShadyDOM=window.ShadyDOM||{},window.ShadyDOM.force=ub);var vb=w.register||w.ce;vb&&window.customElements&&(window.customElements.forcePolyfill=vb);/*
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
function wb(){this.wa=this.root=null;this.Y=!1;this.C=this.T=this.ka=this.assignedSlot=this.assignedNodes=this.J=null;this.childNodes=this.nextSibling=this.previousSibling=this.lastChild=this.firstChild=this.parentNode=this.O=void 0;this.Ca=this.pa=!1}function x(a){a.da||(a.da=new wb);return a.da}function y(a){return a&&a.da};var z=window.ShadyDOM||{};z.Qa=!(!Element.prototype.attachShadow||!Node.prototype.getRootNode);var xb=Object.getOwnPropertyDescriptor(Node.prototype,"firstChild");z.w=!!(xb&&xb.configurable&&xb.get);z.ua=z.force||!z.Qa;var yb=navigator.userAgent.match("Trident"),zb=navigator.userAgent.match("Edge");void 0===z.Aa&&(z.Aa=z.w&&(yb||zb));function Ab(a){return(a=y(a))&&void 0!==a.firstChild}function A(a){return"ShadyRoot"===a.Ia}function Bb(a){a=a.getRootNode();if(A(a))return a}
var Cb=Element.prototype,Db=Cb.matches||Cb.matchesSelector||Cb.mozMatchesSelector||Cb.msMatchesSelector||Cb.oMatchesSelector||Cb.webkitMatchesSelector;function Eb(a,b){if(a&&b)for(var c=Object.getOwnPropertyNames(b),d=0,e;d<c.length&&(e=c[d]);d++){var f=Object.getOwnPropertyDescriptor(b,e);f&&Object.defineProperty(a,e,f)}}function Fb(a,b){for(var c=[],d=1;d<arguments.length;++d)c[d-1]=arguments[d];for(d=0;d<c.length;d++)Eb(a,c[d]);return a}function Gb(a,b){for(var c in b)a[c]=b[c]}
var Hb=document.createTextNode(""),Ib=0,Jb=[];(new MutationObserver(function(){for(;Jb.length;)try{Jb.shift()()}catch(a){throw Hb.textContent=Ib++,a;}})).observe(Hb,{characterData:!0});function Kb(a){Jb.push(a);Hb.textContent=Ib++}var Lb=!!document.contains;function Mb(a,b){for(;b;){if(b==a)return!0;b=b.parentNode}return!1};var Nb=[],Ob;function Pb(a){Ob||(Ob=!0,Kb(Qb));Nb.push(a)}function Qb(){Ob=!1;for(var a=!!Nb.length;Nb.length;)Nb.shift()();return a}Qb.list=Nb;function Rb(){this.a=!1;this.addedNodes=[];this.removedNodes=[];this.X=new Set}function Sb(a){a.a||(a.a=!0,Kb(function(){Tb(a)}))}function Tb(a){if(a.a){a.a=!1;var b=a.takeRecords();b.length&&a.X.forEach(function(a){a(b)})}}Rb.prototype.takeRecords=function(){if(this.addedNodes.length||this.removedNodes.length){var a=[{addedNodes:this.addedNodes,removedNodes:this.removedNodes}];this.addedNodes=[];this.removedNodes=[];return a}return[]};
function Ub(a,b){var c=x(a);c.J||(c.J=new Rb);c.J.X.add(b);var d=c.J;return{Fa:b,G:d,Ja:a,takeRecords:function(){return d.takeRecords()}}}function Vb(a){var b=a&&a.G;b&&(b.X.delete(a.Fa),b.X.size||(x(a.Ja).J=null))}
function Wb(a,b){var c=b.getRootNode();return a.map(function(a){var b=c===a.target.getRootNode();if(b&&a.addedNodes){if(b=Array.from(a.addedNodes).filter(function(a){return c===a.getRootNode()}),b.length)return a=Object.create(a),Object.defineProperty(a,"addedNodes",{value:b,configurable:!0}),a}else if(b)return a}).filter(function(a){return a})};var D={},Xb=Element.prototype.insertBefore,Yb=Element.prototype.replaceChild,Zb=Element.prototype.removeChild,$b=Element.prototype.setAttribute,ac=Element.prototype.removeAttribute,bc=Element.prototype.cloneNode,cc=Document.prototype.importNode,dc=Element.prototype.addEventListener,ec=Element.prototype.removeEventListener,fc=Window.prototype.addEventListener,gc=Window.prototype.removeEventListener,hc=Element.prototype.dispatchEvent,ic=Node.prototype.contains||HTMLElement.prototype.contains,jc=Element.prototype.querySelector,
kc=DocumentFragment.prototype.querySelector,lc=Document.prototype.querySelector,mc=Element.prototype.querySelectorAll,nc=DocumentFragment.prototype.querySelectorAll,oc=Document.prototype.querySelectorAll;D.appendChild=Element.prototype.appendChild;D.insertBefore=Xb;D.replaceChild=Yb;D.removeChild=Zb;D.setAttribute=$b;D.removeAttribute=ac;D.cloneNode=bc;D.importNode=cc;D.addEventListener=dc;D.removeEventListener=ec;D.cb=fc;D.eb=gc;D.dispatchEvent=hc;D.contains=ic;D.mb=jc;D.pb=kc;D.kb=lc;
D.querySelector=function(a){switch(this.nodeType){case Node.ELEMENT_NODE:return jc.call(this,a);case Node.DOCUMENT_NODE:return lc.call(this,a);default:return kc.call(this,a)}};D.nb=mc;D.qb=nc;D.lb=oc;D.querySelectorAll=function(a){switch(this.nodeType){case Node.ELEMENT_NODE:return mc.call(this,a);case Node.DOCUMENT_NODE:return oc.call(this,a);default:return nc.call(this,a)}};var pc=/[&\u00A0"]/g,qc=/[&\u00A0<>]/g;function rc(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";case "\u00a0":return"&nbsp;"}}function sc(a){for(var b={},c=0;c<a.length;c++)b[a[c]]=!0;return b}var tc=sc("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")),uc=sc("style script xmp iframe noembed noframes plaintext noscript".split(" "));
function vc(a,b){"template"===a.localName&&(a=a.content);for(var c="",d=b?b(a):a.childNodes,e=0,f=d.length,h;e<f&&(h=d[e]);e++){a:{var g=h;var k=a;var l=b;switch(g.nodeType){case Node.ELEMENT_NODE:for(var m=g.localName,n="<"+m,t=g.attributes,B=0;k=t[B];B++)n+=" "+k.name+'="'+k.value.replace(pc,rc)+'"';n+=">";g=tc[m]?n:n+vc(g,l)+"</"+m+">";break a;case Node.TEXT_NODE:g=g.data;g=k&&uc[k.localName]?g:g.replace(qc,rc);break a;case Node.COMMENT_NODE:g="\x3c!--"+g.data+"--\x3e";break a;default:throw window.console.error(g),
Error("not implemented");}}c+=g}return c};var E={},F=document.createTreeWalker(document,NodeFilter.SHOW_ALL,null,!1),G=document.createTreeWalker(document,NodeFilter.SHOW_ELEMENT,null,!1);function wc(a){var b=[];F.currentNode=a;for(a=F.firstChild();a;)b.push(a),a=F.nextSibling();return b}E.parentNode=function(a){F.currentNode=a;return F.parentNode()};E.firstChild=function(a){F.currentNode=a;return F.firstChild()};E.lastChild=function(a){F.currentNode=a;return F.lastChild()};E.previousSibling=function(a){F.currentNode=a;return F.previousSibling()};
E.nextSibling=function(a){F.currentNode=a;return F.nextSibling()};E.childNodes=wc;E.parentElement=function(a){G.currentNode=a;return G.parentNode()};E.firstElementChild=function(a){G.currentNode=a;return G.firstChild()};E.lastElementChild=function(a){G.currentNode=a;return G.lastChild()};E.previousElementSibling=function(a){G.currentNode=a;return G.previousSibling()};E.nextElementSibling=function(a){G.currentNode=a;return G.nextSibling()};
E.children=function(a){var b=[];G.currentNode=a;for(a=G.firstChild();a;)b.push(a),a=G.nextSibling();return b};E.innerHTML=function(a){return vc(a,function(a){return wc(a)})};E.textContent=function(a){switch(a.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:a=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1);for(var b="",c;c=a.nextNode();)b+=c.nodeValue;return b;default:return a.nodeValue}};var H={},xc=z.w,yc=[Node.prototype,Element.prototype,HTMLElement.prototype];function I(a){var b;a:{for(b=0;b<yc.length;b++){var c=yc[b];if(c.hasOwnProperty(a)){b=c;break a}}b=void 0}if(!b)throw Error("Could not find descriptor for "+a);return Object.getOwnPropertyDescriptor(b,a)}
var J=xc?{parentNode:I("parentNode"),firstChild:I("firstChild"),lastChild:I("lastChild"),previousSibling:I("previousSibling"),nextSibling:I("nextSibling"),childNodes:I("childNodes"),parentElement:I("parentElement"),previousElementSibling:I("previousElementSibling"),nextElementSibling:I("nextElementSibling"),innerHTML:I("innerHTML"),textContent:I("textContent"),firstElementChild:I("firstElementChild"),lastElementChild:I("lastElementChild"),children:I("children")}:{},zc=xc?{firstElementChild:Object.getOwnPropertyDescriptor(DocumentFragment.prototype,
"firstElementChild"),lastElementChild:Object.getOwnPropertyDescriptor(DocumentFragment.prototype,"lastElementChild"),children:Object.getOwnPropertyDescriptor(DocumentFragment.prototype,"children")}:{},Ac=xc?{firstElementChild:Object.getOwnPropertyDescriptor(Document.prototype,"firstElementChild"),lastElementChild:Object.getOwnPropertyDescriptor(Document.prototype,"lastElementChild"),children:Object.getOwnPropertyDescriptor(Document.prototype,"children")}:{};H.va=J;H.ob=zc;H.jb=Ac;H.parentNode=function(a){return J.parentNode.get.call(a)};
H.firstChild=function(a){return J.firstChild.get.call(a)};H.lastChild=function(a){return J.lastChild.get.call(a)};H.previousSibling=function(a){return J.previousSibling.get.call(a)};H.nextSibling=function(a){return J.nextSibling.get.call(a)};H.childNodes=function(a){return Array.prototype.slice.call(J.childNodes.get.call(a))};H.parentElement=function(a){return J.parentElement.get.call(a)};H.previousElementSibling=function(a){return J.previousElementSibling.get.call(a)};H.nextElementSibling=function(a){return J.nextElementSibling.get.call(a)};
H.innerHTML=function(a){return J.innerHTML.get.call(a)};H.textContent=function(a){return J.textContent.get.call(a)};H.children=function(a){switch(a.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:a=zc.children.get.call(a);break;case Node.DOCUMENT_NODE:a=Ac.children.get.call(a);break;default:a=J.children.get.call(a)}return Array.prototype.slice.call(a)};
H.firstElementChild=function(a){switch(a.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:return zc.firstElementChild.get.call(a);case Node.DOCUMENT_NODE:return Ac.firstElementChild.get.call(a);default:return J.firstElementChild.get.call(a)}};H.lastElementChild=function(a){switch(a.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:return zc.lastElementChild.get.call(a);case Node.DOCUMENT_NODE:return Ac.lastElementChild.get.call(a);default:return J.lastElementChild.get.call(a)}};var K=z.Aa?H:E;function Bc(a){for(;a.firstChild;)a.removeChild(a.firstChild)}
var Cc=z.w,Dc=document.implementation.createHTMLDocument("inert"),Ec=Object.getOwnPropertyDescriptor(Node.prototype,"isConnected"),Fc=Ec&&Ec.get,Gc=Object.getOwnPropertyDescriptor(Document.prototype,"activeElement"),Hc={parentElement:{get:function(){var a=y(this);(a=a&&a.parentNode)&&a.nodeType!==Node.ELEMENT_NODE&&(a=null);return void 0!==a?a:K.parentElement(this)},configurable:!0},parentNode:{get:function(){var a=y(this);a=a&&a.parentNode;return void 0!==a?a:K.parentNode(this)},configurable:!0},
nextSibling:{get:function(){var a=y(this);a=a&&a.nextSibling;return void 0!==a?a:K.nextSibling(this)},configurable:!0},previousSibling:{get:function(){var a=y(this);a=a&&a.previousSibling;return void 0!==a?a:K.previousSibling(this)},configurable:!0},nextElementSibling:{get:function(){var a=y(this);if(a&&void 0!==a.nextSibling){for(a=this.nextSibling;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.nextSibling;return a}return K.nextElementSibling(this)},configurable:!0},previousElementSibling:{get:function(){var a=
y(this);if(a&&void 0!==a.previousSibling){for(a=this.previousSibling;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.previousSibling;return a}return K.previousElementSibling(this)},configurable:!0}},Ic={className:{get:function(){return this.getAttribute("class")||""},set:function(a){this.setAttribute("class",a)},configurable:!0}},Jc={childNodes:{get:function(){if(Ab(this)){var a=y(this);if(!a.childNodes){a.childNodes=[];for(var b=this.firstChild;b;b=b.nextSibling)a.childNodes.push(b)}var c=a.childNodes}else c=
K.childNodes(this);c.item=function(a){return c[a]};return c},configurable:!0},childElementCount:{get:function(){return this.children.length},configurable:!0},firstChild:{get:function(){var a=y(this);a=a&&a.firstChild;return void 0!==a?a:K.firstChild(this)},configurable:!0},lastChild:{get:function(){var a=y(this);a=a&&a.lastChild;return void 0!==a?a:K.lastChild(this)},configurable:!0},textContent:{get:function(){if(Ab(this)){for(var a=[],b=0,c=this.childNodes,d;d=c[b];b++)d.nodeType!==Node.COMMENT_NODE&&
a.push(d.textContent);return a.join("")}return K.textContent(this)},set:function(a){if("undefined"===typeof a||null===a)a="";switch(this.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:if(!Ab(this)&&Cc){var b=this.firstChild;(b!=this.lastChild||b&&b.nodeType!=Node.TEXT_NODE)&&Bc(this);H.va.textContent.set.call(this,a)}else Bc(this),(0<a.length||this.nodeType===Node.ELEMENT_NODE)&&this.appendChild(document.createTextNode(a));break;default:this.nodeValue=a}},configurable:!0},firstElementChild:{get:function(){var a=
y(this);if(a&&void 0!==a.firstChild){for(a=this.firstChild;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.nextSibling;return a}return K.firstElementChild(this)},configurable:!0},lastElementChild:{get:function(){var a=y(this);if(a&&void 0!==a.lastChild){for(a=this.lastChild;a&&a.nodeType!==Node.ELEMENT_NODE;)a=a.previousSibling;return a}return K.lastElementChild(this)},configurable:!0},children:{get:function(){var a=Ab(this)?Array.prototype.filter.call(this.childNodes,function(a){return a.nodeType===Node.ELEMENT_NODE}):
K.children(this);a.item=function(b){return a[b]};return a},configurable:!0},innerHTML:{get:function(){return Ab(this)?vc("template"===this.localName?this.content:this):K.innerHTML(this)},set:function(a){var b="template"===this.localName?this.content:this;Bc(b);var c=this.localName;c&&"template"!==c||(c="div");c=Dc.createElement(c);for(Cc?H.va.innerHTML.set.call(c,a):c.innerHTML=a;c.firstChild;)b.appendChild(c.firstChild)},configurable:!0}},Kc={shadowRoot:{get:function(){var a=y(this);return a&&a.wa||
null},configurable:!0}},Lc={activeElement:{get:function(){var a=Gc&&Gc.get?Gc.get.call(document):z.w?void 0:document.activeElement;if(a&&a.nodeType){var b=!!A(this);if(this===document||b&&this.host!==a&&D.contains.call(this.host,a)){for(b=Bb(a);b&&b!==this;)a=b.host,b=Bb(a);a=this===document?b?null:a:b===this?a:null}else a=null}else a=null;return a},set:function(){},configurable:!0}};
function L(a,b,c){for(var d in b){var e=Object.getOwnPropertyDescriptor(a,d);e&&e.configurable||!e&&c?Object.defineProperty(a,d,b[d]):c&&console.warn("Could not define",d,"on",a)}}function Mc(a){L(a,Hc);L(a,Ic);L(a,Jc);L(a,Lc)}
function Nc(){var a=Oc.prototype;a.__proto__=DocumentFragment.prototype;L(a,Hc,!0);L(a,Jc,!0);L(a,Lc,!0);Object.defineProperties(a,{nodeType:{value:Node.DOCUMENT_FRAGMENT_NODE,configurable:!0},nodeName:{value:"#document-fragment",configurable:!0},nodeValue:{value:null,configurable:!0}});["localName","namespaceURI","prefix"].forEach(function(b){Object.defineProperty(a,b,{value:void 0,configurable:!0})});["ownerDocument","baseURI","isConnected"].forEach(function(b){Object.defineProperty(a,b,{get:function(){return this.host[b]},
configurable:!0})})}var Pc=z.w?function(){}:function(a){var b=x(a);b.pa||(b.pa=!0,L(a,Hc,!0),L(a,Ic,!0))},Qc=z.w?function(){}:function(a){x(a).Ca||(L(a,Jc,!0),L(a,Kc,!0))};var Rc=K.childNodes;function Sc(a,b,c){Pc(a);c=c||null;var d=x(a),e=x(b),f=c?x(c):null;d.previousSibling=c?f.previousSibling:b.lastChild;if(f=y(d.previousSibling))f.nextSibling=a;if(f=y(d.nextSibling=c))f.previousSibling=a;d.parentNode=b;c?c===e.firstChild&&(e.firstChild=a):(e.lastChild=a,e.firstChild||(e.firstChild=a));e.childNodes=null}
function Tc(a,b){var c=x(a);if(void 0===c.firstChild)for(b=b||Rc(a),c.firstChild=b[0]||null,c.lastChild=b[b.length-1]||null,Qc(a),c=0;c<b.length;c++){var d=b[c],e=x(d);e.parentNode=a;e.nextSibling=b[c+1]||null;e.previousSibling=b[c-1]||null;Pc(d)}};var Uc=K.parentNode;
function Vc(a,b,c){if(b===a)throw Error("Failed to execute 'appendChild' on 'Node': The new child element contains the parent.");if(c){var d=y(c);d=d&&d.parentNode;if(void 0!==d&&d!==a||void 0===d&&Uc(c)!==a)throw Error("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.");}if(c===b)return b;b.parentNode&&Wc(b.parentNode,b);var e,f;if(!b.__noInsertionPoint){if(f=e=Bb(a)){var h;"slot"===b.localName?h=[b]:b.querySelectorAll&&
(h=b.querySelectorAll("slot"));f=h&&h.length?h:void 0}f&&(h=e,d=f,h.v=h.v||[],h.g=h.g||[],h.j=h.j||{},h.v.push.apply(h.v,[].concat(d instanceof Array?d:ma(la(d)))))}("slot"===a.localName||f)&&(e=e||Bb(a))&&Xc(e);if(Ab(a)){e=c;Qc(a);f=x(a);void 0!==f.firstChild&&(f.childNodes=null);if(b.nodeType===Node.DOCUMENT_FRAGMENT_NODE){f=b.childNodes;for(h=0;h<f.length;h++)Sc(f[h],a,e);e=x(b);f=void 0!==e.firstChild?null:void 0;e.firstChild=e.lastChild=f;e.childNodes=f}else Sc(b,a,e);e=y(a);if(Yc(a)){Xc(e.root);
var g=!0}else e.root&&(g=!0)}g||(g=A(a)?a.host:a,c?(c=Zc(c),D.insertBefore.call(g,b,c)):D.appendChild.call(g,b));$c(a,b);return b}
function Wc(a,b){if(b.parentNode!==a)throw Error("The node to be removed is not a child of this node: "+b);var c=Bb(b),d=y(a);if(Ab(a)){var e=x(b),f=x(a);b===f.firstChild&&(f.firstChild=e.nextSibling);b===f.lastChild&&(f.lastChild=e.previousSibling);var h=e.previousSibling,g=e.nextSibling;h&&(x(h).nextSibling=g);g&&(x(g).previousSibling=h);e.parentNode=e.previousSibling=e.nextSibling=void 0;void 0!==f.childNodes&&(f.childNodes=null);if(Yc(a)){Xc(d.root);var k=!0}}ad(b);if(c){(e=a&&"slot"===a.localName)&&
(k=!0);if(c.g){bd(c);f=c.j;for(Z in f)for(h=f[Z],g=0;g<h.length;g++){var l=h[g];if(Mb(b,l)){h.splice(g,1);var m=c.g.indexOf(l);0<=m&&c.g.splice(m,1);g--;m=y(l);if(l=m.C)for(var n=0;n<l.length;n++){var t=l[n],B=cd(t);B&&D.removeChild.call(B,t)}m.C=[];m.assignedNodes=[];m=!0}}var Z=m}else Z=void 0;(Z||e)&&Xc(c)}k||(k=A(a)?a.host:a,(!d.root&&"slot"!==b.localName||k===Uc(b))&&D.removeChild.call(k,b));$c(a,null,b);return b}
function ad(a){var b=y(a);if(b&&void 0!==b.O){b=a.childNodes;for(var c=0,d=b.length,e;c<d&&(e=b[c]);c++)ad(e)}if(a=y(a))a.O=void 0}function Zc(a){var b=a;a&&"slot"===a.localName&&(b=(b=(b=y(a))&&b.C)&&b.length?b[0]:Zc(a.nextSibling));return b}function Yc(a){return(a=(a=y(a))&&a.root)&&dd(a)}
function ed(a,b){if("slot"===b)a=a.parentNode,Yc(a)&&Xc(y(a).root);else if("slot"===a.localName&&"name"===b&&(b=Bb(a))){if(b.g){var c=a.Da,d=fd(a);if(d!==c){c=b.j[c];var e=c.indexOf(a);0<=e&&c.splice(e,1);c=b.j[d]||(b.j[d]=[]);c.push(a);1<c.length&&(b.j[d]=gd(c))}}Xc(b)}}function $c(a,b,c){if(a=(a=y(a))&&a.J)b&&a.addedNodes.push(b),c&&a.removedNodes.push(c),Sb(a)}
function hd(a){if(a&&a.nodeType){var b=x(a),c=b.O;void 0===c&&(A(a)?(c=a,b.O=c):(c=(c=a.parentNode)?hd(c):a,D.contains.call(document.documentElement,a)&&(b.O=c)));return c}}function id(a,b,c){var d=[];jd(a.childNodes,b,c,d);return d}function jd(a,b,c,d){for(var e=0,f=a.length,h;e<f&&(h=a[e]);e++){var g;if(g=h.nodeType===Node.ELEMENT_NODE){g=h;var k=b,l=c,m=d,n=k(g);n&&m.push(g);l&&l(n)?g=n:(jd(g.childNodes,k,l,m),g=void 0)}if(g)break}}var kd=null;
function ld(a,b,c){kd||(kd=window.ShadyCSS&&window.ShadyCSS.ScopingShim);kd&&"class"===b?kd.setElementClass(a,c):(D.setAttribute.call(a,b,c),ed(a,b))}function md(a,b){if(a.ownerDocument!==document)return D.importNode.call(document,a,b);var c=D.importNode.call(document,a,!1);if(b){a=a.childNodes;b=0;for(var d;b<a.length;b++)d=md(a[b],!0),c.appendChild(d)}return c};var nd="__eventWrappers"+Date.now(),od={blur:!0,focus:!0,focusin:!0,focusout:!0,click:!0,dblclick:!0,mousedown:!0,mouseenter:!0,mouseleave:!0,mousemove:!0,mouseout:!0,mouseover:!0,mouseup:!0,wheel:!0,beforeinput:!0,input:!0,keydown:!0,keyup:!0,compositionstart:!0,compositionupdate:!0,compositionend:!0,touchstart:!0,touchend:!0,touchmove:!0,touchcancel:!0,pointerover:!0,pointerenter:!0,pointerdown:!0,pointermove:!0,pointerup:!0,pointercancel:!0,pointerout:!0,pointerleave:!0,gotpointercapture:!0,lostpointercapture:!0,
dragstart:!0,drag:!0,dragenter:!0,dragleave:!0,dragover:!0,drop:!0,dragend:!0,DOMActivate:!0,DOMFocusIn:!0,DOMFocusOut:!0,keypress:!0};function pd(a,b){var c=[],d=a;for(a=a===window?window:a.getRootNode();d;)c.push(d),d=d.assignedSlot?d.assignedSlot:d.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&d.host&&(b||d!==a)?d.host:d.parentNode;c[c.length-1]===document&&c.push(window);return c}
function qd(a,b){if(!A)return a;a=pd(a,!0);for(var c=0,d,e,f,h;c<b.length;c++)if(d=b[c],f=d===window?window:d.getRootNode(),f!==e&&(h=a.indexOf(f),e=f),!A(f)||-1<h)return d}
var rd={get composed(){!1!==this.isTrusted&&void 0===this.ba&&(this.ba=od[this.type]);return this.ba||!1},composedPath:function(){this.b||(this.b=pd(this.__target,this.composed));return this.b},get target(){return qd(this.currentTarget,this.composedPath())},get relatedTarget(){if(!this.ca)return null;this.c||(this.c=pd(this.ca,!0));return qd(this.currentTarget,this.c)},stopPropagation:function(){Event.prototype.stopPropagation.call(this);this.a=!0},stopImmediatePropagation:function(){Event.prototype.stopImmediatePropagation.call(this);
this.a=this.i=!0}};function sd(a){function b(b,d){b=new a(b,d);b.ba=d&&!!d.composed;return b}Gb(b,a);b.prototype=a.prototype;return b}var td={focus:!0,blur:!0};function ud(a){return a.__target!==a.target||a.ca!==a.relatedTarget}function vd(a,b,c){if(c=b.__handlers&&b.__handlers[a.type]&&b.__handlers[a.type][c])for(var d=0,e;(e=c[d])&&(!ud(a)||a.target!==a.relatedTarget)&&(e.call(b,a),!a.i);d++);}
function wd(a){var b=a.composedPath();Object.defineProperty(a,"currentTarget",{get:function(){return d},configurable:!0});for(var c=b.length-1;0<=c;c--){var d=b[c];vd(a,d,"capture");if(a.a)return}Object.defineProperty(a,"eventPhase",{get:function(){return Event.AT_TARGET}});var e;for(c=0;c<b.length;c++){d=b[c];var f=y(d);f=f&&f.root;if(0===c||f&&f===e)if(vd(a,d,"bubble"),d!==window&&(e=d.getRootNode()),a.a)break}}
function xd(a,b,c,d,e,f){for(var h=0;h<a.length;h++){var g=a[h],k=g.type,l=g.capture,m=g.once,n=g.passive;if(b===g.node&&c===k&&d===l&&e===m&&f===n)return h}return-1}
function yd(a,b,c){if(b){var d=typeof b;if("function"===d||"object"===d)if("object"!==d||b.handleEvent&&"function"===typeof b.handleEvent){if(c&&"object"===typeof c){var e=!!c.capture;var f=!!c.once;var h=!!c.passive}else e=!!c,h=f=!1;var g=c&&c.ea||this,k=b[nd];if(k){if(-1<xd(k,g,a,e,f,h))return}else b[nd]=[];k=function(e){f&&this.removeEventListener(a,b,c);e.__target||zd(e);if(g!==this){var h=Object.getOwnPropertyDescriptor(e,"currentTarget");Object.defineProperty(e,"currentTarget",{get:function(){return g},
configurable:!0})}if(e.composed||-1<e.composedPath().indexOf(g))if(ud(e)&&e.target===e.relatedTarget)e.eventPhase===Event.BUBBLING_PHASE&&e.stopImmediatePropagation();else if(e.eventPhase===Event.CAPTURING_PHASE||e.bubbles||e.target===g||g instanceof Window){var k="function"===d?b.call(g,e):b.handleEvent&&b.handleEvent(e);g!==this&&(h?(Object.defineProperty(e,"currentTarget",h),h=null):delete e.currentTarget);return k}};b[nd].push({node:g,type:a,capture:e,once:f,passive:h,fb:k});td[a]?(this.__handlers=
this.__handlers||{},this.__handlers[a]=this.__handlers[a]||{capture:[],bubble:[]},this.__handlers[a][e?"capture":"bubble"].push(k)):(this instanceof Window?D.cb:D.addEventListener).call(this,a,k,c)}}}
function Ad(a,b,c){if(b){if(c&&"object"===typeof c){var d=!!c.capture;var e=!!c.once;var f=!!c.passive}else d=!!c,f=e=!1;var h=c&&c.ea||this,g=void 0;var k=null;try{k=b[nd]}catch(l){}k&&(e=xd(k,h,a,d,e,f),-1<e&&(g=k.splice(e,1)[0].fb,k.length||(b[nd]=void 0)));(this instanceof Window?D.eb:D.removeEventListener).call(this,a,g||b,c);g&&td[a]&&this.__handlers&&this.__handlers[a]&&(a=this.__handlers[a][d?"capture":"bubble"],g=a.indexOf(g),-1<g&&a.splice(g,1))}}
function Bd(){for(var a in td)window.addEventListener(a,function(a){a.__target||(zd(a),wd(a))},!0)}function zd(a){a.__target=a.target;a.ca=a.relatedTarget;if(z.w){var b=Object.getPrototypeOf(a);if(!b.hasOwnProperty("__patchProto")){var c=Object.create(b);c.hb=b;Eb(c,rd);b.__patchProto=c}a.__proto__=b.__patchProto}else Eb(a,rd)}var Cd=sd(window.Event),Dd=sd(window.CustomEvent),Ed=sd(window.MouseEvent);function Fd(a,b){return{index:a,P:[],W:b}}
function Gd(a,b,c,d){var e=0,f=0,h=0,g=0,k=Math.min(b-e,d-f);if(0==e&&0==f)a:{for(h=0;h<k;h++)if(a[h]!==c[h])break a;h=k}if(b==a.length&&d==c.length){g=a.length;for(var l=c.length,m=0;m<k-h&&Hd(a[--g],c[--l]);)m++;g=m}e+=h;f+=h;b-=g;d-=g;if(0==b-e&&0==d-f)return[];if(e==b){for(b=Fd(e,0);f<d;)b.P.push(c[f++]);return[b]}if(f==d)return[Fd(e,b-e)];k=e;h=f;d=d-h+1;g=b-k+1;b=Array(d);for(l=0;l<d;l++)b[l]=Array(g),b[l][0]=l;for(l=0;l<g;l++)b[0][l]=l;for(l=1;l<d;l++)for(m=1;m<g;m++)if(a[k+m-1]===c[h+l-1])b[l][m]=
b[l-1][m-1];else{var n=b[l-1][m]+1,t=b[l][m-1]+1;b[l][m]=n<t?n:t}k=b.length-1;h=b[0].length-1;d=b[k][h];for(a=[];0<k||0<h;)0==k?(a.push(2),h--):0==h?(a.push(3),k--):(g=b[k-1][h-1],l=b[k-1][h],m=b[k][h-1],n=l<m?l<g?l:g:m<g?m:g,n==g?(g==d?a.push(0):(a.push(1),d=g),k--,h--):n==l?(a.push(3),k--,d=l):(a.push(2),h--,d=m));a.reverse();b=void 0;k=[];for(h=0;h<a.length;h++)switch(a[h]){case 0:b&&(k.push(b),b=void 0);e++;f++;break;case 1:b||(b=Fd(e,0));b.W++;e++;b.P.push(c[f]);f++;break;case 2:b||(b=Fd(e,0));
b.W++;e++;break;case 3:b||(b=Fd(e,0)),b.P.push(c[f]),f++}b&&k.push(b);return k}function Hd(a,b){return a===b};var cd=K.parentNode,Id=K.childNodes,Jd={};function Kd(a){var b=[];do b.unshift(a);while(a=a.parentNode);return b}function Oc(a,b,c){if(a!==Jd)throw new TypeError("Illegal constructor");this.Ia="ShadyRoot";a=Id(b);this.host=b;this.a=c&&c.mode;Tc(b,a);c=y(b);c.root=this;c.wa="closed"!==this.a?this:null;c=x(this);c.firstChild=c.lastChild=c.parentNode=c.nextSibling=c.previousSibling=null;c.childNodes=[];this.V=!1;this.v=this.j=this.g=null;c=0;for(var d=a.length;c<d;c++)D.removeChild.call(b,a[c])}
function Xc(a){a.V||(a.V=!0,Pb(function(){return Ld(a)}))}function Ld(a){for(var b;a;){a.V&&(b=a);a:{var c=a;a=c.host.getRootNode();if(A(a))for(var d=c.host.childNodes,e=0;e<d.length;e++)if(c=d[e],"slot"==c.localName)break a;a=void 0}}b&&b._renderRoot()}
Oc.prototype._renderRoot=function(){this.V=!1;if(this.g){bd(this);for(var a=0,b;a<this.g.length;a++){b=this.g[a];var c=y(b),d=c.assignedNodes;c.assignedNodes=[];c.C=[];if(c.ka=d)for(c=0;c<d.length;c++){var e=y(d[c]);e.T=e.assignedSlot;e.assignedSlot===b&&(e.assignedSlot=null)}}for(b=this.host.firstChild;b;b=b.nextSibling)Md(this,b);for(a=0;a<this.g.length;a++){b=this.g[a];d=y(b);if(!d.assignedNodes.length)for(c=b.firstChild;c;c=c.nextSibling)Md(this,c,b);(c=(c=y(b.parentNode))&&c.root)&&dd(c)&&c._renderRoot();
Nd(this,d.C,d.assignedNodes);if(c=d.ka){for(e=0;e<c.length;e++)y(c[e]).T=null;d.ka=null;c.length>d.assignedNodes.length&&(d.Y=!0)}d.Y&&(d.Y=!1,Od(this,b))}a=this.g;b=[];for(d=0;d<a.length;d++)c=a[d].parentNode,(e=y(c))&&e.root||!(0>b.indexOf(c))||b.push(c);for(a=0;a<b.length;a++){d=b[a];c=d===this?this.host:d;e=[];d=d.childNodes;for(var f=0;f<d.length;f++){var h=d[f];if("slot"==h.localName){h=y(h).C;for(var g=0;g<h.length;g++)e.push(h[g])}else e.push(h)}d=void 0;f=Id(c);h=Gd(e,e.length,f,f.length);
for(var k=g=0;g<h.length&&(d=h[g]);g++){for(var l=0,m;l<d.P.length&&(m=d.P[l]);l++)cd(m)===c&&D.removeChild.call(c,m),f.splice(d.index+k,1);k-=d.W}for(k=0;k<h.length&&(d=h[k]);k++)for(g=f[d.index],l=d.index;l<d.index+d.W;l++)m=e[l],D.insertBefore.call(c,m,g),f.splice(l,0,m)}}};function Md(a,b,c){var d=x(b),e=d.T;d.T=null;c||(c=(a=a.j[b.slot||"__catchall"])&&a[0]);c?(x(c).assignedNodes.push(b),d.assignedSlot=c):d.assignedSlot=void 0;e!==d.assignedSlot&&d.assignedSlot&&(x(d.assignedSlot).Y=!0)}
function Nd(a,b,c){for(var d=0,e;d<c.length&&(e=c[d]);d++)if("slot"==e.localName){var f=y(e).assignedNodes;f&&f.length&&Nd(a,b,f)}else b.push(c[d])}function Od(a,b){D.dispatchEvent.call(b,new Event("slotchange"));b=y(b);b.assignedSlot&&Od(a,b.assignedSlot)}function bd(a){if(a.v&&a.v.length){for(var b=a.v,c,d=0;d<b.length;d++){var e=b[d];Tc(e);Tc(e.parentNode);var f=fd(e);a.j[f]?(c=c||{},c[f]=!0,a.j[f].push(e)):a.j[f]=[e];a.g.push(e)}if(c)for(var h in c)a.j[h]=gd(a.j[h]);a.v=[]}}
function fd(a){var b=a.name||a.getAttribute("name")||"__catchall";return a.Da=b}function gd(a){return a.sort(function(a,c){a=Kd(a);for(var b=Kd(c),e=0;e<a.length;e++){c=a[e];var f=b[e];if(c!==f)return a=Array.from(c.parentNode.childNodes),a.indexOf(c)-a.indexOf(f)}})}function dd(a){bd(a);return!(!a.g||!a.g.length)};function Pd(a){var b=a.getRootNode();A(b)&&Ld(b);return(a=y(a))&&a.assignedSlot||null}
var Qd={addEventListener:yd.bind(window),removeEventListener:Ad.bind(window)},Rd={addEventListener:yd,removeEventListener:Ad,appendChild:function(a){return Vc(this,a)},insertBefore:function(a,b){return Vc(this,a,b)},removeChild:function(a){return Wc(this,a)},replaceChild:function(a,b){Vc(this,a,b);Wc(this,b);return a},cloneNode:function(a){if("template"==this.localName)var b=D.cloneNode.call(this,a);else if(b=D.cloneNode.call(this,!1),a){a=this.childNodes;for(var c=0,d;c<a.length;c++)d=a[c].cloneNode(!0),
b.appendChild(d)}return b},getRootNode:function(){return hd(this)},contains:function(a){return Mb(this,a)},dispatchEvent:function(a){Qb();return D.dispatchEvent.call(this,a)}};
Object.defineProperties(Rd,{isConnected:{get:function(){if(Fc&&Fc.call(this))return!0;if(this.nodeType==Node.DOCUMENT_FRAGMENT_NODE)return!1;var a=this.ownerDocument;if(Lb){if(D.contains.call(a,this))return!0}else if(a.documentElement&&D.contains.call(a.documentElement,this))return!0;for(a=this;a&&!(a instanceof Document);)a=a.parentNode||(A(a)?a.host:void 0);return!!(a&&a instanceof Document)},configurable:!0}});
var Sd={get assignedSlot(){return Pd(this)}},Td={querySelector:function(a){return id(this,function(b){return Db.call(b,a)},function(a){return!!a})[0]||null},querySelectorAll:function(a,b){if(b){b=Array.prototype.slice.call(D.querySelectorAll(this,a));var c=this.getRootNode();return b.filter(function(a){return a.getRootNode()==c})}return id(this,function(b){return Db.call(b,a)})}},Ud={assignedNodes:function(a){if("slot"===this.localName){var b=this.getRootNode();A(b)&&Ld(b);return(b=y(this))?(a&&a.flatten?
b.C:b.assignedNodes)||[]:[]}}},Vd=Fb({setAttribute:function(a,b){ld(this,a,b)},removeAttribute:function(a){D.removeAttribute.call(this,a);ed(this,a)},attachShadow:function(a){if(!this)throw"Must provide a host.";if(!a)throw"Not enough arguments.";return new Oc(Jd,this,a)},get slot(){return this.getAttribute("slot")},set slot(a){ld(this,"slot",a)},get assignedSlot(){return Pd(this)}},Td,Ud);Object.defineProperties(Vd,Kc);
var Wd=Fb({importNode:function(a,b){return md(a,b)},getElementById:function(a){return id(this,function(b){return b.id==a},function(a){return!!a})[0]||null}},Td);Object.defineProperties(Wd,{_activeElement:Lc.activeElement});
var Xd=HTMLElement.prototype.blur,Yd=Fb({blur:function(){var a=y(this);(a=(a=a&&a.root)&&a.activeElement)?a.blur():Xd.call(this)}}),Zd={addEventListener:function(a,b,c){"object"!==typeof c&&(c={capture:!!c});c.ea=this;this.host.addEventListener(a,b,c)},removeEventListener:function(a,b,c){"object"!==typeof c&&(c={capture:!!c});c.ea=this;this.host.removeEventListener(a,b,c)},getElementById:function(a){return id(this,function(b){return b.id==a},function(a){return!!a})[0]||null}};
function M(a,b){for(var c=Object.getOwnPropertyNames(b),d=0;d<c.length;d++){var e=c[d],f=Object.getOwnPropertyDescriptor(b,e);f.value?a[e]=f.value:Object.defineProperty(a,e,f)}};if(z.ua){var ShadyDOM={inUse:z.ua,patch:function(a){Qc(a);Pc(a);return a},isShadyRoot:A,enqueue:Pb,flush:Qb,settings:z,filterMutations:Wb,observeChildren:Ub,unobserveChildren:Vb,nativeMethods:D,nativeTree:K};window.ShadyDOM=ShadyDOM;window.Event=Cd;window.CustomEvent=Dd;window.MouseEvent=Ed;Bd();var $d=window.customElements&&window.customElements.nativeHTMLElement||HTMLElement;M(Oc.prototype,Zd);M(window.Node.prototype,Rd);M(window.Window.prototype,Qd);M(window.Text.prototype,Sd);M(window.DocumentFragment.prototype,
Td);M(window.Element.prototype,Vd);M(window.Document.prototype,Wd);window.HTMLSlotElement&&M(window.HTMLSlotElement.prototype,Ud);M($d.prototype,Yd);z.w&&(Mc(window.Node.prototype),Mc(window.Text.prototype),Mc(window.DocumentFragment.prototype),Mc(window.Element.prototype),Mc($d.prototype),Mc(window.Document.prototype),window.HTMLSlotElement&&Mc(window.HTMLSlotElement.prototype));Nc();window.ShadowRoot=Oc};var ae=new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));function be(a){var b=ae.has(a);a=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(a);return!b&&a}function N(a){var b=a.isConnected;if(void 0!==b)return b;for(;a&&!(a.__CE_isImportDocument||a instanceof Document);)a=a.parentNode||(window.ShadowRoot&&a instanceof ShadowRoot?a.host:void 0);return!(!a||!(a.__CE_isImportDocument||a instanceof Document))}
function ce(a,b){for(;b&&b!==a&&!b.nextSibling;)b=b.parentNode;return b&&b!==a?b.nextSibling:null}
function de(a,b,c){c=void 0===c?new Set:c;for(var d=a;d;){if(d.nodeType===Node.ELEMENT_NODE){var e=d;b(e);var f=e.localName;if("link"===f&&"import"===e.getAttribute("rel")){d=e.import;if(d instanceof Node&&!c.has(d))for(c.add(d),d=d.firstChild;d;d=d.nextSibling)de(d,b,c);d=ce(a,e);continue}else if("template"===f){d=ce(a,e);continue}if(e=e.__CE_shadowRoot)for(e=e.firstChild;e;e=e.nextSibling)de(e,b,c)}d=d.firstChild?d.firstChild:ce(a,d)}}function O(a,b,c){a[b]=c};function ee(){this.a=new Map;this.s=new Map;this.i=[];this.c=!1}function fe(a,b,c){a.a.set(b,c);a.s.set(c.constructor,c)}function ge(a,b){a.c=!0;a.i.push(b)}function he(a,b){a.c&&de(b,function(b){return a.b(b)})}ee.prototype.b=function(a){if(this.c&&!a.__CE_patched){a.__CE_patched=!0;for(var b=0;b<this.i.length;b++)this.i[b](a)}};function Q(a,b){var c=[];de(b,function(a){return c.push(a)});for(b=0;b<c.length;b++){var d=c[b];1===d.__CE_state?a.connectedCallback(d):ie(a,d)}}
function R(a,b){var c=[];de(b,function(a){return c.push(a)});for(b=0;b<c.length;b++){var d=c[b];1===d.__CE_state&&a.disconnectedCallback(d)}}
function je(a,b,c){c=void 0===c?{}:c;var d=c.bb||new Set,e=c.za||function(b){return ie(a,b)},f=[];de(b,function(b){if("link"===b.localName&&"import"===b.getAttribute("rel")){var c=b.import;c instanceof Node&&(c.__CE_isImportDocument=!0,c.__CE_hasRegistry=!0);c&&"complete"===c.readyState?c.__CE_documentLoadHandled=!0:b.addEventListener("load",function(){var c=b.import;if(!c.__CE_documentLoadHandled){c.__CE_documentLoadHandled=!0;var f=new Set(d);f.delete(c);je(a,c,{bb:f,za:e})}})}else f.push(b)},d);
if(a.c)for(b=0;b<f.length;b++)a.b(f[b]);for(b=0;b<f.length;b++)e(f[b])}
function ie(a,b){if(void 0===b.__CE_state){var c=b.ownerDocument;if(c.defaultView||c.__CE_isImportDocument&&c.__CE_hasRegistry)if(c=a.a.get(b.localName)){c.constructionStack.push(b);var d=c.constructor;try{try{if(new d!==b)throw Error("The custom element constructor did not produce the element being upgraded.");}finally{c.constructionStack.pop()}}catch(h){throw b.__CE_state=2,h;}b.__CE_state=1;b.__CE_definition=c;if(c.attributeChangedCallback)for(c=c.observedAttributes,d=0;d<c.length;d++){var e=c[d],
f=b.getAttribute(e);null!==f&&a.attributeChangedCallback(b,e,null,f,null)}N(b)&&a.connectedCallback(b)}}}ee.prototype.connectedCallback=function(a){var b=a.__CE_definition;b.connectedCallback&&b.connectedCallback.call(a)};ee.prototype.disconnectedCallback=function(a){var b=a.__CE_definition;b.disconnectedCallback&&b.disconnectedCallback.call(a)};
ee.prototype.attributeChangedCallback=function(a,b,c,d,e){var f=a.__CE_definition;f.attributeChangedCallback&&-1<f.observedAttributes.indexOf(b)&&f.attributeChangedCallback.call(a,b,c,d,e)};function ke(a){var b=document;this.m=a;this.a=b;this.G=void 0;je(this.m,this.a);"loading"===this.a.readyState&&(this.G=new MutationObserver(this.b.bind(this)),this.G.observe(this.a,{childList:!0,subtree:!0}))}ke.prototype.disconnect=function(){this.G&&this.G.disconnect()};ke.prototype.b=function(a){var b=this.a.readyState;"interactive"!==b&&"complete"!==b||this.disconnect();for(b=0;b<a.length;b++)for(var c=a[b].addedNodes,d=0;d<c.length;d++)je(this.m,c[d])};function le(){var a=this;this.b=this.a=void 0;this.c=new Promise(function(b){a.b=b;a.a&&b(a.a)})}le.prototype.resolve=function(a){if(this.a)throw Error("Already resolved.");this.a=a;this.b&&this.b(a)};function S(a){this.ha=!1;this.m=a;this.la=new Map;this.ia=function(a){return a()};this.S=!1;this.ja=[];this.Ga=new ke(a)}
S.prototype.define=function(a,b){var c=this;if(!(b instanceof Function))throw new TypeError("Custom element constructors must be functions.");if(!be(a))throw new SyntaxError("The element name '"+a+"' is not valid.");if(this.m.a.get(a))throw Error("A custom element with name '"+a+"' has already been defined.");if(this.ha)throw Error("A custom element is already being defined.");this.ha=!0;try{var d=function(a){var b=e[a];if(void 0!==b&&!(b instanceof Function))throw Error("The '"+a+"' callback must be a function.");
return b},e=b.prototype;if(!(e instanceof Object))throw new TypeError("The custom element constructor's prototype is not an object.");var f=d("connectedCallback");var h=d("disconnectedCallback");var g=d("adoptedCallback");var k=d("attributeChangedCallback");var l=b.observedAttributes||[]}catch(m){return}finally{this.ha=!1}b={localName:a,constructor:b,connectedCallback:f,disconnectedCallback:h,adoptedCallback:g,attributeChangedCallback:k,observedAttributes:l,constructionStack:[]};fe(this.m,a,b);this.ja.push(b);
this.S||(this.S=!0,this.ia(function(){return me(c)}))};function me(a){if(!1!==a.S){a.S=!1;for(var b=a.ja,c=[],d=new Map,e=0;e<b.length;e++)d.set(b[e].localName,[]);je(a.m,document,{za:function(b){if(void 0===b.__CE_state){var e=b.localName,f=d.get(e);f?f.push(b):a.m.a.get(e)&&c.push(b)}}});for(e=0;e<c.length;e++)ie(a.m,c[e]);for(;0<b.length;){var f=b.shift();e=f.localName;f=d.get(f.localName);for(var h=0;h<f.length;h++)ie(a.m,f[h]);(e=a.la.get(e))&&e.resolve(void 0)}}}
S.prototype.get=function(a){if(a=this.m.a.get(a))return a.constructor};S.prototype.a=function(a){if(!be(a))return Promise.reject(new SyntaxError("'"+a+"' is not a valid custom element name."));var b=this.la.get(a);if(b)return b.c;b=new le;this.la.set(a,b);this.m.a.get(a)&&!this.ja.some(function(b){return b.localName===a})&&b.resolve(void 0);return b.c};S.prototype.b=function(a){this.Ga.disconnect();var b=this.ia;this.ia=function(c){return a(function(){return b(c)})}};
window.CustomElementRegistry=S;S.prototype.define=S.prototype.define;S.prototype.get=S.prototype.get;S.prototype.whenDefined=S.prototype.a;S.prototype.polyfillWrapFlushCallback=S.prototype.b;var ne=window.Document.prototype.createElement,oe=window.Document.prototype.createElementNS,pe=window.Document.prototype.importNode,qe=window.Document.prototype.prepend,re=window.Document.prototype.append,se=window.DocumentFragment.prototype.prepend,te=window.DocumentFragment.prototype.append,ue=window.Node.prototype.cloneNode,ve=window.Node.prototype.appendChild,we=window.Node.prototype.insertBefore,xe=window.Node.prototype.removeChild,ye=window.Node.prototype.replaceChild,ze=Object.getOwnPropertyDescriptor(window.Node.prototype,
"textContent"),Ae=window.Element.prototype.attachShadow,Be=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),Ce=window.Element.prototype.getAttribute,De=window.Element.prototype.setAttribute,Ee=window.Element.prototype.removeAttribute,Fe=window.Element.prototype.getAttributeNS,Ge=window.Element.prototype.setAttributeNS,He=window.Element.prototype.removeAttributeNS,Ie=window.Element.prototype.insertAdjacentElement,Je=window.Element.prototype.insertAdjacentHTML,Ke=window.Element.prototype.prepend,
Le=window.Element.prototype.append,Me=window.Element.prototype.before,Ne=window.Element.prototype.after,Oe=window.Element.prototype.replaceWith,Pe=window.Element.prototype.remove,Qe=window.HTMLElement,Re=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),Se=window.HTMLElement.prototype.insertAdjacentElement,Te=window.HTMLElement.prototype.insertAdjacentHTML;var Ue=new function(){};function Ve(){var a=We;window.HTMLElement=function(){function b(){var b=this.constructor,d=a.s.get(b);if(!d)throw Error("The custom element being constructed was not registered with `customElements`.");var e=d.constructionStack;if(0===e.length)return e=ne.call(document,d.localName),Object.setPrototypeOf(e,b.prototype),e.__CE_state=1,e.__CE_definition=d,a.b(e),e;d=e.length-1;var f=e[d];if(f===Ue)throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.");
e[d]=Ue;Object.setPrototypeOf(f,b.prototype);a.b(f);return f}b.prototype=Qe.prototype;return b}()};function Xe(a,b,c){function d(b){return function(c){for(var d=[],e=0;e<arguments.length;++e)d[e-0]=arguments[e];e=[];for(var f=[],l=0;l<d.length;l++){var m=d[l];m instanceof Element&&N(m)&&f.push(m);if(m instanceof DocumentFragment)for(m=m.firstChild;m;m=m.nextSibling)e.push(m);else e.push(m)}b.apply(this,d);for(d=0;d<f.length;d++)R(a,f[d]);if(N(this))for(d=0;d<e.length;d++)f=e[d],f instanceof Element&&Q(a,f)}}void 0!==c.$&&(b.prepend=d(c.$));void 0!==c.append&&(b.append=d(c.append))};function Ye(){var a=We;O(Document.prototype,"createElement",function(b){if(this.__CE_hasRegistry){var c=a.a.get(b);if(c)return new c.constructor}b=ne.call(this,b);a.b(b);return b});O(Document.prototype,"importNode",function(b,c){b=pe.call(this,b,c);this.__CE_hasRegistry?je(a,b):he(a,b);return b});O(Document.prototype,"createElementNS",function(b,c){if(this.__CE_hasRegistry&&(null===b||"http://www.w3.org/1999/xhtml"===b)){var d=a.a.get(c);if(d)return new d.constructor}b=oe.call(this,b,c);a.b(b);return b});
Xe(a,Document.prototype,{$:qe,append:re})};function Ze(){var a=We;function b(b,d){Object.defineProperty(b,"textContent",{enumerable:d.enumerable,configurable:!0,get:d.get,set:function(b){if(this.nodeType===Node.TEXT_NODE)d.set.call(this,b);else{var c=void 0;if(this.firstChild){var e=this.childNodes,g=e.length;if(0<g&&N(this)){c=Array(g);for(var k=0;k<g;k++)c[k]=e[k]}}d.set.call(this,b);if(c)for(b=0;b<c.length;b++)R(a,c[b])}}})}O(Node.prototype,"insertBefore",function(b,d){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);
b=we.call(this,b,d);if(N(this))for(d=0;d<c.length;d++)Q(a,c[d]);return b}c=N(b);d=we.call(this,b,d);c&&R(a,b);N(this)&&Q(a,b);return d});O(Node.prototype,"appendChild",function(b){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=ve.call(this,b);if(N(this))for(var e=0;e<c.length;e++)Q(a,c[e]);return b}c=N(b);e=ve.call(this,b);c&&R(a,b);N(this)&&Q(a,b);return e});O(Node.prototype,"cloneNode",function(b){b=ue.call(this,b);this.ownerDocument.__CE_hasRegistry?je(a,b):
he(a,b);return b});O(Node.prototype,"removeChild",function(b){var c=N(b),e=xe.call(this,b);c&&R(a,b);return e});O(Node.prototype,"replaceChild",function(b,d){if(b instanceof DocumentFragment){var c=Array.prototype.slice.apply(b.childNodes);b=ye.call(this,b,d);if(N(this))for(R(a,d),d=0;d<c.length;d++)Q(a,c[d]);return b}c=N(b);var f=ye.call(this,b,d),h=N(this);h&&R(a,d);c&&R(a,b);h&&Q(a,b);return f});ze&&ze.get?b(Node.prototype,ze):ge(a,function(a){b(a,{enumerable:!0,configurable:!0,get:function(){for(var a=
[],b=0;b<this.childNodes.length;b++)a.push(this.childNodes[b].textContent);return a.join("")},set:function(a){for(;this.firstChild;)xe.call(this,this.firstChild);ve.call(this,document.createTextNode(a))}})})};function $e(a){var b=Element.prototype;function c(b){return function(c){for(var d=[],e=0;e<arguments.length;++e)d[e-0]=arguments[e];e=[];for(var g=[],k=0;k<d.length;k++){var l=d[k];l instanceof Element&&N(l)&&g.push(l);if(l instanceof DocumentFragment)for(l=l.firstChild;l;l=l.nextSibling)e.push(l);else e.push(l)}b.apply(this,d);for(d=0;d<g.length;d++)R(a,g[d]);if(N(this))for(d=0;d<e.length;d++)g=e[d],g instanceof Element&&Q(a,g)}}void 0!==Me&&(b.before=c(Me));void 0!==Me&&(b.after=c(Ne));void 0!==
Oe&&O(b,"replaceWith",function(b){for(var c=[],d=0;d<arguments.length;++d)c[d-0]=arguments[d];d=[];for(var h=[],g=0;g<c.length;g++){var k=c[g];k instanceof Element&&N(k)&&h.push(k);if(k instanceof DocumentFragment)for(k=k.firstChild;k;k=k.nextSibling)d.push(k);else d.push(k)}g=N(this);Oe.apply(this,c);for(c=0;c<h.length;c++)R(a,h[c]);if(g)for(R(a,this),c=0;c<d.length;c++)h=d[c],h instanceof Element&&Q(a,h)});void 0!==Pe&&O(b,"remove",function(){var b=N(this);Pe.call(this);b&&R(a,this)})};function af(){var a=We;function b(b,c){Object.defineProperty(b,"innerHTML",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(b){var d=this,e=void 0;N(this)&&(e=[],de(this,function(a){a!==d&&e.push(a)}));c.set.call(this,b);if(e)for(var f=0;f<e.length;f++){var h=e[f];1===h.__CE_state&&a.disconnectedCallback(h)}this.ownerDocument.__CE_hasRegistry?je(a,this):he(a,this);return b}})}function c(b,c){O(b,"insertAdjacentElement",function(b,d){var e=N(d);b=c.call(this,b,d);e&&R(a,d);N(b)&&Q(a,
d);return b})}function d(b,c){function d(b,c){for(var d=[];b!==c;b=b.nextSibling)d.push(b);for(c=0;c<d.length;c++)je(a,d[c])}O(b,"insertAdjacentHTML",function(a,b){a=a.toLowerCase();if("beforebegin"===a){var e=this.previousSibling;c.call(this,a,b);d(e||this.parentNode.firstChild,this)}else if("afterbegin"===a)e=this.firstChild,c.call(this,a,b),d(this.firstChild,e);else if("beforeend"===a)e=this.lastChild,c.call(this,a,b),d(e||this.firstChild,null);else if("afterend"===a)e=this.nextSibling,c.call(this,
a,b),d(this.nextSibling,e);else throw new SyntaxError("The value provided ("+String(a)+") is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'.");})}Ae&&O(Element.prototype,"attachShadow",function(a){return this.__CE_shadowRoot=a=Ae.call(this,a)});Be&&Be.get?b(Element.prototype,Be):Re&&Re.get?b(HTMLElement.prototype,Re):ge(a,function(a){b(a,{enumerable:!0,configurable:!0,get:function(){return ue.call(this,!0).innerHTML},set:function(a){var b="template"===this.localName,c=b?this.content:
this,d=ne.call(document,this.localName);for(d.innerHTML=a;0<c.childNodes.length;)xe.call(c,c.childNodes[0]);for(a=b?d.content:d;0<a.childNodes.length;)ve.call(c,a.childNodes[0])}})});O(Element.prototype,"setAttribute",function(b,c){if(1!==this.__CE_state)return De.call(this,b,c);var d=Ce.call(this,b);De.call(this,b,c);c=Ce.call(this,b);a.attributeChangedCallback(this,b,d,c,null)});O(Element.prototype,"setAttributeNS",function(b,c,d){if(1!==this.__CE_state)return Ge.call(this,b,c,d);var e=Fe.call(this,
b,c);Ge.call(this,b,c,d);d=Fe.call(this,b,c);a.attributeChangedCallback(this,c,e,d,b)});O(Element.prototype,"removeAttribute",function(b){if(1!==this.__CE_state)return Ee.call(this,b);var c=Ce.call(this,b);Ee.call(this,b);null!==c&&a.attributeChangedCallback(this,b,c,null,null)});O(Element.prototype,"removeAttributeNS",function(b,c){if(1!==this.__CE_state)return He.call(this,b,c);var d=Fe.call(this,b,c);He.call(this,b,c);var e=Fe.call(this,b,c);d!==e&&a.attributeChangedCallback(this,c,d,e,b)});Se?
c(HTMLElement.prototype,Se):Ie?c(Element.prototype,Ie):console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched.");Te?d(HTMLElement.prototype,Te):Je?d(Element.prototype,Je):console.warn("Custom Elements: `Element#insertAdjacentHTML` was not patched.");Xe(a,Element.prototype,{$:Ke,append:Le});$e(a)};var bf=window.customElements;if(!bf||bf.forcePolyfill||"function"!=typeof bf.define||"function"!=typeof bf.get){var We=new ee;Ve();Ye();Xe(We,DocumentFragment.prototype,{$:se,append:te});Ze();af();document.__CE_hasRegistry=!0;var customElements=new S(We);Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:customElements})};function cf(){this.end=this.start=0;this.rules=this.parent=this.previous=null;this.cssText=this.parsedCssText="";this.atRule=!1;this.type=0;this.parsedSelector=this.selector=this.keyframesName=""}
function df(a){a=a.replace(ef,"").replace(ff,"");var b=jf,c=a,d=new cf;d.start=0;d.end=c.length;for(var e=d,f=0,h=c.length;f<h;f++)if("{"===c[f]){e.rules||(e.rules=[]);var g=e,k=g.rules[g.rules.length-1]||null;e=new cf;e.start=f+1;e.parent=g;e.previous=k;g.rules.push(e)}else"}"===c[f]&&(e.end=f+1,e=e.parent||d);return b(d,a)}
function jf(a,b){var c=b.substring(a.start,a.end-1);a.parsedCssText=a.cssText=c.trim();a.parent&&(c=b.substring(a.previous?a.previous.end:a.parent.start,a.start-1),c=kf(c),c=c.replace(lf," "),c=c.substring(c.lastIndexOf(";")+1),c=a.parsedSelector=a.selector=c.trim(),a.atRule=0===c.indexOf("@"),a.atRule?0===c.indexOf("@media")?a.type=mf:c.match(nf)&&(a.type=of,a.keyframesName=a.selector.split(lf).pop()):a.type=0===c.indexOf("--")?pf:qf);if(c=a.rules)for(var d=0,e=c.length,f;d<e&&(f=c[d]);d++)jf(f,
b);return a}function kf(a){return a.replace(/\\([0-9a-f]{1,6})\s/gi,function(a,c){a=c;for(c=6-a.length;c--;)a="0"+a;return"\\"+a})}
function rf(a,b,c){c=void 0===c?"":c;var d="";if(a.cssText||a.rules){var e=a.rules,f;if(f=e)f=e[0],f=!(f&&f.selector&&0===f.selector.indexOf("--"));if(f){f=0;for(var h=e.length,g;f<h&&(g=e[f]);f++)d=rf(g,b,d)}else b?b=a.cssText:(b=a.cssText,b=b.replace(sf,"").replace(tf,""),b=b.replace(uf,"").replace(vf,"")),(d=b.trim())&&(d=" "+d+"\n")}d&&(a.selector&&(c+=a.selector+" {\n"),c+=d,a.selector&&(c+="}\n\n"));return c}
var qf=1,of=7,mf=4,pf=1E3,ef=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,ff=/@import[^;]*;/gim,sf=/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,tf=/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,uf=/@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,vf=/[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,nf=/^@[^\s]*keyframes/,lf=/\s+/g;var T=!(window.ShadyDOM&&window.ShadyDOM.inUse),wf;function xf(a){wf=a&&a.shimcssproperties?!1:T||!(navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/)||!window.CSS||!CSS.supports||!CSS.supports("box-shadow","0 0 0 var(--foo)"))}window.ShadyCSS&&void 0!==window.ShadyCSS.nativeCss?wf=window.ShadyCSS.nativeCss:window.ShadyCSS?(xf(window.ShadyCSS),window.ShadyCSS=void 0):xf(window.WebComponents&&window.WebComponents.flags);var U=wf;var yf=/(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gi,zf=/(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi,Af=/(--[\w-]+)\s*([:,;)]|$)/gi,Bf=/(animation\s*:)|(animation-name\s*:)/,Cf=/@media\s(.*)/,Df=/\{[^}]*\}/g;var Ef=new Set;function Ff(a,b){if(!a)return"";"string"===typeof a&&(a=df(a));b&&Gf(a,b);return rf(a,U)}function Hf(a){!a.__cssRules&&a.textContent&&(a.__cssRules=df(a.textContent));return a.__cssRules||null}function If(a){return!!a.parent&&a.parent.type===of}function Gf(a,b,c,d){if(a){var e=!1,f=a.type;if(d&&f===mf){var h=a.selector.match(Cf);h&&(window.matchMedia(h[1]).matches||(e=!0))}f===qf?b(a):c&&f===of?c(a):f===pf&&(e=!0);if((a=a.rules)&&!e){e=0;f=a.length;for(var g;e<f&&(g=a[e]);e++)Gf(g,b,c,d)}}}
function Jf(a,b,c,d){var e=document.createElement("style");b&&e.setAttribute("scope",b);e.textContent=a;Kf(e,c,d);return e}var Lf=null;function Kf(a,b,c){b=b||document.head;b.insertBefore(a,c&&c.nextSibling||b.firstChild);Lf?a.compareDocumentPosition(Lf)===Node.DOCUMENT_POSITION_PRECEDING&&(Lf=a):Lf=a}
function Mf(a,b){var c=a.indexOf("var(");if(-1===c)return b(a,"","","");a:{var d=0;var e=c+3;for(var f=a.length;e<f;e++)if("("===a[e])d++;else if(")"===a[e]&&0===--d)break a;e=-1}d=a.substring(c+4,e);c=a.substring(0,c);a=Mf(a.substring(e+1),b);e=d.indexOf(",");return-1===e?b(c,d.trim(),"",a):b(c,d.substring(0,e).trim(),d.substring(e+1).trim(),a)}function Nf(a,b){T?a.setAttribute("class",b):window.ShadyDOM.nativeMethods.setAttribute.call(a,"class",b)}
function Of(a){var b=a.localName,c="";b?-1<b.indexOf("-")||(c=b,b=a.getAttribute&&a.getAttribute("is")||""):(b=a.is,c=a.extends);return{is:b,R:c}};function Pf(){}function Qf(a,b,c){var d=V;a.__styleScoped?a.__styleScoped=null:Rf(d,a,b||"",c)}function Rf(a,b,c,d){b.nodeType===Node.ELEMENT_NODE&&Sf(b,c,d);if(b="template"===b.localName?(b.content||b.ib).childNodes:b.children||b.childNodes)for(var e=0;e<b.length;e++)Rf(a,b[e],c,d)}
function Sf(a,b,c){if(b)if(a.classList)c?(a.classList.remove("style-scope"),a.classList.remove(b)):(a.classList.add("style-scope"),a.classList.add(b));else if(a.getAttribute){var d=a.getAttribute(Tf);c?d&&(b=d.replace("style-scope","").replace(b,""),Nf(a,b)):Nf(a,(d?d+" ":"")+"style-scope "+b)}}function Uf(a,b,c){var d=V,e=a.__cssBuild;T||"shady"===e?b=Ff(b,c):(a=Of(a),b=Vf(d,b,a.is,a.R,c)+"\n\n");return b.trim()}
function Vf(a,b,c,d,e){var f=Wf(c,d);c=c?Xf+c:"";return Ff(b,function(b){b.c||(b.selector=b.o=Yf(a,b,a.b,c,f),b.c=!0);e&&e(b,c,f)})}function Wf(a,b){return b?"[is="+a+"]":a}function Yf(a,b,c,d,e){var f=b.selector.split(Zf);if(!If(b)){b=0;for(var h=f.length,g;b<h&&(g=f[b]);b++)f[b]=c.call(a,g,d,e)}return f.join(Zf)}function $f(a){return a.replace(ag,function(a,c,d){-1<d.indexOf("+")?d=d.replace(/\+/g,"___"):-1<d.indexOf("___")&&(d=d.replace(/___/g,"+"));return":"+c+"("+d+")"})}
Pf.prototype.b=function(a,b,c){var d=!1;a=a.trim();var e=ag.test(a);e&&(a=a.replace(ag,function(a,b,c){return":"+b+"("+c.replace(/\s/g,"")+")"}),a=$f(a));a=a.replace(bg,cg+" $1");a=a.replace(dg,function(a,e,g){d||(a=eg(g,e,b,c),d=d||a.stop,e=a.Na,g=a.value);return e+g});e&&(a=$f(a));return a};
function eg(a,b,c,d){var e=a.indexOf(fg);0<=a.indexOf(cg)?a=gg(a,d):0!==e&&(a=c?hg(a,c):a);c=!1;0<=e&&(b="",c=!0);if(c){var f=!0;c&&(a=a.replace(ig,function(a,b){return" > "+b}))}a=a.replace(jg,function(a,b,c){return'[dir="'+c+'"] '+b+", "+b+'[dir="'+c+'"]'});return{value:a,Na:b,stop:f}}function hg(a,b){a=a.split(kg);a[0]+=b;return a.join(kg)}
function gg(a,b){var c=a.match(lg);return(c=c&&c[2].trim()||"")?c[0].match(mg)?a.replace(lg,function(a,c,f){return b+f}):c.split(mg)[0]===b?c:ng:a.replace(cg,b)}function og(a){a.selector===pg&&(a.selector="html")}Pf.prototype.c=function(a){return a.match(fg)?this.b(a,qg):hg(a.trim(),qg)};q.Object.defineProperties(Pf.prototype,{a:{configurable:!0,enumerable:!0,get:function(){return"style-scope"}}});
var ag=/:(nth[-\w]+)\(([^)]+)\)/,qg=":not(.style-scope)",Zf=",",dg=/(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=[])+)/g,mg=/[[.:#*]/,cg=":host",pg=":root",fg="::slotted",bg=new RegExp("^("+fg+")"),lg=/(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,ig=/(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,jg=/(.*):dir\((?:(ltr|rtl))\)/,Xf=".",kg=":",Tf="class",ng="should_not_match",V=new Pf;function rg(a,b,c,d){this.B=a||null;this.b=b||null;this.ma=c||[];this.K=null;this.R=d||"";this.a=this.u=this.F=null}function W(a){return a?a.__styleInfo:null}function sg(a,b){return a.__styleInfo=b}rg.prototype.c=function(){return this.B};rg.prototype._getStyleRules=rg.prototype.c;function tg(a){var b=this.matches||this.matchesSelector||this.mozMatchesSelector||this.msMatchesSelector||this.oMatchesSelector||this.webkitMatchesSelector;return b&&b.call(this,a)}var ug=navigator.userAgent.match("Trident");function vg(){}function wg(a){var b={},c=[],d=0;Gf(a,function(a){xg(a);a.index=d++;a=a.l.cssText;for(var c;c=Af.exec(a);){var e=c[1];":"!==c[2]&&(b[e]=!0)}},function(a){c.push(a)});a.b=c;a=[];for(var e in b)a.push(e);return a}
function xg(a){if(!a.l){var b={},c={};yg(a,c)&&(b.A=c,a.rules=null);b.cssText=a.parsedCssText.replace(Df,"").replace(yf,"");a.l=b}}function yg(a,b){var c=a.l;if(c){if(c.A)return Object.assign(b,c.A),!0}else{c=a.parsedCssText;for(var d;a=yf.exec(c);){d=(a[2]||a[3]).trim();if("inherit"!==d||"unset"!==d)b[a[1].trim()]=d;d=!0}return d}}
function zg(a,b,c){b&&(b=0<=b.indexOf(";")?Ag(a,b,c):Mf(b,function(b,e,f,h){if(!e)return b+h;(e=zg(a,c[e],c))&&"initial"!==e?"apply-shim-inherit"===e&&(e="inherit"):e=zg(a,c[f]||f,c)||f;return b+(e||"")+h}));return b&&b.trim()||""}
function Ag(a,b,c){b=b.split(";");for(var d=0,e,f;d<b.length;d++)if(e=b[d]){zf.lastIndex=0;if(f=zf.exec(e))e=zg(a,c[f[1]],c);else if(f=e.indexOf(":"),-1!==f){var h=e.substring(f);h=h.trim();h=zg(a,h,c)||h;e=e.substring(0,f)+h}b[d]=e&&e.lastIndexOf(";")===e.length-1?e.slice(0,-1):e||""}return b.join(";")}
function Bg(a,b){var c={},d=[];Gf(a,function(a){a.l||xg(a);var e=a.o||a.parsedSelector;b&&a.l.A&&e&&tg.call(b,e)&&(yg(a,c),a=a.index,e=parseInt(a/32,10),d[e]=(d[e]||0)|1<<a%32)},null,!0);return{A:c,key:d}}
function Cg(a,b,c,d){b.l||xg(b);if(b.l.A){var e=Of(a);a=e.is;e=e.R;e=a?Wf(a,e):"html";var f=b.parsedSelector,h=":host > *"===f||"html"===f,g=0===f.indexOf(":host")&&!h;"shady"===c&&(h=f===e+" > *."+e||-1!==f.indexOf("html"),g=!h&&0===f.indexOf(e));"shadow"===c&&(h=":host > *"===f||"html"===f,g=g&&!h);if(h||g)c=e,g&&(b.o||(b.o=Yf(V,b,V.b,a?Xf+a:"",e)),c=b.o||e),d({Za:c,Ta:g,rb:h})}}
function Dg(a,b){var c={},d={},e=b&&b.__cssBuild;Gf(b,function(b){Cg(a,b,e,function(e){tg.call(a.b||a,e.Za)&&(e.Ta?yg(b,c):yg(b,d))})},null,!0);return{Ya:d,Ra:c}}
function Eg(a,b,c,d){var e=Of(b),f=Wf(e.is,e.R),h=new RegExp("(?:^|[^.#[:])"+(b.extends?"\\"+f.slice(0,-1)+"\\]":f)+"($|[.:[\\s>+~])");e=W(b).B;var g=Fg(e,d);return Uf(b,e,function(b){var e="";b.l||xg(b);b.l.cssText&&(e=Ag(a,b.l.cssText,c));b.cssText=e;if(!T&&!If(b)&&b.cssText){var k=e=b.cssText;null==b.ta&&(b.ta=Bf.test(e));if(b.ta)if(null==b.Z){b.Z=[];for(var n in g)k=g[n],k=k(e),e!==k&&(e=k,b.Z.push(n))}else{for(n=0;n<b.Z.length;++n)k=g[b.Z[n]],e=k(e);k=e}b.cssText=k;b.o=b.o||b.selector;e="."+
d;n=b.o.split(",");k=0;for(var t=n.length,B;k<t&&(B=n[k]);k++)n[k]=B.match(h)?B.replace(f,e):e+" "+B;b.selector=n.join(",")}})}function Fg(a,b){a=a.b;var c={};if(!T&&a)for(var d=0,e=a[d];d<a.length;e=a[++d]){var f=e,h=b;f.i=new RegExp("\\b"+f.keyframesName+"(?!\\B|-)","g");f.a=f.keyframesName+"-"+h;f.o=f.o||f.selector;f.selector=f.o.replace(f.keyframesName,f.a);c[e.keyframesName]=Gg(e)}return c}function Gg(a){return function(b){return b.replace(a.i,a.a)}}
function Hg(a,b){var c=Ig,d=Hf(a);a.textContent=Ff(d,function(a){var d=a.cssText=a.parsedCssText;a.l&&a.l.cssText&&(d=d.replace(sf,"").replace(tf,""),a.cssText=Ag(c,d,b))})}q.Object.defineProperties(vg.prototype,{a:{configurable:!0,enumerable:!0,get:function(){return"x-scope"}}});var Ig=new vg;var Jg={},Kg=window.customElements;if(Kg&&!T){var Lg=Kg.define;Kg.define=function(a,b,c){var d=document.createComment(" Shady DOM styles for "+a+" "),e=document.head;e.insertBefore(d,(Lf?Lf.nextSibling:null)||e.firstChild);Lf=d;Jg[a]=d;return Lg.call(Kg,a,b,c)}};function Mg(){this.cache={}}Mg.prototype.store=function(a,b,c,d){var e=this.cache[a]||[];e.push({A:b,styleElement:c,u:d});100<e.length&&e.shift();this.cache[a]=e};Mg.prototype.fetch=function(a,b,c){if(a=this.cache[a])for(var d=a.length-1;0<=d;d--){var e=a[d],f;a:{for(f=0;f<c.length;f++){var h=c[f];if(e.A[h]!==b[h]){f=!1;break a}}f=!0}if(f)return e}};function Ng(){}
function Og(a){for(var b=0;b<a.length;b++){var c=a[b];if(c.target!==document.documentElement&&c.target!==document.head)for(var d=0;d<c.addedNodes.length;d++){var e=c.addedNodes[d];if(e.nodeType===Node.ELEMENT_NODE){var f=e.getRootNode();var h=e;var g=[];h.classList?g=Array.from(h.classList):h instanceof window.SVGElement&&h.hasAttribute("class")&&(g=h.getAttribute("class").split(/\s+/));h=g;g=h.indexOf(V.a);if((h=-1<g?h[g+1]:"")&&f===e.ownerDocument)Qf(e,h,!0);else if(f.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&
(f=f.host))if(f=Of(f).is,h===f)for(e=window.ShadyDOM.nativeMethods.querySelectorAll.call(e,":not(."+V.a+")"),f=0;f<e.length;f++)Sf(e[f],h);else h&&Qf(e,h,!0),Qf(e,f)}}}}
if(!T){var Pg=new MutationObserver(Og),Qg=function(a){Pg.observe(a,{childList:!0,subtree:!0})};if(window.customElements&&!window.customElements.polyfillWrapFlushCallback)Qg(document);else{var Rg=function(){Qg(document.body)};window.HTMLImports?window.HTMLImports.whenReady(Rg):requestAnimationFrame(function(){if("loading"===document.readyState){var a=function(){Rg();document.removeEventListener("readystatechange",a)};document.addEventListener("readystatechange",a)}else Rg()})}Ng=function(){Og(Pg.takeRecords())}}
var Sg=Ng;var Tg={};var Ug=Promise.resolve();function Vg(a){if(a=Tg[a])a._applyShimCurrentVersion=a._applyShimCurrentVersion||0,a._applyShimValidatingVersion=a._applyShimValidatingVersion||0,a._applyShimNextVersion=(a._applyShimNextVersion||0)+1}function Wg(a){return a._applyShimCurrentVersion===a._applyShimNextVersion}function Xg(a){a._applyShimValidatingVersion=a._applyShimNextVersion;a.sa||(a.sa=!0,Ug.then(function(){a._applyShimCurrentVersion=a._applyShimNextVersion;a.sa=!1}))};var Yg=null,Zg=window.HTMLImports&&window.HTMLImports.whenReady||null,$g;function ah(a){requestAnimationFrame(function(){Zg?Zg(a):(Yg||(Yg=new Promise(function(a){$g=a}),"complete"===document.readyState?$g():document.addEventListener("readystatechange",function(){"complete"===document.readyState&&$g()})),Yg.then(function(){a&&a()}))})};var bh=new Mg;function X(){var a=this;this.N={};this.c=document.documentElement;var b=new cf;b.rules=[];this.i=sg(this.c,new rg(b));this.s=!1;this.b=this.a=null;ah(function(){ch(a)})}p=X.prototype;p.Ba=function(){Sg()};p.Pa=function(a){return Hf(a)};p.ab=function(a){return Ff(a)};
p.prepareTemplate=function(a,b,c){if(!a.La){a.La=!0;a.name=b;a.extends=c;Tg[b]=a;var d=(d=a.content.querySelector("style"))?d.getAttribute("css-build")||"":"";var e=[];for(var f=a.content.querySelectorAll("style"),h=0;h<f.length;h++){var g=f[h];if(g.hasAttribute("shady-unscoped")){if(!T){var k=g.textContent;Ef.has(k)||(Ef.add(k),k=g.cloneNode(!0),document.head.appendChild(k));g.parentNode.removeChild(g)}}else e.push(g.textContent),g.parentNode.removeChild(g)}e=e.join("").trim();c={is:b,extends:c,
gb:d};T||Qf(a.content,b);ch(this);f=zf.test(e)||yf.test(e);zf.lastIndex=0;yf.lastIndex=0;e=df(e);f&&U&&this.a&&this.a.transformRules(e,b);a._styleAst=e;a.a=d;d=[];U||(d=wg(a._styleAst));if(!d.length||U)e=T?a.content:null,b=Jg[b],f=Uf(c,a._styleAst),b=f.length?Jf(f,c.is,e,b):void 0,a.ra=b;a.Ka=d}};
function dh(a){!a.b&&window.ShadyCSS&&window.ShadyCSS.CustomStyleInterface&&(a.b=window.ShadyCSS.CustomStyleInterface,a.b.transformCallback=function(b){a.ya(b)},a.b.validateCallback=function(){requestAnimationFrame(function(){(a.b.enqueued||a.s)&&a.I()})})}function ch(a){!a.a&&window.ShadyCSS&&window.ShadyCSS.ApplyShim&&(a.a=window.ShadyCSS.ApplyShim,a.a.invalidCallback=Vg);dh(a)}
p.I=function(){ch(this);if(this.b){var a=this.b.processStyles();if(this.b.enqueued){if(U)for(var b=0;b<a.length;b++){var c=this.b.getStyleForCustomStyle(a[b]);if(c&&U&&this.a){var d=Hf(c);ch(this);this.a.transformRules(d);c.textContent=Ff(d)}}else for(eh(this,this.c,this.i),b=0;b<a.length;b++)(c=this.b.getStyleForCustomStyle(a[b]))&&Hg(c,this.i.F);this.b.enqueued=!1;this.s&&!U&&this.styleDocument()}}};
p.styleElement=function(a,b){var c=Of(a).is,d=W(a);if(!d){var e=Of(a);d=e.is;e=e.R;var f=Jg[d];d=Tg[d];if(d){var h=d._styleAst;var g=d.Ka}d=sg(a,new rg(h,f,g,e))}a!==this.c&&(this.s=!0);b&&(d.K=d.K||{},Object.assign(d.K,b));if(U){if(d.K){b=d.K;for(var k in b)null===k?a.style.removeProperty(k):a.style.setProperty(k,b[k])}if(((k=Tg[c])||a===this.c)&&k&&k.ra&&!Wg(k)){if(Wg(k)||k._applyShimValidatingVersion!==k._applyShimNextVersion)ch(this),this.a&&this.a.transformRules(k._styleAst,c),k.ra.textContent=
Uf(a,d.B),Xg(k);T&&(c=a.shadowRoot)&&(c.querySelector("style").textContent=Uf(a,d.B));d.B=k._styleAst}}else if(eh(this,a,d),d.ma&&d.ma.length){c=d;k=Of(a).is;d=(b=bh.fetch(k,c.F,c.ma))?b.styleElement:null;h=c.u;(g=b&&b.u)||(g=this.N[k]=(this.N[k]||0)+1,g=k+"-"+g);c.u=g;g=c.u;e=Ig;e=d?d.textContent||"":Eg(e,a,c.F,g);f=W(a);var l=f.a;l&&!T&&l!==d&&(l._useCount--,0>=l._useCount&&l.parentNode&&l.parentNode.removeChild(l));T?f.a?(f.a.textContent=e,d=f.a):e&&(d=Jf(e,g,a.shadowRoot,f.b)):d?d.parentNode||
(ug&&-1<e.indexOf("@media")&&(d.textContent=e),Kf(d,null,f.b)):e&&(d=Jf(e,g,null,f.b));d&&(d._useCount=d._useCount||0,f.a!=d&&d._useCount++,f.a=d);g=d;T||(d=c.u,f=e=a.getAttribute("class")||"",h&&(f=e.replace(new RegExp("\\s*x-scope\\s*"+h+"\\s*","g")," ")),f+=(f?" ":"")+"x-scope "+d,e!==f&&Nf(a,f));b||bh.store(k,c.F,g,c.u)}};function fh(a,b){return(b=b.getRootNode().host)?W(b)?b:fh(a,b):a.c}
function eh(a,b,c){a=fh(a,b);var d=W(a);a=Object.create(d.F||null);var e=Dg(b,c.B);b=Bg(d.B,b).A;Object.assign(a,e.Ra,b,e.Ya);b=c.K;for(var f in b)if((e=b[f])||0===e)a[f]=e;f=Ig;b=Object.getOwnPropertyNames(a);for(e=0;e<b.length;e++)d=b[e],a[d]=zg(f,a[d],a);c.F=a}p.styleDocument=function(a){this.styleSubtree(this.c,a)};
p.styleSubtree=function(a,b){var c=a.shadowRoot;(c||a===this.c)&&this.styleElement(a,b);if(b=c&&(c.children||c.childNodes))for(a=0;a<b.length;a++)this.styleSubtree(b[a]);else if(a=a.children||a.childNodes)for(b=0;b<a.length;b++)this.styleSubtree(a[b])};p.ya=function(a){var b=this,c=Hf(a);Gf(c,function(a){if(T)og(a);else{var c=V;a.selector=a.parsedSelector;og(a);a.selector=a.o=Yf(c,a,c.c,void 0,void 0)}U&&(ch(b),b.a&&b.a.transformRule(a))});U?a.textContent=Ff(c):this.i.B.rules.push(c)};
p.getComputedStyleValue=function(a,b){var c;U||(c=(W(a)||W(fh(this,a))).F[b]);return(c=c||window.getComputedStyle(a).getPropertyValue(b))?c.trim():""};p.$a=function(a,b){var c=a.getRootNode();b=b?b.split(/\s/):[];c=c.host&&c.host.localName;if(!c){var d=a.getAttribute("class");if(d){d=d.split(/\s/);for(var e=0;e<d.length;e++)if(d[e]===V.a){c=d[e+1];break}}}c&&b.push(V.a,c);U||(c=W(a))&&c.u&&b.push(Ig.a,c.u);Nf(a,b.join(" "))};p.Ma=function(a){return W(a)};X.prototype.flush=X.prototype.Ba;
X.prototype.prepareTemplate=X.prototype.prepareTemplate;X.prototype.styleElement=X.prototype.styleElement;X.prototype.styleDocument=X.prototype.styleDocument;X.prototype.styleSubtree=X.prototype.styleSubtree;X.prototype.getComputedStyleValue=X.prototype.getComputedStyleValue;X.prototype.setElementClass=X.prototype.$a;X.prototype._styleInfoForNode=X.prototype.Ma;X.prototype.transformCustomStyleForDocument=X.prototype.ya;X.prototype.getStyleAst=X.prototype.Pa;X.prototype.styleAstToString=X.prototype.ab;
X.prototype.flushCustomStyles=X.prototype.I;Object.defineProperties(X.prototype,{nativeShadow:{get:function(){return T}},nativeCss:{get:function(){return U}}});var Y=new X,gh,hh;window.ShadyCSS&&(gh=window.ShadyCSS.ApplyShim,hh=window.ShadyCSS.CustomStyleInterface);window.ShadyCSS={ScopingShim:Y,prepareTemplate:function(a,b,c){Y.I();Y.prepareTemplate(a,b,c)},styleSubtree:function(a,b){Y.I();Y.styleSubtree(a,b)},styleElement:function(a){Y.I();Y.styleElement(a)},styleDocument:function(a){Y.I();Y.styleDocument(a)},getComputedStyleValue:function(a,b){return Y.getComputedStyleValue(a,b)},nativeCss:U,nativeShadow:T};gh&&(window.ShadyCSS.ApplyShim=gh);
hh&&(window.ShadyCSS.CustomStyleInterface=hh);var ih=window.customElements,jh=window.HTMLImports,kh=window.HTMLTemplateElement;window.WebComponents=window.WebComponents||{};if(ih&&ih.polyfillWrapFlushCallback){var lh,mh=function(){if(lh){kh.M&&kh.M(window.document);var a=lh;lh=null;a();return!0}},nh=jh.whenReady;ih.polyfillWrapFlushCallback(function(a){lh=a;nh(mh)});jh.whenReady=function(a){nh(function(){mh()?jh.whenReady(a):a()})}}
jh.whenReady(function(){requestAnimationFrame(function(){window.WebComponents.ready=!0;document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})});var oh=document.createElement("style");oh.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var ph=document.querySelector("head");ph.insertBefore(oh,ph.firstChild);}).call(this);
//# sourceMappingURL=webcomponents-lite.js.map

View File

@@ -17,9 +17,6 @@ rm -rf $OUTPUT_DIR $OUTPUT_DIR_ES5 $BUILD_DIR $BUILD_TRANSLATIONS_DIR
./node_modules/.bin/gulp build-translations gen-icons
NODE_ENV=production ./node_modules/.bin/webpack
# Temp for Hass.io
script/update_mdi.py
# Generate the __init__ file
echo "VERSION = '`git rev-parse HEAD`'" >> $OUTPUT_DIR/__init__.py
echo "CREATED_AT = `date +%s`" >> $OUTPUT_DIR/__init__.py

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env python3
"""Download the latest Polymer v1 iconset for materialdesignicons.com."""
import os
import re
import sys
import urllib.request
GETTING_STARTED_URL = ('https://raw.githubusercontent.com/Templarian/'
'MaterialDesign/master/site/getting-started.savvy')
DOWNLOAD_LINK = re.compile(r'(/api/download/polymer/v1/([A-Z0-9-]{36}))')
START_ICONSET = '<iron-iconset-svg'
OUTPUT_BASE = 'hass_frontend'
ICONSET_OUTPUT = os.path.join(OUTPUT_BASE, 'mdi.html')
def get_text(url):
with urllib.request.urlopen(url) as f:
return f.read().decode('utf-8')
def get_remote_version():
"""Get current version and download link."""
gs_page = get_text(GETTING_STARTED_URL)
mdi_download = re.search(DOWNLOAD_LINK, gs_page)
if not mdi_download:
print("Unable to find download link")
sys.exit()
return 'https://materialdesignicons.com' + mdi_download.group(1)
def clean_component(source):
"""Clean component."""
return source[source.index(START_ICONSET):]
def write_component(source):
"""Write component."""
with open(ICONSET_OUTPUT, 'w') as outp:
print('Writing icons to', ICONSET_OUTPUT)
outp.write(source)
def main():
"""Main section of the script."""
# All scripts should have their current work dir set to project root
if os.path.basename(os.getcwd()) == 'script':
os.chdir('..')
print("materialdesignicons.com icon updater")
remote_url = get_remote_version()
source = clean_component(get_text(remote_url))
write_component(source)
print('Updated to latest version')
if __name__ == '__main__':
main()

View File

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

View File

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

View File

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

View File

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

View File

@@ -203,7 +203,7 @@ class HaWeatherCard extends
}
getUnit(measure) {
const lengthUnit = this.hass.config.core.unit_system.length || '';
const lengthUnit = this.hass.config.unit_system.length || '';
switch (measure) {
case 'air_pressure':
return lengthUnit === 'km' ? 'hPa' : 'inHg';
@@ -212,7 +212,7 @@ class HaWeatherCard extends
case 'precipitation':
return lengthUnit === 'km' ? 'mm' : 'in';
default:
return this.hass.config.core.unit_system[measure] || '';
return this.hass.config.unit_system[measure] || '';
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
export default function canToggleDomain(hass, domain) {
const services = hass.config.services[domain];
const services = hass.services[domain];
if (!services) { return false; }
if (domain === 'lock') {

View File

@@ -1,11 +0,0 @@
export default function parseQuery(queryString) {
const query = {};
const items = queryString.split('&');
for (let i = 0; i < items.length; i++) {
const item = items[i].split('=');
const key = decodeURIComponent(item[0]);
const value = item.length > 1 ? decodeURIComponent(item[1]) : undefined;
query[key] = value;
}
return query;
}

View File

@@ -1,8 +1,9 @@
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import './ha-state-icon.js';
import '../ha-icon.js';
import computeStateDomain from '../../common/entity/compute_state_domain.js';
import stateIcon from '../../common/entity/state_icon.js';
class StateBadge extends PolymerElement {
static get template() {
@@ -20,26 +21,31 @@ class StateBadge extends PolymerElement {
line-height: 40px;
}
ha-state-icon {
ha-icon {
transition: color .3s ease-in-out, filter .3s ease-in-out;
}
/* Color the icon if light or sun is on */
ha-state-icon[data-domain=light][data-state=on],
ha-state-icon[data-domain=switch][data-state=on],
ha-state-icon[data-domain=binary_sensor][data-state=on],
ha-state-icon[data-domain=fan][data-state=on],
ha-state-icon[data-domain=sun][data-state=above_horizon] {
ha-icon[data-domain=light][data-state=on],
ha-icon[data-domain=switch][data-state=on],
ha-icon[data-domain=binary_sensor][data-state=on],
ha-icon[data-domain=fan][data-state=on],
ha-icon[data-domain=sun][data-state=above_horizon] {
color: var(--paper-item-icon-active-color, #FDD835);
}
/* Color the icon if unavailable */
ha-state-icon[data-state=unavailable] {
ha-icon[data-state=unavailable] {
color: var(--state-icon-unavailable-color);
}
</style>
<ha-state-icon id="icon" state-obj="[[stateObj]]" data-domain$="[[computeDomain(stateObj)]]" data-state$="[[stateObj.state]]"></ha-state-icon>
<ha-icon
id="icon"
data-domain$="[[_computeDomain(stateObj)]]"
data-state$="[[stateObj.state]]"
icon="[[_computeIcon(stateObj)]]"
></ha-icon>
`;
}
@@ -47,19 +53,22 @@ class StateBadge extends PolymerElement {
return {
stateObj: {
type: Object,
observer: 'updateIconAppearance',
observer: '_updateIconAppearance',
},
overrideIcon: String
};
}
computeDomain(stateObj) {
_computeDomain(stateObj) {
return computeStateDomain(stateObj);
}
_computeIcon(stateObj) {
return this.overrideIcon || stateIcon(stateObj);
}
updateIconAppearance(newVal) {
_updateIconAppearance(newVal) {
const iconStyle = {
display: 'inline',
color: '',
filter: '',
};

View File

@@ -60,7 +60,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
computeCurrentStatus(hass, stateObj) {
if (!hass || !stateObj) return null;
if (stateObj.attributes.current_temperature != null) {
return `${stateObj.attributes.current_temperature} ${hass.config.core.unit_system.temperature}`;
return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`;
}
if (stateObj.attributes.current_humidity != null) {
return `${stateObj.attributes.current_humidity} %`;
@@ -73,9 +73,9 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
// 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) {
return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.core.unit_system.temperature}`;
return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
} else if (stateObj.attributes.temperature != null) {
return `${stateObj.attributes.temperature} ${hass.config.core.unit_system.temperature}`;
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
} else if (stateObj.attributes.target_humidity_low != null &&
stateObj.attributes.target_humidity_high != null) {
return `${stateObj.attributes.target_humidity_low} - ${stateObj.attributes.target_humidity_high} %`;

View File

@@ -23,7 +23,7 @@ class HaForm extends EventsMixin(PolymerElement) {
</style>
<template is="dom-if" if="[[_isArray(schema)]]" restamp="">
<template is="dom-if" if="[[error.base]]">
[[computeError(error.base, schema)]]
<div class='error'>[[computeError(error.base, schema)]]</div>
</template>
<template is="dom-repeat" items="[[schema]]">
@@ -155,7 +155,10 @@ class HaForm extends EventsMixin(PolymerElement) {
}
_getValue(obj, item) {
return obj[item.name];
if (obj) {
return obj[item.name];
}
return null;
}
_valueChanged(ev) {

View File

@@ -3,6 +3,12 @@ import EventsMixin from '../mixins/events-mixin.js';
let loaded = null;
/**
* White list allowed svg tag.
* Only put in the tag used in QR code, can be extend in future.
*/
const svgWhiteList = ['svg', 'path'];
/*
* @appliesMixin EventsMixin
*/
@@ -12,7 +18,11 @@ class HaMarkdown extends EventsMixin(PolymerElement) {
content: {
type: String,
observer: '_render',
}
},
allowSvg: {
type: Boolean,
value: false,
},
};
}
@@ -50,7 +60,11 @@ class HaMarkdown extends EventsMixin(PolymerElement) {
gfm: true,
tables: true,
breaks: true
}));
}), {
onIgnoreTag: this.allowSvg
? (tag, html) => (svgWhiteList.indexOf(tag) >= 0 ? html : null)
: null
});
this._resize();
const walker = document.createTreeWalker(this, 1 /* SHOW_ELEMENT */, null, false);

View File

@@ -45,10 +45,13 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
async connectedCallback() {
super.connectedCallback();
if (!('serviceWorker' in navigator)) return;
if (!pushSupported) return;
try {
const reg = await navigator.serviceWorker.ready;
if (!reg.pushManager) {
return;
}
reg.pushManager.getSubscription().then((subscription) => {
this.loading = false;
this.pushChecked = !!subscription;
@@ -59,6 +62,10 @@ class HaPushNotificationsToggle extends EventsMixin(PolymerElement) {
}
handlePushChange(pushChecked) {
// Somehow this is triggered on Safari on page load causing
// it to get into a loop and crash the page.
if (!pushSupported) return;
if (pushChecked) {
this.subscribePushNotifications();
} else {

View File

@@ -17,7 +17,7 @@ class HaServiceDescription extends PolymerElement {
}
_getDescription(hass, domain, service) {
var domainServices = hass.config.services[domain];
var domainServices = hass.services[domain];
if (!domainServices) return '';
var serviceObject = domainServices[service];
if (!serviceObject) return '';

View File

@@ -34,13 +34,13 @@ class HaServicePicker extends LocalizeMixin(PolymerElement) {
if (!hass) {
this._services = [];
return;
} else if (oldHass && hass.config.services === oldHass.config.services) {
} else if (oldHass && hass.services === oldHass.services) {
return;
}
const result = [];
Object.keys(hass.config.services).sort().forEach((domain) => {
const services = Object.keys(hass.config.services[domain]).sort();
Object.keys(hass.services).sort().forEach((domain) => {
const services = Object.keys(hass.services[domain]).sort();
for (let i = 0; i < services.length; i++) {
result.push(`${domain}.${services[i]}`);

View File

@@ -10,6 +10,7 @@ import '../components/ha-icon.js';
import '../util/hass-translation.js';
import LocalizeMixin from '../mixins/localize-mixin.js';
import isComponentLoaded from '../common/config/is_component_loaded.js';
/*
* @appliesMixin LocalizeMixin
@@ -250,7 +251,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) {
}
_mqttLoaded(hass) {
return hass.config.core.components.indexOf('mqtt') !== -1;
return isComponentLoaded(hass, 'mqtt');
}
_computeUserName(user) {

View File

@@ -14,7 +14,7 @@ const DOMAINS_USE_LAST_UPDATED = ['thermostat', 'climate'];
const LINE_ATTRIBUTES_TO_KEEP = ['temperature', 'current_temperature', 'target_temp_low', 'target_temp_high'];
const stateHistoryCache = {};
function computeHistory(stateHistory, localize, language) {
function computeHistory(hass, stateHistory, localize, language) {
const lineChartDevices = {};
const timelineDevices = [];
if (!stateHistory) {
@@ -28,8 +28,12 @@ function computeHistory(stateHistory, localize, language) {
const stateWithUnit = stateInfo.find(state => 'unit_of_measurement' in state.attributes);
const unit = stateWithUnit ?
stateWithUnit.attributes.unit_of_measurement : false;
let unit = false;
if (stateWithUnit) {
unit = stateWithUnit.attributes.unit_of_measurement;
} else if (computeStateDomain(stateInfo[0]) === 'climate') {
unit = hass.config.unit_system.temperature;
}
if (!unit) {
timelineDevices.push({
@@ -311,7 +315,7 @@ class HaStateHistoryData extends LocalizeMixin(PolymerElement) {
// Use only data from the new fetch. Old fetch is already stored in cache.data
.then(oldAndNew => oldAndNew[1])
// Convert data into format state-history-chart-* understands.
.then(stateHistory => computeHistory(stateHistory, localize, language))
.then(stateHistory => computeHistory(this.hass, stateHistory, localize, language))
// Merge old and new.
.then((stateHistory) => {
this.mergeLine(stateHistory.line, cache.data.line);
@@ -341,7 +345,7 @@ class HaStateHistoryData extends LocalizeMixin(PolymerElement) {
}
const prom = this.fetchRecent(entityId, startTime, endTime).then(
stateHistory => computeHistory(stateHistory, localize, language),
stateHistory => computeHistory(this.hass, stateHistory, localize, language),
() => {
RECENT_CACHE[entityId] = false;
return null;
@@ -376,7 +380,7 @@ class HaStateHistoryData extends LocalizeMixin(PolymerElement) {
const filter = startTime.toISOString() + '?end_time=' + endTime.toISOString();
const prom = this.hass.callApi('GET', 'history/period/' + filter).then(
stateHistory => computeHistory(stateHistory, localize, language),
stateHistory => computeHistory(this.hass, stateHistory, localize, language),
() => null
);

10
src/data/ws-panels.js Normal file
View File

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

20
src/data/ws-themes.js Normal file
View File

@@ -0,0 +1,20 @@
import { createCollection } from 'home-assistant-js-websocket';
const fetchThemes = conn => conn.sendMessagePromise({
type: 'frontend/get_themes'
});
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
);

10
src/data/ws-user.js Normal file
View File

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

View File

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

View File

@@ -125,13 +125,13 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
<div class$="[[stateObj.attributes.operation_mode]]">
<div hidden$="[[!supportsTemperatureControls(stateObj)]]">[[localize('ui.card.climate.target_temperature')]]</div>
<template is="dom-if" if="[[supportsTemperature(stateObj)]]">
<ha-climate-control value="[[stateObj.attributes.temperature]]" units="[[hass.config.core.unit_system.temperature]]" step="[[computeTemperatureStepSize(hass, stateObj)]]" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" on-change="targetTemperatureChanged">
<ha-climate-control value="[[stateObj.attributes.temperature]]" units="[[hass.config.unit_system.temperature]]" step="[[computeTemperatureStepSize(hass, stateObj)]]" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.max_temp]]" on-change="targetTemperatureChanged">
</ha-climate-control>
</template>
<template is="dom-if" if="[[supportsTemperatureRange(stateObj)]]">
<ha-climate-control value="[[stateObj.attributes.target_temp_low]]" units="[[hass.config.core.unit_system.temperature]]" step="[[computeTemperatureStepSize(hass, stateObj)]]" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.target_temp_high]]" class="range-control-left" on-change="targetTemperatureLowChanged">
<ha-climate-control value="[[stateObj.attributes.target_temp_low]]" units="[[hass.config.unit_system.temperature]]" step="[[computeTemperatureStepSize(hass, stateObj)]]" min="[[stateObj.attributes.min_temp]]" max="[[stateObj.attributes.target_temp_high]]" class="range-control-left" on-change="targetTemperatureLowChanged">
</ha-climate-control>
<ha-climate-control value="[[stateObj.attributes.target_temp_high]]" units="[[hass.config.core.unit_system.temperature]]" step="[[computeTemperatureStepSize(hass, stateObj)]]" min="[[stateObj.attributes.target_temp_low]]" max="[[stateObj.attributes.max_temp]]" class="range-control-right" on-change="targetTemperatureHighChanged">
<ha-climate-control value="[[stateObj.attributes.target_temp_high]]" units="[[hass.config.unit_system.temperature]]" step="[[computeTemperatureStepSize(hass, stateObj)]]" min="[[stateObj.attributes.target_temp_low]]" max="[[stateObj.attributes.max_temp]]" class="range-control-right" on-change="targetTemperatureHighChanged">
</ha-climate-control>
</template>
</div>
@@ -293,7 +293,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
computeTemperatureStepSize(hass, stateObj) {
if (stateObj.attributes.target_temp_step) {
return stateObj.attributes.target_temp_step;
} else if (hass.config.core.unit_system.temperature.indexOf('F') !== -1) {
} else if (hass.config.unit_system.temperature.indexOf('F') !== -1) {
return 1;
}
return 0.5;

View File

@@ -304,7 +304,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
sendTTS() {
const services = this.hass.config.services.tts;
const services = this.hass.services.tts;
const serviceKeys = Object.keys(services).sort();
let service;
let i;

View File

@@ -156,7 +156,7 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
}
getUnit(measure) {
const lengthUnit = this.hass.config.core.unit_system.length || '';
const lengthUnit = this.hass.config.unit_system.length || '';
switch (measure) {
case 'air_pressure':
return lengthUnit === 'km' ? 'hPa' : 'inHg';
@@ -165,7 +165,7 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
case 'precipitation':
return lengthUnit === 'km' ? 'mm' : 'in';
default:
return this.hass.config.core.unit_system[measure] || '';
return this.hass.config.unit_system[measure] || '';
}
}

View File

@@ -17,7 +17,11 @@ import '../components/ha-iconset-svg.js';
import '../layouts/app/home-assistant.js';
/* polyfill for paper-dropdown */
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js');
setTimeout(
() =>
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'),
2000
);
setPassiveTouchGestures(true);
/* LastPass createElement workaround. See #428 */

View File

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

View File

@@ -1,78 +1,39 @@
import {
ERR_INVALID_AUTH,
getAuth,
createConnection,
subscribeConfig,
subscribeEntities,
subscribeServices,
} from 'home-assistant-js-websocket';
import { redirectLogin, resolveCode, refreshToken } from '../common/auth/token.js';
// import refreshToken_ from '../common/auth/refresh_token.js';
import parseQuery from '../common/util/parse_query.js';
import { loadTokens } from '../common/auth/token_storage.js';
import { loadTokens, saveTokens } from '../common/auth/token_storage.js';
import { subscribePanels } from '../data/ws-panels.js';
import { subscribeThemes } from '../data/ws-themes.js';
import { subscribeUser } from '../data/ws-user.js';
const init = window.createHassConnection = function (password, accessToken) {
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
const url = `${proto}://${window.location.host}/api/websocket?${__BUILD__}`;
const options = {
setupRetry: 10,
};
if (password) {
options.authToken = password;
} else if (accessToken) {
options.accessToken = accessToken.access_token;
options.expires = accessToken.expires;
window.hassAuth = getAuth({
hassUrl: `${location.protocol}//${location.host}`,
saveTokens,
loadTokens: () => Promise.resolve(loadTokens()),
});
window.hassConnection = window.hassAuth.then((auth) => {
if (location.search.includes('auth_callback=1')) {
history.replaceState(null, null, location.pathname);
}
return createConnection(url, options)
.then(function (conn) {
subscribeEntities(conn);
subscribeConfig(conn);
return conn;
});
};
return createConnection({ auth });
});
function main() {
if (location.search) {
const query = parseQuery(location.search.substr(1));
if (query.code) {
window.hassConnection = resolveCode(query.code).then(newTokens => init(null, newTokens));
return;
}
}
const tokens = loadTokens();
if (tokens == null) {
redirectLogin();
return;
}
if (Date.now() + 30000 > tokens.expires) {
// refresh access token if it will expire in 30 seconds to avoid invalid auth event
window.hassConnection = refreshToken().then(newTokens => init(null, newTokens));
return;
}
window.hassConnection = init(null, tokens).catch((err) => {
if (err !== ERR_INVALID_AUTH) throw err;
return refreshToken().then(newTokens => init(null, newTokens));
});
}
function mainLegacy() {
if (window.noAuth === '1') {
window.hassConnection = init();
} else if (window.localStorage.authToken) {
window.hassConnection = init(window.localStorage.authToken);
} else {
window.hassConnection = null;
}
}
if (window.useOAuth === '1') {
main();
} else {
mainLegacy();
}
// Start fetching some of the data that we will need.
window.hassConnection.then((conn) => {
const noop = () => {};
subscribeEntities(conn, noop);
subscribeConfig(conn, noop);
subscribeServices(conn, noop);
subscribePanels(conn, noop);
subscribeThemes(conn, noop);
subscribeUser(conn, noop);
});
window.addEventListener('error', (e) => {
const homeAssistant = document.querySelector('home-assistant');

View File

@@ -13,7 +13,7 @@ let es5Loaded = null;
window.loadES5Adapter = () => {
if (!es5Loaded) {
es5Loaded = Promise.all([
loadJS(`${__PUBLIC_PATH__}custom-elements-es5-adapter.js`).catch(),
loadJS(`${__STATIC_PATH__}custom-elements-es5-adapter.js`).catch(),
import(/* webpackChunkName: "compat" */ './compatibility.js'),
]);
}

View File

@@ -0,0 +1,15 @@
<meta charset="utf-8">
<link rel='manifest' href='/manifest.json' crossorigin="use-credentials">
<link rel='icon' href='/static/icons/favicon.ico'>
<meta name='viewport' content='width=device-width, user-scalable=no'>
<style>
body {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
}
</style>

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<title>Home Assistant</title>
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<%= require('raw-loader!./_header.html.template') %>
<style>
.content {
padding: 20px 16px;
max-width: 360px;
margin: 0 auto;
}
.header {
font-size: 1.96em;
display: flex;
align-items: center;
justify-content: center;
font-weight: 300;
}
.header img {
margin-right: 16px;
}
</style>
</head>
<body>
<div class="content">
<div class='header'>
<img src="/static/icons/favicon-192x192.png" height="52">
Home Assistant
</div>
<ha-authorize><p>Initializing</p></ha-authorize>
</div>
<% if (!latestBuild) { %>
<script src="/static/custom-elements-es5-adapter.js"></script>
<script src="<%= compatibility %>"></script>
<% } %>
<script>
window.providersPromise = fetch('/auth/providers', { credentials: 'same-origin' });
var webComponentsSupported = (
'customElements' in window &&
'content' in document.createElement('template'));
if (!webComponentsSupported) {
var e = document.createElement('script');
e.src = '/static/webcomponents-bundle.js';
document.write(e.outerHTML);
}
</script>
<script src="<%= entrypoint %>"></script>
<script src='<%= hassIconsJS %>' async></script>
</body>
</html>

View File

@@ -1,40 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name='viewport' content='width=device-width, user-scalable=no'>
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
<title>Home Assistant</title>
<style>
body {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
}
</style>
</head>
<body>
<<%= tag %>>Loading</<%= tag %>>
<% if (!latestBuild) { %>
<script src="/static/custom-elements-es5-adapter.js"></script>
<script src="<%= compatibility %>"></script>
<% } %>
<script>
var webComponentsSupported = (
'customElements' in window &&
'content' in document.createElement('template'));
if (!webComponentsSupported) {
var e = document.createElement('script');
e.src = '/static/webcomponents-bundle.js';
document.write(e.outerHTML);
}
</script>
<script src="<%= entrypoint %>"></script>
<script src='/static/hass-icons.js' async></script>
</body>
</html>

View File

@@ -1,17 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home Assistant</title>
<link rel='manifest' href='/manifest.json' crossorigin="use-credentials">
<link rel='icon' href='/static/icons/favicon.ico'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#3fbbf4">
<link rel='preload' href='<%= coreJS %>' as='script'/>
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
<%= require('raw-loader!./_header.html.template') %>
<title>Home Assistant</title>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#3fbbf4">
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"/>
<meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"/>
@@ -20,19 +17,8 @@
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='referrer' content='same-origin'>
<meta name='viewport' content='width=device-width, user-scalable=no'>
<meta name='theme-color' content='{{ theme_color }}'>
<style>
body {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
}
#ha-init-skeleton::before {
display: block;
content: "";

View File

@@ -0,0 +1,57 @@
<!doctype html>
<html lang="en">
<head>
<title>Home Assistant</title>
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<%= require('raw-loader!./_header.html.template') %>
<style>
.content {
padding: 20px 16px;
max-width: 400px;
margin: 0 auto;
}
.header {
text-align: center;
font-size: 1.96em;
display: flex;
align-items: center;
justify-content: center;
font-weight: 300;
}
.header img {
margin-right: 16px;
}
</style>
</head>
<body>
<div class="content">
<div class='header'>
<img src="/static/icons/favicon-192x192.png" height="52">
Home Assistant
</div>
<ha-onboarding>Initializing…</ha-onboarding>
</div>
<% if (!latestBuild) { %>
<script src="/static/custom-elements-es5-adapter.js"></script>
<script src="<%= compatibility %>"></script>
<% } %>
<script>
window.stepsPromise = fetch('/api/onboarding', { credentials: 'same-origin' });
var webComponentsSupported = (
'customElements' in window &&
'content' in document.createElement('template'));
if (!webComponentsSupported) {
var e = document.createElement('script');
e.src = '/static/webcomponents-bundle.js';
document.write(e.outerHTML);
}
</script>
<script src="<%= entrypoint %>"></script>
<script src='<%= hassIconsJS %>' async></script>
</body>
</html>

View File

@@ -1,11 +1,21 @@
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
import { getUser } from 'home-assistant-js-websocket';
import { clearState } from '../../util/ha-pref-storage.js';
import { askWrite } from '../../common/auth/token_storage.js';
import { subscribeUser } from '../../data/ws-user.js';
export default superClass => class extends superClass {
ready() {
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 })));
}
hassConnected() {
super.hassConnected();
subscribeUser(this.hass.connection, user => this._updateHass({ user }));
afterNextRender(null, () => {
if (askWrite()) {
@@ -17,17 +27,6 @@ export default superClass => class extends superClass {
});
}
hassConnected() {
super.hassConnected();
// only for new auth
if (this.hass.connection.options.accessToken) {
this.hass.callWS({
type: 'auth/current_user',
}).then(user => this._updateHass({ user }), () => {});
}
}
_handleLogout() {
this.hass.connection.close();
clearState();

View File

@@ -2,6 +2,8 @@ import {
ERR_INVALID_AUTH,
subscribeEntities,
subscribeConfig,
subscribeServices,
callService,
} from 'home-assistant-js-websocket';
import translationMetadata from '../../../build-translations/translationMetadata.json';
@@ -9,44 +11,24 @@ import translationMetadata from '../../../build-translations/translationMetadata
import LocalizeMixin from '../../mixins/localize-mixin.js';
import EventsMixin from '../../mixins/events-mixin.js';
import { refreshToken } from '../../common/auth/token.js';
import { getState } from '../../util/ha-pref-storage.js';
import { getActiveTranslation } from '../../util/hass-translation.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)) {
constructor() {
super();
this.unsubFuncs = [];
}
ready() {
super.ready();
this.addEventListener('try-connection', e =>
this._handleNewConnProm(e.detail.connProm));
if (window.hassConnection) {
this._handleNewConnProm(window.hassConnection);
}
this._handleConnProm();
}
async _handleNewConnProm(connProm) {
this.connectionPromise = connProm;
async _handleConnProm() {
const [auth, conn] = await Promise.all([window.hassAuth, window.hassConnection]);
let conn;
try {
conn = await connProm;
} catch (err) {
this.connectionPromise = null;
return;
}
this._setConnection(conn);
}
_setConnection(conn) {
this.hass = Object.assign({
auth,
connection: conn,
connected: true,
states: null,
@@ -64,7 +46,7 @@ export default superClass =>
moreInfoEntityId: null,
callService: async (domain, service, serviceData = {}) => {
try {
await conn.callService(domain, service, serviceData);
await callService(conn, domain, service, serviceData);
let message;
let name;
@@ -100,24 +82,20 @@ export default superClass =>
},
callApi: async (method, path, parameters) => {
const host = window.location.protocol + '//' + window.location.host;
const auth = conn.options;
try {
// Refresh token if it will expire in 30 seconds
if (auth.accessToken && Date.now() + 30000 > auth.expires) {
const accessToken = await refreshToken();
conn.options.accessToken = accessToken.access_token;
conn.options.expires = accessToken.expires;
}
return await hassCallApi(host, auth, method, path, parameters);
} catch (err) {
if (!err || err.status_code !== 401 || !auth.accessToken) throw err;
// If we connect with access token and get 401, refresh token and try again
const accessToken = await refreshToken();
conn.options.accessToken = accessToken.access_token;
conn.options.expires = accessToken.expires;
return await hassCallApi(host, auth, method, path, parameters);
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) => {
@@ -138,9 +116,7 @@ export default superClass =>
err => console.log('Error', err),
);
}
// In the future we'll do this as a breaking change
// inside home-assistant-js-websocket
return resp.then(result => result.result);
return resp;
},
}, getState());
@@ -152,56 +128,26 @@ export default superClass =>
const conn = this.hass.connection;
const reconnected = () => this.hassReconnected();
const disconnected = () => this.hassDisconnected();
const reconnectError = async (_conn, err) => {
if (err !== ERR_INVALID_AUTH) return;
while (this.unsubFuncs.length) {
this.unsubFuncs.pop()();
}
const accessToken = await refreshToken();
const newConn = window.createHassConnection(null, accessToken);
newConn.then(() => this.hassReconnected());
this._handleNewConnProm(newConn);
};
conn.addEventListener('ready', reconnected);
conn.addEventListener('disconnected', disconnected);
// If we reconnect after losing connection and access token is no longer
// valid.
conn.addEventListener('reconnect-error', reconnectError);
this.unsubFuncs.push(() => {
conn.removeEventListener('ready', reconnected);
conn.removeEventListener('disconnected', disconnected);
conn.removeEventListener('reconnect-error', reconnectError);
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 }))
.then(unsub => this.unsubFuncs.push(unsub));
subscribeConfig(conn, config => this._updateHass({ config }))
.then(unsub => this.unsubFuncs.push(unsub));
this._loadPanels();
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 });
this._loadPanels();
}
hassDisconnected() {
super.hassDisconnected();
this._updateHass({ connected: false });
}
async _loadPanels() {
const panels = await this.hass.callWS({
type: 'get_panels'
});
this._updateHass({ panels });
}
};

View File

@@ -6,6 +6,7 @@ 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 '../../resources/ha-style.js';
import registerServiceWorker from '../../util/register-service-worker.js';
@@ -20,8 +21,6 @@ import ConnectionMixin from './connection-mixin.js';
import NotificationMixin from './notification-mixin.js';
import DisconnectToastMixin from './disconnect-toast-mixin.js';
import(/* webpackChunkName: "login-form" */ '../../layouts/login-form.js');
const ext = (baseClass, mixins) => mixins.reduceRight((base, mixin) => mixin(base), baseClass);
class HomeAssistant extends ext(PolymerElement, [
@@ -52,21 +51,13 @@ class HomeAssistant extends ext(PolymerElement, [
</template>
<template is="dom-if" if="[[!showMain]]" restamp>
<login-form
hass="[[hass]]"
connection-promise="[[connectionPromise]]"
show-loading="[[computeShowLoading(connectionPromise, hass)]]"
></login-form>
<ha-init-page></ha-init-page>
</template>
`;
}
static get properties() {
return {
connectionPromise: {
type: Object,
value: null,
},
hass: {
type: Object,
value: null,
@@ -91,13 +82,7 @@ class HomeAssistant extends ext(PolymerElement, [
}
computeShowMain(hass) {
return hass && hass.states && hass.config && hass.panels;
}
computeShowLoading(connectionPromise, hass) {
// Show loading when connecting or when connected but not all pieces loaded yet
return (connectionPromise != null
|| (hass && hass.connection && (!hass.states || !hass.config)));
return hass && hass.states && hass.config && hass.panels && hass.services;
}
computePanelUrl(routeData) {

View File

@@ -1,46 +1,33 @@
import applyThemesOnElement from '../../common/dom/apply_themes_on_element.js';
import { storeState } from '../../util/ha-pref-storage.js';
import { subscribeThemes } from '../../data/ws-themes.js';
export default superClass => class extends superClass {
ready() {
super.ready();
this.addEventListener('settheme', e => this._setTheme(e));
this.addEventListener('settheme', (ev) => {
this._updateHass({ selectedTheme: ev.detail });
this._applyTheme();
storeState(this.hass);
});
}
hassConnected() {
super.hassConnected();
this.hass.callWS({
type: 'frontend/get_themes',
}).then((themes) => {
subscribeThemes(this.hass.connection, (themes) => {
this._updateHass({ themes });
applyThemesOnElement(
document.documentElement,
themes,
this.hass.selectedTheme,
true
);
this._applyTheme();
});
this.hass.connection.subscribeEvents((event) => {
this._updateHass({ themes: event.data });
applyThemesOnElement(
document.documentElement,
event.data,
this.hass.selectedTheme,
true
);
}, 'themes_updated').then(unsub => this.unsubFuncs.push(unsub));
}
_setTheme(event) {
this._updateHass({ selectedTheme: event.detail });
_applyTheme() {
applyThemesOnElement(
document.documentElement,
this.hass.themes,
this.hass.selectedTheme,
true
);
storeState(this.hass);
}
};

View File

@@ -14,102 +14,24 @@ import EventsMixin from '../mixins/events-mixin.js';
/*
* @appliesMixin LocalizeMixin
*/
class LoginForm extends EventsMixin(LocalizeMixin(PolymerElement)) {
class HaInitPage extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex iron-positioning"></style>
<style>
:host {
white-space: nowrap;
}
paper-input {
display: block;
margin-bottom: 16px;
}
paper-checkbox {
margin-right: 8px;
}
paper-button {
margin-left: 72px;
}
.interact {
height: 125px;
}
#validatebox {
margin-top: 16px;
text-align: center;
}
.validatemessage {
margin-top: 10px;
paper-spinner {
margin-bottom: 10px;
}
</style>
<div class="layout vertical center center-center fit">
<img src="/static/icons/favicon-192x192.png" height="192">
<a href="#" id="hideKeyboardOnFocus"></a>
<div class="interact">
<div id="loginform" hidden$="[[showSpinner]]">
<paper-input id="passwordInput" label="[[localize('ui.login-form.password')]]" type="password" autofocus="" invalid="[[errorMessage]]" error-message="[[errorMessage]]" value="{{password}}"></paper-input>
<div class="layout horizontal center">
<paper-checkbox for="" id="rememberLogin">[[localize('ui.login-form.remember')]]</paper-checkbox>
<paper-button on-click="validatePassword">[[localize('ui.login-form.log_in')]]</paper-button>
</div>
</div>
<div id="validatebox" hidden$="[[!showSpinner]]">
<paper-spinner active="true"></paper-spinner><br>
<div class="validatemessage">[[computeLoadingMsg(isValidating)]]</div>
</div>
</div>
<paper-spinner active="true"></paper-spinner>
Loading data
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
connectionPromise: {
type: Object,
notify: true,
observer: 'handleConnectionPromiseChanged',
},
errorMessage: {
type: String,
value: '',
},
isValidating: {
type: Boolean,
observer: 'isValidatingChanged',
value: false,
},
showLoading: {
type: Boolean,
value: false,
},
showSpinner: {
type: Boolean,
computed: 'computeShowSpinner(showLoading, isValidating)',
},
password: {
type: String,
value: '',
},
};
}
ready() {
super.ready();
this.addEventListener('keydown', ev => this.passwordKeyDown(ev));
@@ -183,4 +105,4 @@ class LoginForm extends EventsMixin(LocalizeMixin(PolymerElement)) {
}
}
customElements.define('login-form', LoginForm);
customElements.define('ha-init-page', HaInitPage);

View File

@@ -0,0 +1,52 @@
/**
* Lite mixin to add localization without depending on the Hass object.
*/
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js';
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
import { AppLocalizeBehavior } from '../util/app-localize-behavior.js';
import { getActiveTranslation, getTranslation } from '../util/hass-translation.js';
/**
* @polymerMixin
* @appliesMixin AppLocalizeBehavior
*/
export default dedupingMixin(superClass =>
class extends mixinBehaviors([AppLocalizeBehavior], superClass) {
static get properties() {
return {
language: {
type: String,
value: getActiveTranslation(),
},
resources: Object,
// The fragment to load.
translationFragment: String,
};
}
async ready() {
super.ready();
if (this.resources) {
return;
}
if (!this.translationFragment) {
// In dev mode, we will issue a warning if after a second we are still
// not configured correctly.
if (__DEV__) {
// eslint-disable-next-line
setTimeout(() => !this.resources && console.error(
'Forgot to pass in resources or set translationFragment for',
this.nodeName
), 1000);
}
return;
}
const { language, data } = await getTranslation(this.translationFragment);
this.resources = {
[language]: data
};
}
});

View File

@@ -1,4 +1,3 @@
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
import '@polymer/polymer/lib/elements/dom-if.js';
import '@polymer/polymer/lib/elements/dom-repeat.js';
import '@polymer/paper-input/paper-input.js';
@@ -6,40 +5,16 @@ import '@polymer/paper-button/paper-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import hassCallApi from '../util/hass-call-api.js';
import localizeLiteMixin from '../mixins/localize-lite-mixin.js';
const callApi = (method, path, data) => hassCallApi('', {}, method, path, data);
class HaOnboarding extends PolymerElement {
class HaOnboarding extends localizeLiteMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex iron-positioning"></style>
<style>
.content {
padding: 20px 16px;
max-width: 400px;
margin: 0 auto;
}
.header {
text-align: center;
font-size: 1.96em;
display: flex;
align-items: center;
justify-content: center;
font-weight: 300;
}
.header img {
margin-right: 16px;
}
h1 {
font-size: 1.2em;
}
.error {
color: red;
font-weight: bold;
}
.action {
@@ -47,54 +22,58 @@ class HaOnboarding extends PolymerElement {
text-align: center;
}
</style>
<div class="content layout vertical fit">
<div class='header'>
<img src="/static/icons/favicon-192x192.png" height="52">
Home Assistant
</div>
<div>
<p>Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?</p>
<p>Let's get started by creating a user account.</p>
</div>
<p>
[[localize('ui.panel.page-onboarding.intro')]]
</p>
<template is='dom-if' if='[[_error]]'>
<p class='error'>[[_error]]</p>
</template>
<p>
[[localize('ui.panel.page-onboarding.user.intro')]]
</p>
<template is='dom-if' if='[[_errorMsg]]'>
<p class='error'>[[_computeErrorMsg(localize, _errorMsg)]]</p>
</template>
<form>
<paper-input
autofocus
label='Name'
label="[[localize('ui.panel.page-onboarding.user.data.name')]]"
value='{{_name}}'
required
auto-validate
error-message='Required'
autocapitalize='on'
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
on-blur='_maybePopulateUsername'
></paper-input>
<paper-input
label='Username'
label="[[localize('ui.panel.page-onboarding.user.data.username')]]"
value='{{_username}}'
required
auto-validate
error-message='Required'
autocapitalize='none'
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
></paper-input>
<paper-input
label='Password'
label="[[localize('ui.panel.page-onboarding.user.data.password')]]"
value='{{_password}}'
required
type='password'
auto-validate
error-message='Required'
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
></paper-input>
<template is='dom-if' if='[[!_loading]]'>
<p class='action'>
<paper-button raised on-click='_submitForm'>Create Account</paper-button>
<paper-button raised on-click='_submitForm'>
[[localize('ui.panel.page-onboarding.user.create_account')]]
</paper-button>
</p>
</template>
</div>
</form>
`;
}
@@ -106,7 +85,12 @@ class HaOnboarding extends PolymerElement {
_loading: {
type: Boolean,
value: false,
}
},
translationFragment: {
type: String,
value: 'page-onboarding',
},
_errorMsg: String,
};
}
@@ -117,10 +101,24 @@ class HaOnboarding extends PolymerElement {
this._submitForm();
}
});
const steps = await callApi('get', 'onboarding');
if (steps.every(step => step.done)) {
// Onboarding is done!
document.location = '/';
try {
const response = await window.stepsPromise;
if (response.status === 404) {
// We don't load the component when onboarding is done
document.location = '/';
return;
}
const steps = await response.json();
if (steps.every(step => step.done)) {
// Onboarding is done!
document.location = '/';
}
} catch (err) {
alert('Something went wrong loading loading onboarding, try refreshing');
}
}
@@ -135,7 +133,12 @@ class HaOnboarding extends PolymerElement {
}
async _submitForm() {
if (!this._name || !this._username || !this._password) return;
if (!this._name || !this._username || !this._password) {
this._errorMsg = 'required_fields';
return;
}
this._errorMsg = '';
try {
await callApi('post', 'onboarding/users', {
@@ -150,9 +153,13 @@ class HaOnboarding extends PolymerElement {
console.error(err);
this.setProperties({
_loading: false,
_error: err.message,
_errorMsg: err.message,
});
}
}
_computeErrorMsg(localize, errorMsg) {
return localize(`ui.panel.page-onboarding.user.error.${errorMsg}`) || errorMsg;
}
}
customElements.define('ha-onboarding', HaOnboarding);

View File

@@ -71,7 +71,7 @@ class HaConfigFlow extends
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
<ha-markdown content="[[_computeStepDescription(localize, _step)]]"></ha-markdown>
<ha-markdown content="[[_computeStepDescription(localize, _step)]]" allow-svg></ha-markdown>
</template>
<ha-form

View File

@@ -37,6 +37,7 @@ class HaDialogAddUser extends LocalizeMixin(PolymerElement) {
value='{{_name}}'
required
auto-validate
autocapitalize='on'
error-message='Required'
on-blur='_maybePopulateUsername'
></paper-input>
@@ -46,6 +47,7 @@ class HaDialogAddUser extends LocalizeMixin(PolymerElement) {
value='{{_username}}'
required
auto-validate
autocapitalize='none'
error-message='Required'
></paper-input>
<paper-input

View File

@@ -496,8 +496,16 @@ class HaConfigZwave extends LocalizeMixin(PolymerElement) {
const valueIndex = this.values.indexOf(valueData);
this.hass.callApi('GET', `config/zwave/device_config/${this.entities[selectedEntity].entity_id}`)
.then((data) => {
this.entityIgnored = data.ignored || false;
this.entityPollingIntensity = this.values[valueIndex].value.poll_intensity;
this.setProperties({
entityIgnored: data.ignored || false,
entityPollingIntensity: this.values[valueIndex].value.poll_intensity
});
})
.catch(() => {
this.setProperties({
entityIgnored: false,
entityPollingIntensity: this.values[valueIndex].value.poll_intensity
});
});
}

View File

@@ -47,7 +47,7 @@ class HaPanelCustom extends NavigateMixin(EventsMixin(PolymerElement)) {
const config = panel.config._panel_custom;
const tempA = document.createElement('a');
tempA.href = config.html_url || config.js_url;
tempA.href = config.html_url || config.js_url || config.module_url;
if (!config.trust_external && !['localhost', '127.0.0.1', location.hostname].includes(tempA.hostname)) {
if (!confirm(`Do you trust the external panel "${config.name}" at "${tempA.href}"?

View File

@@ -122,10 +122,10 @@ class HaPanelDevInfo extends PolymerElement {
<p class='version'>
<a href='https://www.home-assistant.io'><img src="/static/icons/favicon-192x192.png" height="192" /></a><br />
Home Assistant<br />
[[hass.config.core.version]]
[[hass.config.version]]
</p>
<p>
Path to configuration.yaml: [[hass.config.core.config_dir]]
Path to configuration.yaml: [[hass.config.config_dir]]
</p>
<p class='develop'>
<a href='https://www.home-assistant.io/developers/credits/' target='_blank'>

View File

@@ -230,7 +230,7 @@ class HaPanelDevService extends PolymerElement {
}
_computeAttributesArray(hass, domain, service) {
const serviceDomains = hass.config.services;
const serviceDomains = hass.services;
if (!(domain in serviceDomains)) return [];
if (!(service in serviceDomains[domain])) return [];
@@ -241,7 +241,7 @@ class HaPanelDevService extends PolymerElement {
}
_computeDescription(hass, domain, service) {
const serviceDomains = hass.config.services;
const serviceDomains = hass.services;
if (!(domain in serviceDomains)) return undefined;
if (!(service in serviceDomains[domain])) return undefined;
return serviceDomains[domain][service].description;

View File

@@ -22,21 +22,14 @@ class HuiGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style>
ha-card {
padding: 16px;
}
ha-card[header] {
padding-top: 0;
}
ha-icon {
padding: 8px;
color: var(--paper-item-icon-color);
}
.entities {
display: flex;
margin-bottom: -12px;
padding: 0 16px 4px;
flex-wrap: wrap;
}
.entities.no-header {
padding-top: 16px;
}
.entity {
box-sizing: border-box;
padding: 0 4px;
@@ -59,20 +52,18 @@ class HuiGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
</style>
<ha-card header$="[[_config.title]]">
<div class="entities">
<ha-card header="[[_config.title]]">
<div class$="[[_computeClasses(_config.title)]]">
<template is="dom-repeat" items="[[_configEntities]]">
<template is="dom-if" if="[[_showEntity(item, hass.states)]]">
<div class="entity" on-click="_handleClick">
<template is="dom-if" if="[[_showInfo(_config.show_name)]]">
<div class="name">[[_computeName(item, hass.states)]]</div>
</template>
<template is="dom-if" if="[[!item.icon]]">
<state-badge state-obj="[[_computeStateObj(item, hass.states)]]"></state-badge>
</template>
<template is="dom-if" if="[[item.icon]]">
<ha-icon icon="[[item.icon]]"></ha-icon>
</template>
<state-badge
state-obj="[[_computeStateObj(item, hass.states)]]"
override-icon="[[item.icon]]"
></state-badge>
<template is="dom-if" if="[[_showInfo(_config.show_state)]]">
<div>[[_computeState(item, hass.states)]]</div>
</template>
@@ -102,6 +93,10 @@ class HuiGlanceCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
this._configEntities = processConfigEntities(config.entities);
}
_computeClasses(hasHeader) {
return `entities ${hasHeader ? '' : 'no-header'}`;
}
_showEntity(item, states) {
return item.entity in states;
}

View File

@@ -159,14 +159,20 @@ class HuiMapCard extends PolymerElement {
}
_fitMap() {
const zoom = this._config.default_zoom;
if (this._mapItems.length === 0) {
this._map.setView(
new Leaflet.LatLng(this.hass.config.core.latitude, this.hass.config.core.longitude),
14
new Leaflet.LatLng(this.hass.config.latitude, this.hass.config.longitude),
zoom || 14
);
} else {
const bounds = new Leaflet.latLngBounds(this._mapItems.map(item => item.getLatLng()));
this._map.fitBounds(bounds.pad(0.5));
return;
}
const bounds = new Leaflet.latLngBounds(this._mapItems.map(item => item.getLatLng()));
this._map.fitBounds(bounds.pad(0.5));
if (zoom && this._map.getZoom() > zoom) {
this._map.setZoom(zoom);
}
}

View File

@@ -0,0 +1,12 @@
import computeDomain from '../../../common/entity/compute_domain.js';
const NOTIFICATION_DOMAINS = [
'configurator',
'persistent_notification'
];
export default function computeNotifications(states) {
return Object.keys(states)
.filter(entityId => NOTIFICATION_DOMAINS.includes(computeDomain(entityId)))
.map(entityId => states[entityId]);
}

View File

@@ -18,6 +18,7 @@ function computeUsedEntities(config) {
if (obj.entities) obj.entities.forEach(entity => addEntityId(entity));
if (obj.card) addEntities(obj.card);
if (obj.cards) obj.cards.forEach(card => addEntities(card));
if (obj.badges) obj.badges.forEach(badge => addEntityId(badge));
}
config.views.forEach(view => addEntities(view));

View File

@@ -17,11 +17,14 @@ class HuiGenericEntityRow extends PolymerElement {
}
.flex {
flex: 1;
overflow: hidden;
margin-left: 16px;
display: flex;
justify-content: space-between;
align-items: center;
min-width: 0;
}
.info {
flex: 1 0 60px;
}
.info,
.info > * {
@@ -31,29 +34,27 @@ class HuiGenericEntityRow extends PolymerElement {
}
.flex ::slotted(*) {
margin-left: 8px;
min-width: 0;
}
.secondary,
ha-relative-time {
display: block;
color: var(--secondary-text-color);
}
ha-icon {
padding: 8px;
color: var(--paper-item-icon-color);
}
.not-found {
flex: 1;
background-color: yellow;
padding: 8px;
}
state-badge {
flex: 0 0 40px;
}
</style>
<template is="dom-if" if="[[_stateObj]]">
<template is="dom-if" if="[[!config.icon]]">
<state-badge state-obj="[[_stateObj]]"></state-badge>
</template>
<template is="dom-if" if="[[config.icon]]">
<ha-icon icon="[[config.icon]]"></ha-icon>
</template>
<state-badge
state-obj="[[_stateObj]]"
override-icon="[[config.icon]]"
></state-badge>
<div class="flex">
<div class="info">
[[_computeName(config.name, _stateObj)]]

View File

@@ -0,0 +1,53 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import './hui-notification-item-template.js';
import EventsMixin from '../../../../mixins/events-mixin.js';
import LocalizeMixin from '../../../../mixins/localize-mixin.js';
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
export class HuiConfiguratorNotificationItem extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<hui-notification-item-template>
<span slot="header">[[localize('domain.configurator')]]</span>
<div>[[_getMessage(stateObj)]]</div>
<paper-button
slot="actions"
class="primary"
on-click="_handleClick"
>[[_localizeState(stateObj.state)]]</paper-button>
</hui-notification-item-template>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object
};
}
_handleClick() {
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
}
_localizeState(state) {
return this.localize(`state.configurator.${state}`);
}
_getMessage(stateObj) {
const friendlyName = stateObj.attributes.friendly_name;
return this.localize('ui.notification_drawer.click_to_configure', 'entity', friendlyName);
}
}
customElements.define('hui-configurator-notification-item', HuiConfiguratorNotificationItem);

View File

@@ -0,0 +1,179 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import './hui-notification-item.js';
import computeNotifications from '../../common/compute-notifications.js';
import EventsMixin from '../../../../mixins/events-mixin.js';
import LocalizeMixin from '../../../../mixins/localize-mixin.js';
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
export class HuiNotificationDrawer extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style include="paper-material-styles">
:host {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
:host([hidden]) {
display: none;
}
.container {
align-items: stretch;
background: var(--sidebar-background-color, var(--primary-background-color));
bottom: 0;
box-shadow: var(--paper-material-elevation-1_-_box-shadow);
display: flex;
flex-direction: column;
overflow-y: hidden;
position: fixed;
top: 0;
transition: right .2s ease-in;
width: 500px;
z-index: 10;
}
:host(:not(narrow)) .container {
right: -500px;
}
:host([narrow]) .container {
right: -100%;
width: 100%;
}
:host(.open) .container,
:host(.open[narrow]) .container {
right: 0;
}
app-toolbar {
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
background-color: var(--primary-background-color);
min-height: 64px;
width: calc(100% - 32px);
z-index: 11;
}
.overlay {
display: none;
}
:host(.open) .overlay {
bottom: 0;
display: block;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: 5;
}
.notifications {
overflow-y: auto;
padding-top: 16px;
}
.notification {
padding: 0 16px 16px;
}
.empty {
padding: 16px;
text-align: center;
}
</style>
<div class="overlay" on-click="_closeDrawer"></div>
<div class="container">
<app-toolbar>
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
<paper-icon-button icon="hass:chevron-right" on-click="_closeDrawer"></paper-icon-button>
</app-toolbar>
<div class="notifications">
<template is="dom-if" if="[[!_empty(_entities)]]">
<dom-repeat items="[[_entities]]">
<template>
<div class="notification">
<hui-notification-item hass="[[hass]]" state-obj="[[item]]"></hui-notification-item>
</div>
</template>
</dom-repeat>
</template>
<template is="dom-if" if="[[_empty(_entities)]]">
<div class="empty">[[localize('ui.notification_drawer.empty')]]<div>
</template>
</div>
</div>
`;
}
static get properties() {
return {
hass: Object,
_entities: {
type: Array,
computed: '_getEntities(hass.states, hidden)'
},
narrow: {
type: Boolean,
reflectToAttribute: true
},
open: {
type: Boolean,
notify: true,
observer: '_openChanged'
},
hidden: {
type: Boolean,
value: true,
reflectToAttribute: true
}
};
}
_getEntities(states, hidden) {
return (states && !hidden) ? computeNotifications(states) : [];
}
_closeDrawer(ev) {
ev.stopPropagation();
this.open = false;
}
_empty(entities) {
return entities.length === 0;
}
_openChanged(open) {
clearTimeout(this._openTimer);
if (open) {
// Render closed then animate open
this.hidden = false;
this._openTimer = setTimeout(() => {
this.classList.add('open');
}, 50);
} else {
// Animate closed then hide
this.classList.remove('open');
this._openTimer = setTimeout(() => {
this.hidden = true;
}, 250);
}
}
}
customElements.define('hui-notification-drawer', HuiNotificationDrawer);

View File

@@ -0,0 +1,46 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '../../../../components/ha-card.js';
export class HuiNotificationItemTemplate extends PolymerElement {
static get template() {
return html`
<style>
.contents {
padding: 16px;
}
ha-card .header {
@apply --paper-font-headline;
color: var(--primary-text-color);
padding: 16px 16px 0;
}
.actions {
border-top: 1px solid #e8e8e8;
padding: 5px 16px;
}
::slotted(.primary) {
color: var(--primary-color);
}
</style>
<ha-card>
<div class="header">
<slot name="header"></slot>
</div>
<div class="contents">
<slot></slot>
</div>
<div class="actions">
<slot name="actions"></slot>
</div>
</ha-card>
`;
}
}
customElements.define('hui-notification-item-template', HuiNotificationItemTemplate);

View File

@@ -0,0 +1,33 @@
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import computeDomain from '../../../../common/entity/compute_domain.js';
import './hui-configurator-notification-item.js';
import './hui-persistent-notification-item.js';
export class HuiNotificationItem extends PolymerElement {
static get properties() {
return {
hass: Object,
stateObj: {
type: Object,
observer: '_stateChanged'
}
};
}
_stateChanged(stateObj) {
if (this.lastChild) {
this.removeChild(this.lastChild);
}
if (!stateObj) return;
const domain = computeDomain(stateObj.entity_id);
const tag = `hui-${domain}-notification-item`;
const el = document.createElement(tag);
el.hass = this.hass;
el.stateObj = stateObj;
this.appendChild(el);
}
}
customElements.define('hui-notification-item', HuiNotificationItem);

View File

@@ -0,0 +1,61 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import computeNotifications from '../../common/compute-notifications.js';
import EventsMixin from '../../../../mixins/events-mixin.js';
/*
* @appliesMixin EventsMixin
*/
export class HuiNotificationsButton extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style>
:host {
position: relative;
}
.indicator {
position: absolute;
top: 10px;
right: 10px;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--accent-color);
pointer-events: none;
}
.indicator[hidden] {
display: none;
}
</style>
<paper-icon-button icon="hass:bell" on-click="_clicked"></paper-icon-button>
<span class="indicator" hidden$="[[!_hasNotifications(hass.states)]]"></span>
`;
}
static get properties() {
return {
hass: Object,
notificationsOpen: {
type: Boolean,
notify: true
}
};
}
_clicked() {
this.notificationsOpen = true;
}
_hasNotifications(states) {
return computeNotifications(states).length > 0;
}
}
customElements.define('hui-notifications-button', HuiNotificationsButton);

View File

@@ -0,0 +1,52 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import computeStateName from '../../../../common/entity/compute_state_name.js';
import '../../../../components/ha-markdown.js';
import './hui-notification-item-template.js';
import LocalizeMixin from '../../../../mixins/localize-mixin.js';
/*
* @appliesMixin LocalizeMixin
*/
export class HuiPersistentNotificationItem extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<hui-notification-item-template>
<span slot="header">[[_computeTitle(stateObj)]]</span>
<ha-markdown content="[[stateObj.attributes.message]]"></ha-markdown>
<paper-button
slot="actions"
class="primary"
on-click="_handleDismiss"
>[[localize('ui.card.persistent_notification.dismiss')]]</paper-button>
</hui-notification-item-template>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object
};
}
_handleDismiss() {
this.hass.callApi('DELETE', `states/${this.stateObj.entity_id}`);
}
_computeTitle(stateObj) {
return (stateObj.attributes.title || computeStateName(stateObj));
}
}
customElements.define(
'hui-persistent_notification-notification-item',
HuiPersistentNotificationItem
);

View File

@@ -1,5 +1,6 @@
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-scroll-effects/effects/waterfall.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/app-route/app-route.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
@@ -21,6 +22,8 @@ import '../../layouts/ha-app-layout.js';
import '../../components/ha-start-voice-button.js';
import '../../components/ha-icon.js';
import { loadModule, loadCSS, loadJS } from '../../common/dom/load_resource.js';
import './components/notifications/hui-notification-drawer.js';
import './components/notifications/hui-notifications-button.js';
import './hui-unused-entities.js';
import './hui-view.js';
import debounce from '../../common/util/debounce.js';
@@ -71,11 +74,20 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
}
</style>
<app-route route="[[route]]" pattern="/:view" data="{{routeData}}"></app-route>
<hui-notification-drawer
hass="[[hass]]"
open="{{notificationsOpen}}"
narrow="[[narrow]]"
></hui-notification-drawer>
<ha-app-layout id="layout">
<app-header slot="header" fixed>
<app-header slot="header" effects="waterfall" fixed condenses>
<app-toolbar>
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
<div main-title>[[_computeTitle(config)]]</div>
<hui-notifications-button
hass="[[hass]]"
notifications-open="{{notificationsOpen}}"
></hui-notifications-button>
<ha-start-voice-button hass="[[hass]]"></ha-start-voice-button>
<paper-menu-button
no-animations
@@ -138,7 +150,13 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
type: Object,
observer: '_routeChanged'
},
routeData: Object
notificationsOpen: {
type: Boolean,
value: false,
},
routeData: Object,
};
}
@@ -285,5 +303,4 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
});
}
}
customElements.define('hui-root', HUIRoot);

View File

@@ -79,7 +79,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
if (this._mapItems.length === 0) {
this._map.setView(
new Leaflet.LatLng(this.hass.config.core.latitude, this.hass.config.core.longitude),
new Leaflet.LatLng(this.hass.config.latitude, this.hass.config.longitude),
14
);
} else {

View File

@@ -0,0 +1,275 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
import '@polymer/paper-dialog/paper-dialog.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 '../../components/ha-form.js';
import '../../components/ha-markdown.js';
import '../../resources/ha-style.js';
import EventsMixin from '../../mixins/events-mixin.js';
import LocalizeMixin from '../../mixins/localize-mixin.js';
let instance = 0;
/*
* @appliesMixin LocalizeMixin
* @appliesMixin EventsMixin
*/
class HaMfaModuleSetupFlow extends
LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style include="ha-style-dialog">
.error {
color: red;
}
paper-dialog {
max-width: 500px;
}
ha-markdown img:first-child:last-child,
ha-markdown svg:first-child:last-child {
display: block;
margin: 0 auto;
}
ha-markdown a {
color: var(--primary-color);
}
.init-spinner {
padding: 10px 100px 34px;
text-align: center;
}
.submit-spinner {
margin-right: 16px;
}
</style>
<paper-dialog id="dialog" with-backdrop="" opened="{{_opened}}" on-opened-changed="_openedChanged">
<h2>
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
Aborted
</template>
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
Success!
</template>
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
[[_computeStepTitle(localize, _step)]]
</template>
</h2>
<paper-dialog-scrollable>
<template is="dom-if" if="[[_errorMsg]]">
<div class='error'>[[_errorMsg]]</div>
</template>
<template is="dom-if" if="[[!_step]]">
<div class='init-spinner'><paper-spinner active></paper-spinner></div>
</template>
<template is="dom-if" if="[[_step]]">
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
<ha-markdown content="[[_computeStepAbortedReason(localize, _step)]]"></ha-markdown>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
<p>Setup done for [[_step.title]]</p>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
<ha-markdown content="[[_computeStepDescription(localize, _step)]]" allow-svg></ha-markdown>
</template>
<ha-form
data="{{_stepData}}"
schema="[[_step.data_schema]]"
error="[[_step.errors]]"
compute-label="[[_computeLabelCallback(localize, _step)]]"
compute-error="[[_computeErrorCallback(localize, _step)]]"
></ha-form>
</template>
</template>
</paper-dialog-scrollable>
<div class="buttons">
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
<paper-button on-click="_flowDone">Close</paper-button>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
<paper-button on-click="_flowDone">Close</paper-button>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
<template is="dom-if" if="[[_loading]]">
<div class='submit-spinner'><paper-spinner active></paper-spinner></div>
</template>
<template is="dom-if" if="[[!_loading]]">
<paper-button on-click="_submitStep">Submit</paper-button>
</template>
</template>
</div>
</paper-dialog>
`;
}
static get properties() {
return {
_hass: Object,
_dialogClosedCallback: Function,
_instance: Number,
_loading: {
type: Boolean,
value: false,
},
// Error message when can't talk to server etc
_errorMsg: String,
_opened: {
type: Boolean,
value: false,
},
_step: {
type: Object,
value: null,
},
/*
* Store user entered data.
*/
_stepData: Object,
};
}
ready() {
super.ready();
this.addEventListener('keypress', (ev) => {
if (ev.keyCode === 13) {
this._submitStep();
}
});
}
showDialog({ hass, continueFlowId, mfaModuleId, dialogClosedCallback }) {
this.hass = hass;
this._instance = instance++;
this._dialogClosedCallback = dialogClosedCallback;
this._createdFromHandler = !!mfaModuleId;
this._loading = true;
this._opened = true;
const fetchStep = continueFlowId ?
this.hass.callWS({
type: 'auth/setup_mfa',
flow_id: continueFlowId,
}) :
this.hass.callWS({
type: 'auth/setup_mfa',
mfa_module_id: mfaModuleId,
});
const curInstance = this._instance;
fetchStep.then((step) => {
if (curInstance !== this._instance) return;
this._processStep(step);
this._loading = false;
// When the flow changes, center the dialog.
// Don't do it on each step or else the dialog keeps bouncing.
setTimeout(() => this.$.dialog.center(), 0);
});
}
_submitStep() {
this._loading = true;
this._errorMsg = null;
const curInstance = this._instance;
this.hass.callWS({
type: 'auth/setup_mfa',
flow_id: this._step.flow_id,
user_input: this._stepData,
}).then(
(step) => {
if (curInstance !== this._instance) return;
this._processStep(step);
this._loading = false;
},
(err) => {
this._errorMsg = (err && err.body && err.body.message) || 'Unknown error occurred';
this._loading = false;
}
);
}
_processStep(step) {
if (!step.errors) step.errors = {};
this._step = step;
// We got a new form if there are no errors.
if (Object.keys(step.errors).length === 0) {
this._stepData = {};
}
}
_flowDone() {
this._opened = false;
const flowFinished = this._step && ['create_entry', 'abort'].includes(this._step.type);
if (this._step && !flowFinished && this._createdFromHandler) {
// console.log('flow not finish');
}
this._dialogClosedCallback({
flowFinished,
});
this._errorMsg = null;
this._step = null;
this._stepData = {};
this._dialogClosedCallback = null;
}
_equals(a, b) {
return a === b;
}
_openedChanged(ev) {
// Closed dialog by clicking on the overlay
if (this._step && !ev.detail.value) {
this._flowDone();
}
}
_computeStepAbortedReason(localize, step) {
return localize(`component.auth.mfa_setup.${step.handler}.abort.${step.reason}`);
}
_computeStepTitle(localize, step) {
return localize(`component.auth.mfa_setup.${step.handler}.step.${step.step_id}.title`)
|| 'Setup Multi-factor Authentication';
}
_computeStepDescription(localize, step) {
const args = [`component.auth.mfa_setup.${step.handler}.step.${step.step_id}.description`];
const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => {
args.push(key);
args.push(placeholders[key]);
});
return localize(...args);
}
_computeLabelCallback(localize, step) {
// Returns a callback for ha-form to calculate labels per schema object
return schema => localize(`component.auth.mfa_setup.${step.handler}.step.${step.step_id}.data.${schema.name}`)
|| schema.name;
}
_computeErrorCallback(localize, step) {
// Returns a callback for ha-form to calculate error messages
return error => localize(`component.auth.mfa_setup.${step.handler}.error.${error}`) || error;
}
}
customElements.define('ha-mfa-module-setup-flow', HaMfaModuleSetupFlow);

View File

@@ -0,0 +1,119 @@
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-item/paper-item-body.js';
import '@polymer/paper-item/paper-item.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '../../resources/ha-style.js';
import EventsMixin from '../../mixins/events-mixin.js';
import LocalizeMixin from '../../mixins/localize-mixin.js';
let registeredDialog = false;
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex ha-style">
.error {
color: red;
}
.status {
color: var(--primary-color);
}
.error, .status {
position: absolute;
top: -4px;
}
paper-card {
display: block;
max-width: 600px;
margin: 16px auto;
}
paper-button {
color: var(--primary-color);
font-weight: 500;
margin-right: -.57em;
}
</style>
<paper-card heading="Multi-factor Authentication Modules">
<template is="dom-repeat" items="[[mfaModules]]" as="module">
<paper-item>
<paper-item-body two-line="">
<div>[[module.name]]</div>
<div secondary="">[[module.id]]</div>
</paper-item-body>
<template is="dom-if" if="[[module.enabled]]">
<paper-button on-click="_disable">Disable</paper-button>
</template>
<template is="dom-if" if="[[!module.enabled]]">
<paper-button on-click="_enable">Enable</paper-button>
</template>
</paper-item>
</template>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
_loading: {
type: Boolean,
value: false,
},
// Error message when can't talk to server etc
_statusMsg: String,
_errorMsg: String,
mfaModules: Array,
};
}
connectedCallback() {
super.connectedCallback();
if (!registeredDialog) {
registeredDialog = true;
this.fire('register-dialog', {
dialogShowEvent: 'show-mfa-module-setup-flow',
dialogTag: 'ha-mfa-module-setup-flow',
dialogImport: () => import('./ha-mfa-module-setup-flow.js'),
});
}
}
_enable(ev) {
this.fire('show-mfa-module-setup-flow', {
hass: this.hass,
mfaModuleId: ev.model.module.id,
dialogClosedCallback: () => this._refreshCurrentUser(),
});
}
_disable(ev) {
if (!confirm(`Are you sure you want to disable ${ev.model.module.name}?`)) return;
const mfaModuleId = ev.model.module.id;
this.hass.callWS({
type: 'auth/depose_mfa',
mfa_module_id: mfaModuleId,
}).then(() => {
this._refreshCurrentUser();
});
}
_refreshCurrentUser() {
this.fire('hass-refresh-current-user');
}
}
customElements.define('ha-mfa-modules-card', HaMfaModulesCard);

View File

@@ -14,6 +14,7 @@ import '../../resources/ha-style.js';
import EventsMixin from '../../mixins/events-mixin.js';
import './ha-change-password-card.js';
import './ha-mfa-modules-card.js';
import './ha-pick-language-row.js';
import './ha-pick-theme-row.js';
import './ha-push-notifications-row.js';
@@ -83,6 +84,7 @@ class HaPanelProfile extends EventsMixin(PolymerElement) {
<ha-change-password-card hass="[[hass]]"></ha-change-password-card>
</template>
<ha-mfa-modules-card hass='[[hass]]' mfa-modules='[[hass.user.mfa_modules]]'></ha-mfa-modules-card>
</div>
</app-header-layout>
`;

View File

@@ -478,6 +478,11 @@
"remember": "Remember",
"log_in": "Log in"
},
"notification_drawer": {
"click_to_configure": "Click button to configure {entity}",
"empty": "No Notifications",
"title": "Notifications"
},
"notification_toast": {
"entity_turned_on": "Turned on {entity}.",
"entity_turned_off": "Turned off {entity}.",
@@ -747,6 +752,95 @@
"clear_completed": "Clear completed",
"add_item": "Add item",
"microphone_tip": "Tap the microphone on the top right and say “Add candy to my shopping list”"
},
"page-authorize": {
"initializing": "Initializing",
"authorizing_client": "You're about to give {clientId} access to your Home Assistant instance.",
"logging_in_with": "Logging in with **{authProviderName}**.",
"pick_auth_provider": "Or log in with",
"abort_intro": "Login aborted",
"form": {
"working": "Please wait",
"unknown_error": "Something went wrong",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Username",
"password": "Password"
}
},
"mfa": {
"data": {
"code": "Two-factor Authentication Code"
},
"description": "Open the **{mfa_module_name}** on your device to view your two-factor authentication code and verify your identity:"
}
},
"error": {
"invalid_auth": "Invalid username or password",
"invalid_code": "Invalid authentication code"
},
"abort": {
"login_expired": "Session expired, please login again."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "API Password"
},
"description": "Please input the API password in your http config:"
},
"mfa": {
"data": {
"code": "[%key:ui::panel::page-authorize::form::providers::homeassistant::step::mfa::data::code%]"
},
"description": "[%key:ui::panel::page-authorize::form::providers::homeassistant::step::mfa::description%]"
}
},
"error": {
"invalid_auth": "Invalid API password",
"invalid_code": "[%key:ui::panel::page-authorize::form::providers::homeassistant::error::invalid_code%]"
},
"abort": {
"no_api_password_set": "You don't have an API password configured.",
"login_expired": "[%key:ui::panel::page-authorize::form::providers::homeassistant::abort::login_expired%]"
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "User"
},
"description": "Please select a user you want to login as:"
}
},
"abort": {
"not_whitelisted": "Your computer is not whitelisted."
}
}
}
}
},
"page-onboarding": {
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
"user": {
"intro": "Let's get started by creating a user account.",
"required_field": "Required",
"data": {
"name": "Name",
"username": "Username",
"password": "Password"
},
"create_account": "Create Account",
"error": {
"required_fields": "Fill in all required fields"
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
import { loadJS } from '../../common/dom/load_resource.js';
import { loadJS, loadModule } from '../../common/dom/load_resource.js';
// Make sure we only import every JS-based panel once (HTML import has this built-in)
const JS_CACHE = {};
@@ -20,6 +20,8 @@ export default function loadCustomPanel(panelConfig) {
JS_CACHE[panelConfig.js_url] = loadJS(panelConfig.js_url);
}
return JS_CACHE[panelConfig.js_url];
} else if (panelConfig.module_url) {
return loadModule(panelConfig.module_url);
}
return Promise.reject('No valid url found in panel config.');
}

View File

@@ -4,12 +4,7 @@ export default function hassCallApi(host, auth, method, path, parameters) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open(method, url, true);
if (auth.authToken) {
req.setRequestHeader('X-HA-access', auth.authToken);
} else if (auth.accessToken) {
req.setRequestHeader('authorization', `Bearer ${auth.accessToken}`);
}
req.setRequestHeader('authorization', `Bearer ${auth.accessToken}`);
req.onload = function () {
let body = req.responseText;

View File

@@ -4,19 +4,17 @@ import canToggleDomain from '../../../src/common/entity/can_toggle_domain';
describe('canToggleDomain', () => {
const hass = {
config: {
services: {
light: {
turn_on: null, // Service keys only need to be present for test
turn_off: null,
},
lock: {
lock: null,
unlock: null,
},
sensor: {
custom_service: null,
},
services: {
light: {
turn_on: null, // Service keys only need to be present for test
turn_off: null,
},
lock: {
lock: null,
unlock: null,
},
sensor: {
custom_service: null,
},
},
};

View File

@@ -4,12 +4,10 @@ import canToggleState from '../../../src/common/entity/can_toggle_state';
describe('canToggleState', () => {
const hass = {
config: {
services: {
light: {
turn_on: null, // Service keys only need to be present for test
turn_off: null,
},
services: {
light: {
turn_on: null, // Service keys only need to be present for test
turn_off: null,
},
},
};

View File

@@ -4,12 +4,10 @@ import stateCardType from '../../../src/common/entity/state_card_type.js';
describe('stateCardType', () => {
const hass = {
config: {
services: {
light: {
turn_on: null, // Service keys only need to be present for test
turn_off: null,
},
services: {
light: {
turn_on: null, // Service keys only need to be present for test
turn_off: null,
},
},
};

View File

@@ -1,18 +0,0 @@
import { assert } from 'chai';
import parseQuery from '../../../src/common/util/parse_query.js';
describe('parseQuery', () => {
it('works', () => {
assert.deepEqual(parseQuery('hello=world'), { hello: 'world' });
assert.deepEqual(parseQuery('hello=world&drink=soda'), {
hello: 'world',
drink: 'soda',
});
assert.deepEqual(parseQuery('hello=world&no_value&drink=soda'), {
hello: 'world',
no_value: undefined,
drink: 'soda',
});
});
});

View File

@@ -531,6 +531,13 @@
"link_promo": "تعرف على التصاميم",
"dropdown_label": "التصميم"
}
},
"page-onboarding": {
"user": {
"error": {
"required_fields": "املأ جميع الحقول المطلوبة"
}
}
}
},
"sidebar": {

View File

@@ -525,6 +525,78 @@
"delete_user": "Изтриване на потребител"
}
}
},
"profile": {
"push_notifications": {
"header": "Известия",
"description": "Изпращане на известия до това устройство.",
"error_load_platform": "Конфигуриране на notify.html5.",
"error_use_https": "Изисква наличен SSL за уеб-интерфейса.",
"push_notifications": "Известия",
"link_promo": "Научете повече"
},
"language": {
"header": "Език",
"link_promo": "Помогнете за превода",
"dropdown_label": "Език"
},
"themes": {
"header": "Тема",
"error_no_theme": "Няма налични теми.",
"link_promo": "Научете повече за темите",
"dropdown_label": "Тема"
}
},
"page-authorize": {
"form": {
"working": "Моля, изчакайте",
"unknown_error": "Ся си еба майката",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Потребителско име",
"password": "Парола"
}
}
},
"error": {
"invalid_auth": "Невалидно потребителско име или парола"
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Потребител"
},
"description": "Моля, изберете потребител, като който искате да влезете:"
}
}
},
"legacy_api_password": {
"error": {
"invalid_code": "Невалиден код за аутентикация"
}
}
}
}
},
"page-onboarding": {
"user": {
"intro": "Нека да започнете, като създадете потребителски акаунт.",
"required_field": "Задължително",
"data": {
"name": "Име",
"username": "Потребителско име",
"password": "Парола"
},
"create_account": "Създай акаунт",
"error": {
"required_fields": "Попълнете всички задължителни полета"
}
}
}
},
"sidebar": {
@@ -684,6 +756,16 @@
"name": "Име",
"entity_id": "Идентификация на обект"
}
},
"auth_store": {
"ask": "Искате ли да запазите този логин?",
"decline": "Не, благодаря",
"confirm": "Запази логин"
},
"notification_drawer": {
"click_to_configure": "Щракнете върху бутона, за да конфигурирате {entity}",
"empty": "Няма известия",
"title": "Известия"
}
},
"domain": {

View File

@@ -546,6 +546,95 @@
"link_promo": "Més informació sobre temes",
"dropdown_label": "Tema"
}
},
"page-authorize": {
"initializing": "S'està inicialitzant",
"authorizing_client": "Esteu a punt de permetre l'accés a la vostra instància de Home Assistant al client {clientId}.",
"logging_in_with": "Iniciant sessió amb **{authProviderName}**.",
"pick_auth_provider": "O bé inicieu sessió amb",
"abort_intro": "S'ha avortat l'inici de sessió",
"form": {
"working": "Si us plau, espereu",
"unknown_error": "Alguna cosa no ha anat bé",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Nom d'usuari",
"password": "Contrasenya"
}
},
"mfa": {
"data": {
"code": "Codi de verificació en dos passos"
},
"description": "Obriu el **{mfa_module_name}** al vostre dispositiu per veure el codi de verificació en dos passos i verificar la vostra identitat:"
}
},
"error": {
"invalid_auth": "Nom d'usuari o contrasenya incorrectes",
"invalid_code": "El codi d'autenticació no és vàlid"
},
"abort": {
"login_expired": "La sessió ha caducat, torneu a iniciar la sessió."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "Contrasenya API"
},
"description": "Introduïu la contrasenya API en la vostra configuració http:"
},
"mfa": {
"data": {
"code": "Codi de verificació en dos passos"
},
"description": "Obriu el **{mfa_module_name}** al vostre dispositiu per veure el codi de verificació en dos passos i verificar la vostra identitat:"
}
},
"error": {
"invalid_auth": "Contrasenya API no vàlida",
"invalid_code": "El codi d'autenticació no és vàlid"
},
"abort": {
"no_api_password_set": "No teniu una contrasenya API configurada.",
"login_expired": "La sessió ha caducat, torneu a iniciar la sessió."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Usuari"
},
"description": "Seleccioneu l'usuari amb el qual iniciar la sessió:"
}
},
"abort": {
"not_whitelisted": "El teu ordinador no es troba accessible a la llista."
}
}
}
}
},
"page-onboarding": {
"intro": "Estàs preparat donar vida pròpia a la teva llar, recuperar la teva privacitat i unir-te a una comunitat mundial de \"tinkerers\"?",
"user": {
"intro": "Comencem creant un nou compte d'usuari.",
"required_field": "Obligatori",
"data": {
"name": "Nom",
"username": "Nom d'usuari",
"password": "Contrasenya"
},
"create_account": "Crear un compte",
"error": {
"required_fields": "Ompliu tots els camps obligatoris"
}
}
}
},
"sidebar": {
@@ -710,6 +799,11 @@
"ask": "Voleu desar aquest inici de sessió?",
"decline": "No, gràcies",
"confirm": "Desar inici de sessió"
},
"notification_drawer": {
"click_to_configure": "Prem el botó per configurar {entity}",
"empty": "No hi ha notificacions",
"title": "Notificacions"
}
},
"domain": {

View File

@@ -546,6 +546,95 @@
"link_promo": "Další info o motivech",
"dropdown_label": "Motiv"
}
},
"page-authorize": {
"initializing": "Inicializuji",
"authorizing_client": "Chystáte se dát {clientId} práva pro Home Assistant instanci.",
"logging_in_with": "Přihlásit se pomocí ** {authProviderName} **.",
"pick_auth_provider": "Nebo se přihlaste s",
"abort_intro": "Přihlášení bylo zrušeno",
"form": {
"working": "Počkejte prosím",
"unknown_error": "Něco se pokazilo",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Uživatelské jméno",
"password": "Heslo"
}
},
"mfa": {
"data": {
"code": "Dvoufaktorový ověřovací kód"
},
"description": "Otevřte **{mfa_module_name}** na vašem zařízení pro zobrazení dvoufaktorového ověřovacího kódu a ověřte vaší identitu"
}
},
"error": {
"invalid_auth": "Neplatné uživatelské jméno či heslo",
"invalid_code": "Neplatný ověřovací kód"
},
"abort": {
"login_expired": "Relace vypršela, přihlaste se prosím znovu."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "Heslo API"
},
"description": "Zadejte API heslo v http config"
},
"mfa": {
"data": {
"code": "Dvoufaktorový ověřovací kód"
},
"description": "Otevřte **{mfa_module_name}** na vašem zařízení pro zobrazení dvoufaktorového ověřovacího kódu a ověřte vaší identitu"
}
},
"error": {
"invalid_auth": "Neplatné heslo API",
"invalid_code": "Neplatný ověřovací kód"
},
"abort": {
"no_api_password_set": "Nemáte nakonfigurované heslo API.",
"login_expired": "Relace vypršela, přihlaste se prosím znovu."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Uživatel"
},
"description": "Vyberte uživatele, za kterého se chcete přihlásit"
}
},
"abort": {
"not_whitelisted": "Váš počítač není na seznamu povolených."
}
}
}
}
},
"page-onboarding": {
"intro": "Jste připraveni oživit svůj domov, zachovat si své soukromí a připojit se k celosvětové komunitě tvůrců?",
"user": {
"intro": "Začněme tím, že vytvoříme uživatelský účet.",
"required_field": "Povinný",
"data": {
"name": "Jméno",
"username": "Uživatelské jméno",
"password": "Heslo"
},
"create_account": "Vytvořit účet",
"error": {
"required_fields": "Vyplňte všechna povinná pole"
}
}
}
},
"sidebar": {
@@ -710,6 +799,11 @@
"ask": "Chcete toto přihlášení uložit?",
"decline": "Ne děkuji",
"confirm": "Uložit login"
},
"notification_drawer": {
"click_to_configure": "Klepnutím na tlačítko nastavíte {entity}",
"empty": "Žádné oznámení",
"title": "Oznámení"
}
},
"domain": {

View File

@@ -246,12 +246,17 @@
"lightning": "Lyn",
"lightning-rainy": "Lyn, regnvejr",
"partlycloudy": "Delvist overskyet",
"pouring": "Regnvejr",
"rainy": "Regnfuldt",
"snowy": "Snedækket",
"snowy-rainy": "Snedækket, regnfuldt"
"snowy-rainy": "Snedækket, regnfuldt",
"sunny": "Solrig",
"windy": "Blæsende",
"windy-variant": "Blæsende"
},
"vacuum": {
"cleaning": "Rengøring",
"docked": "I dock",
"error": "Fejl",
"idle": "Inaktiv",
"off": "Off",
@@ -321,7 +326,7 @@
},
"reloading": {
"heading": "Genindlæsning af konfiguration",
"introduction": "Nogle dele af Home Assistant kan genindlæse uden at kræve en genstart. Tryk på reload vil af montere deres nuværende konfiguration og indlæse den nye.",
"introduction": "Nogle dele af Home Assistant kan genindlæse uden at kræve en genstart. Tryk på \"genindlæs\" vil afmontere deres nuværende konfiguration og indlæse den nye.",
"core": "Genindlæs system",
"group": "Genindlæs grupper",
"automation": "Genindlæs automatiseringer",
@@ -344,6 +349,8 @@
"caption": "Automatisering",
"description": "Opret og rediger automatiseringer",
"picker": {
"header": "Automatiserings editor",
"introduction": "Automatiserings editoren giver dig mulighed for at oprette og redigere automatiseringer. Læs venligst [instruktionerne] (https:\/\/home-assistant.io\/docs\/automation\/editor\/) for at sikre dig, at du har konfigureret Home Assistant korrekt.",
"pick_automation": "Vælg automatisering for at redigere",
"no_automations": "Vi kunne ikke finde nogen redigerbare automatiseringer",
"add_automation": "Tilføj automatisering"
@@ -356,6 +363,7 @@
"alias": "Navn",
"triggers": {
"header": "Udløser",
"introduction": "Udløsere er, hvad der begynder behandlingen af en automatiseringsregel. Det er muligt at angive flere udløsere for samme regel. Når en udløser starter, vil Home Assistant validere betingelserne, hvis nogen, og udføre handlingen. \n\n [Lær mere om udløsere.] (Https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
"add": "Tilføj udløser",
"duplicate": "Kopier",
"delete": "Slet",
@@ -381,16 +389,21 @@
},
"mqtt": {
"label": "MQTT",
"topic": "Emne"
"topic": "Emne",
"payload": "Indhold (valgfri)"
},
"numeric_state": {
"label": "Numerisk tilstand",
"above": "Over",
"below": "Under"
"below": "Under",
"value_template": "Værdi-skabelon"
},
"sun": {
"label": "Sol",
"event": "Begivenhed",
"sunrise": "Solopgang",
"sunset": "Solnedgang"
"sunset": "Solnedgang",
"offset": "Timeout (valgfri)"
},
"template": {
"label": "Skabelon",
@@ -401,7 +414,9 @@
"at": "Klokken"
},
"zone": {
"label": "Zone",
"entity": "Enhed med placering",
"zone": "Zone",
"event": "Begivenhed",
"enter": "Ankom",
"leave": "Forlade"
@@ -410,6 +425,7 @@
},
"conditions": {
"header": "Betingelser",
"introduction": "Betingelser er en valgfri del af en automatiseringsregel og kan bruges til at forhindre, at en handling sker, når den udløses. Betingelser ligner meget udløsere, men er meget forskellige. En udløser vil se på begivenheder der sker i systemet, mens en tilstand kun ser på, hvordan systemet ser ud lige nu. En udløser kan observere, at der er tændt for en kontakt. En tilstand kan kun se, om en switch er i øjeblikket til eller fra. \n\n [Lær mere om betingelser.] (Https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)",
"add": "Tilføj betingelse",
"duplicate": "Kopier",
"delete": "Slet",
@@ -422,6 +438,7 @@
"state": "Tilstand"
},
"numeric_state": {
"label": "Numerisk stadie",
"above": "Over",
"below": "Under",
"value_template": "Værdi skabelon (ikke krævet)"
@@ -430,38 +447,56 @@
"label": "Sol",
"before": "Før:",
"after": "Efter:",
"before_offset": "Forskydning før",
"after_offset": "Forskydning efter",
"sunrise": "Solopgang",
"sunset": "Solnedgang"
},
"template": {
"label": "Skabelon"
"label": "Skabelon",
"value_template": "Værdi-skabelon"
},
"time": {
"label": "Tid",
"after": "Efter",
"before": "Før"
},
"zone": {
"label": "Zone",
"entity": "Enhed med placering",
"zone": "Zone"
}
}
},
"actions": {
"header": "Handlinger",
"introduction": "Handlingerne er, hvad Home Assistant skal gøre, når automatiseringen udløses. \n\n [Lær mere om handlinger.] (https:\/\/home-assistant.io\/docs\/automation\/action\/)",
"add": "Tilføj automatisering",
"duplicate": "Kopier",
"delete": "Slet",
"delete_confirm": "Er du sikker på du vil slette?",
"unsupported_action": "Ikke-understøttet handling: {action}",
"type_select": "Begivenhedstype",
"type": {
"service": {
"label": "Kald service"
"label": "Kald service",
"service_data": "Service data"
},
"delay": {
"label": "Forsinkelse",
"delay": "Forsinkelse"
},
"wait_template": {
"label": "Vent"
"label": "Vent",
"wait_template": "Vente-skabelon",
"timeout": "Timeout (valgfri)"
},
"condition": {
"label": "Betingelse"
},
"event": {
"label": "Afsend hændelse",
"event": "Hændelse:",
"service_data": "Service data"
}
}
@@ -477,6 +512,7 @@
"description": "Administrer dit Z-Wave netværk"
},
"users": {
"caption": "Brugere",
"description": "Administrer brugere",
"picker": {
"title": "Brugere"
@@ -510,6 +546,95 @@
"link_promo": "Lær om temaer",
"dropdown_label": "Tema"
}
},
"page-authorize": {
"initializing": "Initialiserer",
"authorizing_client": "Du er ved at give {clientId} adgang til din Home Assistant-instans.",
"logging_in_with": "Log ind med ** {authProviderName} **.",
"pick_auth_provider": "Eller log ind med",
"abort_intro": "Login afbrudt",
"form": {
"working": "Vent venligst",
"unknown_error": "Noget gik galt",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Brugernavn",
"password": "Password"
}
},
"mfa": {
"data": {
"code": "To-faktor godkendelseskode"
},
"description": "Åbn ** {mfa_module_name} ** på din enhed for at se din to-faktor godkendelseskode og bekræft din identitet:"
}
},
"error": {
"invalid_auth": "Ugyldigt brugernavn eller password",
"invalid_code": "Ugyldig godkendelseskode"
},
"abort": {
"login_expired": "Session er udløbet, log ind igen."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "API password"
},
"description": "Indtast venligst API-adgangskoden fra din http-konfiguration:"
},
"mfa": {
"data": {
"code": "To faktor godkendelseskode"
},
"description": "Åbn ** {mfa_module_name} ** på din enhed for at se din to-faktor godkendelseskode og bekræft din identitet:"
}
},
"error": {
"invalid_auth": "Ugyldig API password",
"invalid_code": "Ugyldig godkendelseskode"
},
"abort": {
"no_api_password_set": "Du har ikke konfigureret en API-adgangskode.",
"login_expired": "Sessionen er udløbet, log venligst ind igen"
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Bruger"
},
"description": "Vælg venligst den bruger, du vil logge ind som:"
}
},
"abort": {
"not_whitelisted": "Din computer er ikke whitelistet."
}
}
}
}
},
"page-onboarding": {
"intro": "Er du klar til at vække din hjem, genvinde dit privatliv og deltage i et verdensomspændende fællesskab af tinkerers?",
"user": {
"intro": "Lad os komme i gang ved at oprette en brugerkonto.",
"required_field": "Nødvendig",
"data": {
"name": "Navn",
"username": "Brugernavn",
"password": "Password"
},
"create_account": "Opret konto",
"error": {
"required_fields": "Udfyld alle obligatoriske felter"
}
}
}
},
"sidebar": {
@@ -546,7 +671,26 @@
"execute": "Udfør"
},
"weather": {
"attributes": {
"air_pressure": "Lufttryk",
"humidity": "Fugtighed",
"temperature": "Temperatur",
"visibility": "Sigtbarhed",
"wind_speed": "Vindhastighed"
},
"cardinal_direction": {
"e": "Ø",
"ene": "ØNØ",
"ese": "ØSØ",
"n": "N",
"ne": "NØ",
"nne": "NNØ",
"nw": "NV",
"nnw": "NNV",
"s": "S",
"se": "SØ",
"sse": "SSØ",
"ssw": "SSV",
"sw": "SV",
"w": "V",
"wnw": "VNV",
@@ -577,20 +721,29 @@
"light": {
"brightness": "Lysstyrke",
"color_temperature": "Farvetemperatur",
"white_value": "Hvidværdi",
"effect": "Effekt"
},
"media_player": {
"text_to_speak": "Tekst til tale",
"source": "Kilde",
"sound_mode": "Lydtilstand"
},
"climate": {
"currently": "Lige nu",
"on_off": "On \/ off",
"fan_mode": "Ventilator tilstand"
"target_temperature": "Ønsket temperatur",
"target_humidity": "Ønsket luftfugtighed",
"operation": "Drift",
"fan_mode": "Ventilator tilstand",
"away_mode": "Ude af huset-modus",
"aux_heat": "Støtte-varme"
},
"lock": {
"code": "Kode",
"lock": "Lås",
"unlock": "Lås op"
},
"media_player": {
"source": "Kilde"
},
"vacuum": {
"actions": {
"resume_cleaning": "Genoptag rengøring",
@@ -637,13 +790,19 @@
"dialogs": {
"more_info_settings": {
"save": "Gem",
"name": "Navn"
"name": "Navn",
"entity_id": "Enheds ID"
}
},
"auth_store": {
"ask": "Vil du gemme dette login?",
"decline": "Nej tak",
"confirm": "Gem login"
},
"notification_drawer": {
"click_to_configure": "Klik på knappen for at konfigurere {entity}",
"empty": "Ingen notifikationer",
"title": "Notifikationer"
}
},
"domain": {

View File

@@ -244,12 +244,12 @@
"fog": "Nebel",
"hail": "Hagel",
"lightning": "Gewitter",
"lightning-rainy": "Gewitter, Regnerisch",
"lightning-rainy": "Gewitter, regnerisch",
"partlycloudy": "Teilweise bewölkt",
"pouring": "Strömend",
"rainy": "Regnerisch",
"snowy": "Verschneit",
"snowy-rainy": "Verschneit, Regnerisch",
"snowy-rainy": "Verschneit, regnerisch",
"sunny": "Sonnig",
"windy": "Windig",
"windy-variant": "Windig"
@@ -529,7 +529,7 @@
"profile": {
"push_notifications": {
"header": "Push-Benachrichtigungen",
"description": "Sende Benachrichtigungen an dieses Gerät",
"description": "Sende Benachrichtigungen an dieses Gerät.",
"error_load_platform": "Konfiguriere notify.html5.",
"error_use_https": "Erfordert aktiviertes SSL für das Frontend.",
"push_notifications": "Push-Benachrichtigungen",
@@ -537,7 +537,7 @@
},
"language": {
"header": "Sprache",
"link_promo": "Helfe beim Übersetzen",
"link_promo": "Hilf beim Übersetzen",
"dropdown_label": "Sprache"
},
"themes": {
@@ -546,6 +546,95 @@
"link_promo": "Erfahre mehr über Themen",
"dropdown_label": "Thema"
}
},
"page-authorize": {
"initializing": "Initialisieren",
"authorizing_client": "Du bist dabei, {clientId} Zugriff auf deine Home Assistant Instanz zu gewähren.",
"logging_in_with": "Anmeldung mit **{authProviderName}**.",
"pick_auth_provider": "Oder melde dich an mit",
"abort_intro": "Anmeldung abgebrochen",
"form": {
"working": "Bitte warten",
"unknown_error": "Etwas lief schief",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Benutzername",
"password": "Passwort"
}
},
"mfa": {
"data": {
"code": "Zwei-Faktor Authentifizierungscode"
},
"description": "Öffne das **{mfa_module_name}** auf deinem Gerät um den 2-Faktor Authentifizierungscode zu sehen und deine Identität zu bestätigen:"
}
},
"error": {
"invalid_auth": "Ungültiger Benutzername oder Passwort",
"invalid_code": "Ungültiger Authentifizierungscode"
},
"abort": {
"login_expired": "Sitzung abgelaufen, bitte erneut anmelden."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "API-Passwort"
},
"description": "Bitte gib das API-Passwort deiner http-Konfiguration ein:"
},
"mfa": {
"data": {
"code": "Zwei-Faktor Authentifizierungscode"
},
"description": "Öffne das **{mfa_module_name}** auf deinem Gerät um den 2-Faktor Authentifizierungscode zu sehen und deine Identität zu bestätigen:"
}
},
"error": {
"invalid_auth": "Ungültiges API-Passwort",
"invalid_code": "Ungültiger Authentifizierungscode"
},
"abort": {
"no_api_password_set": "Du hast kein API-Passwort konfiguriert.",
"login_expired": "Sitzung abgelaufen, bitte erneut anmelden."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Benutzer"
},
"description": "Bitte wähle einen Benutzer aus, mit dem du dich anmelden möchtest:"
}
},
"abort": {
"not_whitelisted": "Dein Computer ist nicht auf der Whitelist."
}
}
}
}
},
"page-onboarding": {
"intro": "Bist du bereit, dein Zuhause zu erwecken, deine Privatsphäre zurückzugewinnen und einer weltweiten Gemeinschaft von Tüftlern beizutreten?",
"user": {
"intro": "Beginnen wir mit dem Erstellen eines Benutzerkontos.",
"required_field": "Erforderlich",
"data": {
"name": "Name",
"username": "Benutzername",
"password": "Passwort"
},
"create_account": "Benutzerkonto anlegen",
"error": {
"required_fields": "Fülle alle Pflichtfelder aus."
}
}
}
},
"sidebar": {
@@ -710,6 +799,11 @@
"ask": "Möchtest du diesen Login speichern?",
"decline": "Nein Danke",
"confirm": "Login speichern"
},
"notification_drawer": {
"click_to_configure": "Klicke auf die Schaltfläche, um {entity} zu konfigurieren.",
"empty": "Keine Benachrichtigungen",
"title": "Benachrichtigungen"
}
},
"domain": {

View File

@@ -546,6 +546,95 @@
"link_promo": "Learn about themes",
"dropdown_label": "Theme"
}
},
"page-authorize": {
"initializing": "Initializing",
"authorizing_client": "You're about to give {clientId} access to your Home Assistant instance.",
"logging_in_with": "Logging in with **{authProviderName}**.",
"pick_auth_provider": "Or log in with",
"abort_intro": "Login aborted",
"form": {
"working": "Please wait",
"unknown_error": "Something went wrong",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Username",
"password": "Password"
}
},
"mfa": {
"data": {
"code": "Two-factor Authentication Code"
},
"description": "Open the **{mfa_module_name}** on your device to view your two-factor authentication code and verify your identity:"
}
},
"error": {
"invalid_auth": "Invalid username or password",
"invalid_code": "Invalid authentication code"
},
"abort": {
"login_expired": "Session expired, please login again."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "API Password"
},
"description": "Please input the API password in your http config:"
},
"mfa": {
"data": {
"code": "Two-factor Authentication Code"
},
"description": "Open the **{mfa_module_name}** on your device to view your two-factor authentication code and verify your identity:"
}
},
"error": {
"invalid_auth": "Invalid API password",
"invalid_code": "Invalid authentication code"
},
"abort": {
"no_api_password_set": "You don't have an API password configured.",
"login_expired": "Session expired, please login again."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "User"
},
"description": "Please select a user you want to login as:"
}
},
"abort": {
"not_whitelisted": "Your computer is not whitelisted."
}
}
}
}
},
"page-onboarding": {
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
"user": {
"intro": "Let's get started by creating a user account.",
"required_field": "Required",
"data": {
"name": "Name",
"username": "Username",
"password": "Password"
},
"create_account": "Create Account",
"error": {
"required_fields": "Fill in all required fields"
}
}
}
},
"sidebar": {
@@ -710,6 +799,11 @@
"ask": "Do you want to save this login?",
"decline": "No thanks",
"confirm": "Save login"
},
"notification_drawer": {
"click_to_configure": "Click button to configure {entity}",
"empty": "No Notifications",
"title": "Notifications"
}
},
"domain": {

View File

@@ -20,7 +20,7 @@
"off": "Desactivado",
"on": "Encendido",
"unknown": "Desconocido",
"unavailable": "No Disponible"
"unavailable": "No disponible"
},
"alarm_control_panel": {
"armed": "Armado",
@@ -546,6 +546,95 @@
"link_promo": "Más información sobre los temas",
"dropdown_label": "Tema"
}
},
"page-authorize": {
"initializing": "Inicializando",
"authorizing_client": "Está por dar acceso a {clientId} a su instancia de Home Assistant.",
"logging_in_with": "Iniciando sesión con ** {authProviderName} **.",
"pick_auth_provider": "O inicia sesión con",
"abort_intro": "Inicio de sesión cancelado",
"form": {
"working": "Por favor, espere",
"unknown_error": "Algo salió mal",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Nombre de usuario",
"password": "Contraseña"
}
},
"mfa": {
"data": {
"code": "Código de autenticación de dos factores"
},
"description": "Abra el **{mfa_module_name}** en su dispositivo para ver su código de autenticar de dos factores y verificar su identidad:"
}
},
"error": {
"invalid_auth": "Nombre de usuario o contraseña inválidos",
"invalid_code": "Código de autenticación inválido"
},
"abort": {
"login_expired": "La sesión expiró, por favor inicie sesión nuevamente."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "Contraseña API"
},
"description": "Por favor, introduzca la contraseña de la API en su configuración http:"
},
"mfa": {
"data": {
"code": "Código de autenticado de dos factores"
},
"description": "Abra el **{mfa_module_name}** en su dispositivo para ver su código de autenticar de dos factores y verificar su identidad:"
}
},
"error": {
"invalid_auth": "Contraseña API inválida",
"invalid_code": "Código de autenticado invalido"
},
"abort": {
"no_api_password_set": "No tienes una contraseña API configurada.",
"login_expired": "La sesión ha expirado, por favor inicie sesión nuevamente."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Usuario"
},
"description": "Por favor, seleccione el usuario con el que desea iniciar sesión:"
}
},
"abort": {
"not_whitelisted": "Tu computadora no está incluida en la lista blanca."
}
}
}
}
},
"page-onboarding": {
"intro": "¿Estás listo para despertar tu hogar, reclamar tu privacidad y unirte a una comunidad mundial de experimentadores?",
"user": {
"intro": "Comencemos creando una cuenta de usuario.",
"required_field": "Necesario",
"data": {
"name": "Nombre",
"username": "Nombre de usuario",
"password": "Contraseña"
},
"create_account": "Crear una cuenta",
"error": {
"required_fields": "Completar todos los campos requeridos"
}
}
}
},
"sidebar": {
@@ -710,6 +799,11 @@
"ask": "¿Deseas guardar este inicio de sesión?",
"decline": "No, gracias",
"confirm": "Guardar inicio de sesión"
},
"notification_drawer": {
"click_to_configure": "Haga clic en el botón para configurar {entidad}.",
"empty": "Sin Notificaciones",
"title": "Notificaciones"
}
},
"domain": {

View File

@@ -484,7 +484,7 @@
},
"delay": {
"label": "Retardo",
"delay": "Retardo"
"delay": "Demora"
},
"wait_template": {
"label": "Esperar",
@@ -546,6 +546,95 @@
"link_promo": "Aprenda sobre los temas",
"dropdown_label": "Tema"
}
},
"page-authorize": {
"initializing": "Inicializando",
"authorizing_client": "Está por dar acceso a {clientId} a su instancia de Home Assistant.",
"logging_in_with": "Iniciando sesión con ** {authProviderName} **.",
"pick_auth_provider": "O inicia sesión con",
"abort_intro": "Inicio de sesión cancelado",
"form": {
"working": "Por favor, espere",
"unknown_error": "Algo salió mal",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Nombre de usuario",
"password": "Contraseña"
}
},
"mfa": {
"data": {
"code": "Código de autenticado de dos factores"
},
"description": "Abra el **{mfa_module_name}** en su dispositivo para ver su código autenticado de dos factores y verificar su identidad:"
}
},
"error": {
"invalid_auth": "Nombre de usuario o contraseña inválidos",
"invalid_code": "Código de autenticación inválido"
},
"abort": {
"login_expired": "La sesión expiró, por favor inicie sesión de nuevo."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "Contraseña de API"
},
"description": "Por favor, introduzca la contraseña de la API en su configuración http:"
},
"mfa": {
"data": {
"code": "Código de autenticar de dos factores"
},
"description": "Abra el **{mfa_module_name}** en su dispositivo para ver su código autenticado de dos factores y verificar su identidad:"
}
},
"error": {
"invalid_auth": "Contraseña de API inválida",
"invalid_code": "Código de verificación invalido"
},
"abort": {
"no_api_password_set": "No tienes una contraseña de API configurada.",
"login_expired": "Sesión vencida, por favor iniciar sesión de nuevo."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Usuario"
},
"description": "Por favor, seleccione el usuario con el que desea iniciar sesión:"
}
},
"abort": {
"not_whitelisted": "Tu computadora no está en la lista de autorizados."
}
}
}
}
},
"page-onboarding": {
"intro": "¿Estás listo para despertar tu casa, reclamar tu privacidad y unirte a una comunidad mundial de pensadores?",
"user": {
"intro": "Comencemos creando una cuenta de usuario.",
"required_field": "Obligatorio",
"data": {
"name": "Nombre",
"username": "Nombre de usuario",
"password": "Contraseña"
},
"create_account": "Crear una cuenta",
"error": {
"required_fields": "Complete todos los campos requeridos"
}
}
}
},
"sidebar": {
@@ -621,7 +710,8 @@
"trigger": "Desencadenar"
},
"cover": {
"position": "Posición"
"position": "Posición",
"tilt_position": "Posición inclinada"
},
"fan": {
"speed": "Velocidad",
@@ -658,7 +748,7 @@
"vacuum": {
"actions": {
"resume_cleaning": "Reanudar la limpieza",
"return_to_base": "Volverr a la base",
"return_to_base": "Volver a la base",
"start_cleaning": "Empezar la limpieza",
"turn_on": "Encender",
"turn_off": "Apagar"
@@ -709,6 +799,11 @@
"ask": "¿Quieres guardar este inicio de sesión?",
"decline": "No, gracias",
"confirm": "Guardar usuario"
},
"notification_drawer": {
"click_to_configure": "Haga clic en el botón para configurar {entity}",
"empty": "Sin Notificaciones",
"title": "Notificaciones"
}
},
"domain": {

View File

@@ -253,6 +253,16 @@
"sunny": "Päikeseline",
"windy": "Tuuline",
"windy-variant": "Tuuline"
},
"vacuum": {
"cleaning": "Puhastamine",
"docked": "Dokitud",
"error": "Viga",
"idle": "Ootel",
"off": "Väljas",
"on": "Sees",
"paused": "Peatatud",
"returning": "Pöördun tagasi dokki"
}
},
"state_badge": {
@@ -515,6 +525,27 @@
"delete_user": "Kustuta kasutaja"
}
}
},
"profile": {
"push_notifications": {
"header": "Tõuketeavitused",
"description": "Saada teatisi sellele seadmele.",
"error_load_platform": "Seadista notify.html5.",
"error_use_https": "Nõuab kasutajaliidese jaoks SSL lubamist.",
"push_notifications": "Tõuketeavitused",
"link_promo": "Lisateave"
},
"language": {
"header": "Keel",
"link_promo": "Aita tõlkida",
"dropdown_label": "Keel"
},
"themes": {
"header": "Teema",
"error_no_theme": "Teemasid pole saadaval.",
"link_promo": "Lisateave teemade kohta",
"dropdown_label": "Teema"
}
}
},
"sidebar": {
@@ -624,6 +655,15 @@
"code": "Kood",
"lock": "Lukusta",
"unlock": "Ava"
},
"vacuum": {
"actions": {
"resume_cleaning": "Jätka puhastamist",
"return_to_base": "Tagasi dokki",
"start_cleaning": "Alusta puhastamist",
"turn_on": "Lülita sisse",
"turn_off": "Lülita välja"
}
}
},
"components": {
@@ -665,6 +705,11 @@
"name": "Nimi",
"entity_id": "Üksuse ID"
}
},
"auth_store": {
"ask": "Kas soovid selle sisselogimise salvestada?",
"decline": "Tänan ei",
"confirm": "Salvesta sisselogimine"
}
},
"domain": {
@@ -702,7 +747,8 @@
"switch": "Lüliti",
"updater": "Uuendaja",
"weblink": "Veebilink",
"zwave": "Z-Wave"
"zwave": "Z-Wave",
"vacuum": "Tühjenda"
},
"attribute": {
"weather": {

View File

@@ -12,7 +12,8 @@
"dev-events": "Événements",
"dev-templates": "Templates",
"dev-mqtt": "MQTT",
"dev-info": "Info"
"dev-info": "Info",
"calendar": "Calendrier"
},
"state": {
"default": {
@@ -252,6 +253,16 @@
"sunny": "Soleil",
"windy": "Vent",
"windy-variant": "Vent"
},
"vacuum": {
"cleaning": "Nettoyage",
"docked": "Sur la base",
"error": "Erreur",
"idle": "Inactif",
"off": "Off",
"on": "On",
"paused": "En pause",
"returning": "Retourne à la base"
}
},
"state_badge": {
@@ -473,7 +484,7 @@
},
"delay": {
"label": "Délai",
"delay": "Délais"
"delay": "Délai"
},
"wait_template": {
"label": "Attendre",
@@ -499,6 +510,130 @@
"zwave": {
"caption": "Z-Wave",
"description": "Gérez votre réseau Z-Wave"
},
"users": {
"caption": "Utilisateurs",
"description": "Gérer les utilisateurs",
"picker": {
"title": "Utilisateurs"
},
"editor": {
"rename_user": "Renommer l'utilisateur",
"change_password": "Changer le mot de passe",
"activate_user": "Activer l'utilisateur",
"deactivate_user": "Désactiver l'utilisateur",
"delete_user": "Supprimer l'utilisateur"
}
}
},
"profile": {
"push_notifications": {
"header": "Notifications push",
"description": "Envoyer des notifications à cet appareil.",
"error_load_platform": "Configurer notify.html5.",
"error_use_https": "Nécessite l'activation de SSL pour le frontend.",
"push_notifications": "Notifications push",
"link_promo": "En savoir plus"
},
"language": {
"header": "Langue",
"link_promo": "Aider à traduire",
"dropdown_label": "Langue"
},
"themes": {
"header": "Thème",
"error_no_theme": "Aucun thème disponible.",
"link_promo": "En savoir plus sur les thèmes",
"dropdown_label": "Thème"
}
},
"page-authorize": {
"initializing": "Initialisation",
"authorizing_client": "Vous êtes sur le point de donner accès à {clientId} à votre instance de Home Assistant.",
"logging_in_with": "Se connecter avec **{authProviderName}**.",
"pick_auth_provider": "Ou connectez-vous avec",
"abort_intro": "Connexion interrompue",
"form": {
"working": "Veuillez patienter",
"unknown_error": "Quelque chose a mal tourné",
"providers": {
"homeassistant": {
"step": {
"init": {
"data": {
"username": "Nom d'utilisateur",
"password": "Mot de passe"
}
},
"mfa": {
"data": {
"code": "Code d'authentification à deux facteurs"
},
"description": "Ouvrez le ** {mfa_module_name} ** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
}
},
"error": {
"invalid_auth": "Nom d'utilisateur ou mot de passe invalide",
"invalid_code": "Code d'authentification invalide"
},
"abort": {
"login_expired": "Session expirée, veuillez vous connecter à nouveau."
}
},
"legacy_api_password": {
"step": {
"init": {
"data": {
"password": "Mot de passe API"
},
"description": "Veuillez saisir le mot de passe API dans votre configuration http:"
},
"mfa": {
"data": {
"code": "Code d'authentification à deux facteurs"
},
"description": "Ouvrez le ** {mfa_module_name} ** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
}
},
"error": {
"invalid_auth": "Mot de passe API invalide",
"invalid_code": "Code d'authentification invalide"
},
"abort": {
"no_api_password_set": "Vous n'avez pas de mot de passe API configuré.",
"login_expired": "Session expirée, veuillez vous connecter à nouveau."
}
},
"trusted_networks": {
"step": {
"init": {
"data": {
"user": "Utilisateur"
},
"description": "Sélectionnez l'utilisateur avec lequel vous souhaitez vous connecter :"
}
},
"abort": {
"not_whitelisted": "Votre ordinateur n'est pas en liste blanche."
}
}
}
}
},
"page-onboarding": {
"intro": "Êtes-vous prêt à réveiller votre maison, à récupérer votre vie privée et à rejoindre une communauté mondiale de bricoleurs?",
"user": {
"intro": "Commençons par créer un compte utilisateur.",
"required_field": "Requis",
"data": {
"name": "Nom",
"username": "Nom d'utilisateur",
"password": "Mot de passe"
},
"create_account": "Créer un compte",
"error": {
"required_fields": "Remplissez tous les champs requis"
}
}
}
},
@@ -550,8 +685,8 @@
"n": "N",
"ne": "NE",
"nne": "N-NE",
"nw": "NW",
"nnw": "N-NW",
"nw": "NO",
"nnw": "N-NO",
"s": "S",
"se": "SE",
"sse": "S-SE",
@@ -590,7 +725,9 @@
"effect": "Effet"
},
"media_player": {
"text_to_speak": "Texte à lire"
"text_to_speak": "Texte à lire",
"source": "Source",
"sound_mode": "Mode sonore"
},
"climate": {
"currently": "Actuellement",
@@ -607,6 +744,15 @@
"code": "Code",
"lock": "Verrouiller",
"unlock": "Déverrouiller"
},
"vacuum": {
"actions": {
"resume_cleaning": "Reprendre le nettoyage",
"return_to_base": "Retourner à la base",
"start_cleaning": "Commencer à nettoyer",
"turn_on": "Allumer",
"turn_off": "Éteindre"
}
}
},
"components": {
@@ -621,7 +767,14 @@
"relative_time": {
"past": "{time} auparavant",
"future": "Dans {time}",
"never": "Jamais"
"never": "Jamais",
"duration": {
"second": "{count} {count, plural,\none {seconde}\nother {secondes}\n}",
"minute": "{count} {count, plural,\none {minute}\nother {minutes}\n}",
"hour": "{count} {count, plural,\n one {hour}\n other {hours}\n}",
"day": "{count} {count, plural,\n one {day}\n other {days}\n}",
"week": "{count} {count, plural,\n one {week}\n other {weeks}\n}"
}
},
"history_charts": {
"loading_history": "Chargement de l'historique des valeurs ...",
@@ -634,6 +787,23 @@
"service_called": "Service \"{service}\" appelé.",
"service_call_failed": "Échec d'appel du service \"{service}\".",
"connection_lost": "Connexion perdue. Reconnexion en cours ..."
},
"dialogs": {
"more_info_settings": {
"save": "Sauvegarder",
"name": "Nom",
"entity_id": "ID de l'entité"
}
},
"auth_store": {
"ask": "Voulez-vous enregistrer cette connexion?",
"decline": "Non merci",
"confirm": "Enregistrer la connexion"
},
"notification_drawer": {
"click_to_configure": "Cliquez sur le bouton pour configurer {entity}",
"empty": "Aucune notification",
"title": "Notifications"
}
},
"domain": {
@@ -671,7 +841,8 @@
"switch": "Interrupteur",
"updater": "Mise à jour",
"weblink": "Lien",
"zwave": "Z-Wave"
"zwave": "Z-Wave",
"vacuum": "Aspirateur"
},
"attribute": {
"weather": {

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