mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-29 06:49:43 +00:00
Compare commits
95 Commits
20201203.0
...
multiple-a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e87accdfb4 | ||
![]() |
4dcfe3031d | ||
![]() |
1538fbb102 | ||
![]() |
b9259b87eb | ||
![]() |
30997dbc88 | ||
![]() |
607eb6d130 | ||
![]() |
f2e9b3577d | ||
![]() |
cb2c6d8560 | ||
![]() |
bfe8346ced | ||
![]() |
88da9bb91b | ||
![]() |
5e2ee1a16c | ||
![]() |
2fdc746392 | ||
![]() |
1667973a66 | ||
![]() |
42f0101440 | ||
![]() |
13b69bff1b | ||
![]() |
2c2226dfd6 | ||
![]() |
a3fdfe0e15 | ||
![]() |
a0de209a55 | ||
![]() |
0208b50ac7 | ||
![]() |
4a61779aba | ||
![]() |
05057ade05 | ||
![]() |
6fb206853c | ||
![]() |
fab68055bf | ||
![]() |
d5a77ef3cd | ||
![]() |
dcb2605de4 | ||
![]() |
e6d38f4539 | ||
![]() |
293b56cfa6 | ||
![]() |
775e93d54b | ||
![]() |
7f840e75df | ||
![]() |
2113ea675e | ||
![]() |
916a5c1a6b | ||
![]() |
f684531315 | ||
![]() |
fe8dda8996 | ||
![]() |
4cd4b328c8 | ||
![]() |
d844c89b94 | ||
![]() |
177ea2b85a | ||
![]() |
50c5c15f49 | ||
![]() |
1810760dc7 | ||
![]() |
4635b92e3f | ||
![]() |
1c652626eb | ||
![]() |
2000cfb1db | ||
![]() |
f4d07828e7 | ||
![]() |
95b552671c | ||
![]() |
ef3bc3efe1 | ||
![]() |
371ad899f5 | ||
![]() |
2c54158d84 | ||
![]() |
5d9e30bbdc | ||
![]() |
e477fd567d | ||
![]() |
6a6c2937fe | ||
![]() |
09e17c4da8 | ||
![]() |
fd00469d11 | ||
![]() |
cbbeb795f3 | ||
![]() |
bba40e0da8 | ||
![]() |
d23165d06a | ||
![]() |
405fef6f03 | ||
![]() |
588f217826 | ||
![]() |
3d8b7cf80e | ||
![]() |
c0ef923ad3 | ||
![]() |
3df44fc71e | ||
![]() |
c1965492d9 | ||
![]() |
1f56ffde80 | ||
![]() |
f335fdc002 | ||
![]() |
0c914b5ec8 | ||
![]() |
d767b06858 | ||
![]() |
d4e49f3944 | ||
![]() |
7dfc5b3faf | ||
![]() |
8a88033ab9 | ||
![]() |
7b06b38c94 | ||
![]() |
5409752817 | ||
![]() |
909f3a3005 | ||
![]() |
4930532c7b | ||
![]() |
8a42e65c6a | ||
![]() |
5d4121a9b4 | ||
![]() |
a70e6c49a1 | ||
![]() |
3d83d5f4b5 | ||
![]() |
f9dece0743 | ||
![]() |
ac0871d0e8 | ||
![]() |
ffc19e591d | ||
![]() |
c53380ca3d | ||
![]() |
7c74a2026a | ||
![]() |
adaed438d9 | ||
![]() |
baf38305cb | ||
![]() |
8254712521 | ||
![]() |
53214781e3 | ||
![]() |
88cbbbdf65 | ||
![]() |
c10dca9c7b | ||
![]() |
7f2ebb4bde | ||
![]() |
f1abb60e4a | ||
![]() |
e014c7aff6 | ||
![]() |
b79c03433e | ||
![]() |
34eb4d974d | ||
![]() |
3264be3c5e | ||
![]() |
655f4f75fb | ||
![]() |
4383f31696 | ||
![]() |
99eb15d15e |
10
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
10
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@@ -74,12 +74,12 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
|
||||
```
|
||||
|
||||
## Problem-relevant configuration
|
||||
## Problem-relevant frontend configuration
|
||||
|
||||
<!--
|
||||
An example configuration that caused the problem for you. Fill this out even
|
||||
if it seems unimportant to you. Please be sure to remove personal information
|
||||
like passwords, private URLs and other credentials.
|
||||
An example configuration that caused the problem for you, e.g. the YAML configuration
|
||||
of the used cards. Fill this out even if it seems unimportant to you. Please be sure
|
||||
to remove personal information like passwords, private URLs and other credentials.
|
||||
-->
|
||||
|
||||
```yaml
|
||||
@@ -89,7 +89,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
## Javascript errors shown in your browser console/inspector
|
||||
|
||||
<!--
|
||||
If you come across any javascript or other error logs, e.g., in your browser
|
||||
If you come across any Javascript or other error logs, e.g. in your browser
|
||||
console/inspector please provide them.
|
||||
-->
|
||||
|
||||
|
@@ -14,7 +14,7 @@ This is the repository for the official [Home Assistant](https://home-assistant.
|
||||
- Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/)
|
||||
- Production build: `script/build_frontend`
|
||||
- Gallery: `cd gallery && script/develop_gallery`
|
||||
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
|
||||
- Supervisor: [Instructions](https://developers.home-assistant.io/docs/supervisor/developing)
|
||||
|
||||
## Frontend development
|
||||
|
||||
|
@@ -36,6 +36,7 @@ const createWebpackConfig = ({
|
||||
const ignorePackages = bundle.ignorePackages({ latestBuild });
|
||||
return {
|
||||
mode: isProdBuild ? "production" : "development",
|
||||
target: ["web", latestBuild ? "es2017" : "es5"],
|
||||
devtool: isProdBuild
|
||||
? "cheap-module-source-map"
|
||||
: "eval-cheap-module-source-map",
|
||||
@@ -131,22 +132,6 @@ const createWebpackConfig = ({
|
||||
}
|
||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||
},
|
||||
environment: {
|
||||
// The environment supports arrow functions ('() => { ... }').
|
||||
arrowFunction: latestBuild,
|
||||
// The environment supports BigInt as literal (123n).
|
||||
bigIntLiteral: false,
|
||||
// The environment supports const and let for variable declarations.
|
||||
const: latestBuild,
|
||||
// The environment supports destructuring ('{ a, b } = obj').
|
||||
destructuring: latestBuild,
|
||||
// The environment supports an async import() function to import EcmaScript modules.
|
||||
dynamicImport: latestBuild,
|
||||
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
|
||||
forOf: latestBuild,
|
||||
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
|
||||
module: latestBuild,
|
||||
},
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild
|
||||
? "chunk.[chunkhash].js"
|
||||
|
@@ -9,30 +9,29 @@ class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
#card {
|
||||
max-width: 400px;
|
||||
width: 100vw;
|
||||
}
|
||||
ha-card {
|
||||
width: 333px;
|
||||
width: 352px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
state-card-content {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 400px;
|
||||
margin: 0 16px;
|
||||
overflow: auto;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
:host {
|
||||
.root {
|
||||
flex-direction: column;
|
||||
}
|
||||
pre {
|
||||
@@ -40,21 +39,25 @@ class DemoMoreInfo extends PolymerElement {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<ha-card>
|
||||
<state-card-content
|
||||
state-obj="[[_stateObj]]"
|
||||
hass="[[hass]]"
|
||||
in-dialog
|
||||
></state-card-content>
|
||||
<div class="root">
|
||||
<div id="card">
|
||||
<ha-card>
|
||||
<state-card-content
|
||||
state-obj="[[_stateObj]]"
|
||||
hass="[[hass]]"
|
||||
in-dialog
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></more-info-content>
|
||||
</ha-card>
|
||||
<template is="dom-if" if="[[showConfig]]">
|
||||
<pre>[[_jsonEntity(_stateObj)]]</pre>
|
||||
</template>
|
||||
<more-info-content
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></more-info-content>
|
||||
</ha-card>
|
||||
</div>
|
||||
<template is="dom-if" if="[[showConfig]]">
|
||||
<pre>[[_jsonEntity(_stateObj)]]</pre>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -3,12 +3,18 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "./demo-more-info";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
|
||||
class DemoMoreInfos extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
#container {
|
||||
min-height: calc(100vh - 128px);
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -23,20 +29,31 @@ class DemoMoreInfos extends PolymerElement {
|
||||
.filters {
|
||||
margin-left: 60px;
|
||||
}
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class="filters">
|
||||
<ha-switch checked="{{_showConfig}}">Show entity</ha-switch>
|
||||
<ha-formfield label="Show entities">
|
||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Dark theme">
|
||||
<ha-switch on-change="_darkThemeToggled"> </ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class="cards">
|
||||
<template is="dom-repeat" items="[[entities]]">
|
||||
<demo-more-info
|
||||
entity-id="[[item]]"
|
||||
show-config="[[_showConfig]]"
|
||||
hass="[[hass]]"
|
||||
></demo-more-info>
|
||||
</template>
|
||||
<div id="container">
|
||||
<div class="cards">
|
||||
<template is="dom-repeat" items="[[entities]]">
|
||||
<demo-more-info
|
||||
entity-id="[[item]]"
|
||||
show-config="[[_showConfig]]"
|
||||
hass="[[hass]]"
|
||||
></demo-more-info>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -51,6 +68,16 @@ class DemoMoreInfos extends PolymerElement {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_showConfigToggled(ev) {
|
||||
this._showConfig = ev.target.checked;
|
||||
}
|
||||
|
||||
_darkThemeToggled(ev) {
|
||||
applyThemesOnElement(this.$.container, { themes: {} }, "default", {
|
||||
dark: ev.target.checked,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-more-infos", DemoMoreInfos);
|
||||
|
72
gallery/src/data/plants.ts
Normal file
72
gallery/src/data/plants.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
|
||||
export const createPlantEntities = () => [
|
||||
getEntity("plant", "lemon_tree", "ok", {
|
||||
problem: "none",
|
||||
sensors: {
|
||||
moisture: "sensor.lemon_tree_moisture",
|
||||
battery: "sensor.lemon_tree_battery",
|
||||
temperature: "sensor.lemon_tree_temperature",
|
||||
conductivity: "sensor.lemon_tree_conductivity",
|
||||
brightness: "sensor.lemon_tree_brightness",
|
||||
},
|
||||
unit_of_measurement_dict: {
|
||||
temperature: "°C",
|
||||
moisture: "%",
|
||||
brightness: "lx",
|
||||
battery: "%",
|
||||
conductivity: "μS/cm",
|
||||
},
|
||||
moisture: 54,
|
||||
battery: 95,
|
||||
temperature: 15.6,
|
||||
conductivity: 1,
|
||||
brightness: 12,
|
||||
max_brightness: 20,
|
||||
friendly_name: "Lemon Tree",
|
||||
}),
|
||||
getEntity("plant", "apple_tree", "ok", {
|
||||
problem: "brightness",
|
||||
sensors: {
|
||||
moisture: "sensor.apple_tree_moisture",
|
||||
battery: "sensor.apple_tree_battery",
|
||||
temperature: "sensor.apple_tree_temperature",
|
||||
conductivity: "sensor.apple_tree_conductivity",
|
||||
brightness: "sensor.apple_tree_brightness",
|
||||
},
|
||||
unit_of_measurement_dict: {
|
||||
temperature: "°C",
|
||||
moisture: "%",
|
||||
brightness: "lx",
|
||||
battery: "%",
|
||||
conductivity: "μS/cm",
|
||||
},
|
||||
moisture: 54,
|
||||
battery: 2,
|
||||
temperature: 15.6,
|
||||
conductivity: 1,
|
||||
brightness: 25,
|
||||
max_brightness: 20,
|
||||
friendly_name: "Apple Tree",
|
||||
}),
|
||||
getEntity("plant", "sunflowers", "ok", {
|
||||
problem: "moisture, temperature, conductivity",
|
||||
sensors: {
|
||||
moisture: "sensor.sunflowers_moisture",
|
||||
temperature: "sensor.sunflowers_temperature",
|
||||
conductivity: "sensor.sunflowers_conductivity",
|
||||
brightness: "sensor.sunflowers_brightness",
|
||||
},
|
||||
unit_of_measurement_dict: {
|
||||
temperature: "°C",
|
||||
moisture: "%",
|
||||
brightness: "lx",
|
||||
conductivity: "μS/cm",
|
||||
},
|
||||
moisture: 54,
|
||||
temperature: 15.6,
|
||||
conductivity: 1,
|
||||
brightness: 25,
|
||||
entity_picture: "/images/sunflowers.jpg",
|
||||
}),
|
||||
];
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -71,28 +76,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoAlarmPanelEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-alarm-panel-card")
|
||||
class DemoAlarmPanelEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
this._setupDemo();
|
||||
}
|
||||
|
||||
private async _setupDemo() {
|
||||
const hass = provideHass(this.$.demos);
|
||||
await hass.updateTranslations(null, "en");
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -53,24 +58,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoConditional extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-conditional-card")
|
||||
class DemoConditional extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -217,24 +222,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoEntities extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-entities-card")
|
||||
class DemoEntities extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -69,24 +74,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoButtonEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-entity-button-card")
|
||||
class DemoButtonEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -89,26 +94,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoFilter extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-entity-filter-card")
|
||||
class DemoEntityFilter extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-entity-filter-card", DemoFilter);
|
||||
customElements.define("demo-hui-entity-filter-card", DemoEntityFilter);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -107,24 +112,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoGaugeEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-gauge-card")
|
||||
class DemoGaugeEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -218,26 +223,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-glance-card")
|
||||
class DemoGlanceEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-glance-card", DemoPicEntity);
|
||||
customElements.define("demo-hui-glance-card", DemoGlanceEntity);
|
||||
|
@@ -1,6 +1,4 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { html, LitElement, customElement, TemplateResult } from "lit-element";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -37,18 +35,10 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoIframe extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards configs="[[_configs]]"></demo-cards> `;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
@customElement("demo-hui-iframe-card")
|
||||
class DemoIframe extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -63,24 +68,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoLightEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-light-card")
|
||||
class DemoLightEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -161,24 +166,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoMap extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-map-card")
|
||||
class DemoMap extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { mockTemplate } from "../../../demo/src/stubs/template";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -254,23 +259,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoMarkdown extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-markdown-card")
|
||||
class DemoMarkdown extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
mockTemplate(hass);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
import { createMediaPlayerEntities } from "../data/media_players";
|
||||
@@ -158,26 +163,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoHuiMediControlCard extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-media-control-card")
|
||||
class DemoHuiMediaControlCard extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(createMediaPlayerEntities());
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-media-control-card", DemoHuiMediControlCard);
|
||||
customElements.define("demo-hui-media-control-card", DemoHuiMediaControlCard);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
import { createMediaPlayerEntities } from "../data/media_players";
|
||||
@@ -55,26 +60,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoHuiMediaPlayerRows extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-media-player-row")
|
||||
class DemoHuiMediaPlayerRow extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(createMediaPlayerEntities());
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows);
|
||||
customElements.define("demo-hui-media-player-row", DemoHuiMediaPlayerRow);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -125,26 +130,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicElements extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-picture-elements-card")
|
||||
class DemoPictureElements extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-picture-elements-card", DemoPicElements);
|
||||
customElements.define("demo-hui-picture-elements-card", DemoPictureElements);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -80,26 +85,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-picture-entity-card")
|
||||
class DemoPictureEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-picture-entity-card", DemoPicEntity);
|
||||
customElements.define("demo-hui-picture-entity-card", DemoPictureEntity);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -121,26 +126,21 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicGlance extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-picture-glance-card")
|
||||
class DemoPictureGlance extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-picture-glance-card", DemoPicGlance);
|
||||
customElements.define("demo-hui-picture-glance-card", DemoPictureGlance);
|
||||
|
55
gallery/src/demos/demo-hui-plant-card.ts
Normal file
55
gallery/src/demos/demo-hui-plant-card.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
import { createPlantEntities } from "../data/plants";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: plant-status
|
||||
entity: plant.lemon_tree
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Problem (too bright) + low battery",
|
||||
config: `
|
||||
- type: plant-status
|
||||
entity: plant.apple_tree
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With picture + multiple problems",
|
||||
config: `
|
||||
- type: plant-status
|
||||
entity: plant.sunflowers
|
||||
name: Sunflowers Name Overwrite
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-hui-plant-card")
|
||||
export class DemoPlantEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(createPlantEntities());
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-plant-card", DemoPlantEntity);
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
@@ -20,24 +25,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoShoppingListEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-shopping-list-card")
|
||||
class DemoShoppingListEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
|
||||
hass.mockAPI("shopping_list", () => [
|
||||
{ name: "list", id: 1, complete: false },
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { mockHistory } from "../../../demo/src/stubs/history";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
@@ -132,24 +137,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoStack extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-stack-card")
|
||||
class DemoStack extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockHistory(hass);
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
@@ -74,24 +79,19 @@ const CONFIGS = [
|
||||
},
|
||||
];
|
||||
|
||||
class DemoThermostatEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
|
||||
@customElement("demo-hui-thermostat-card")
|
||||
class DemoThermostatEntity extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,27 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
||||
import {
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_FLASH,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_TRANSITION,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
} from "../../../src/data/light";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import {
|
||||
provideHass,
|
||||
MockHomeAssistant,
|
||||
} from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-more-infos";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
|
||||
@@ -14,38 +31,52 @@ const ENTITIES = [
|
||||
}),
|
||||
getEntity("light", "kitchen_light", "on", {
|
||||
friendly_name: "Brightness Light",
|
||||
brightness: 80,
|
||||
brightness: 200,
|
||||
supported_features: SUPPORT_BRIGHTNESS,
|
||||
}),
|
||||
getEntity("light", "color_temperature_light", "on", {
|
||||
friendly_name: "White Color Temperature Light",
|
||||
brightness: 128,
|
||||
color_temp: 75,
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_features: SUPPORT_BRIGHTNESS + SUPPORT_COLOR_TEMP,
|
||||
}),
|
||||
getEntity("light", "color_effectslight", "on", {
|
||||
friendly_name: "Color Effets Light",
|
||||
brightness: 255,
|
||||
hs_color: [30, 100],
|
||||
white_value: 36,
|
||||
supported_features:
|
||||
SUPPORT_BRIGHTNESS +
|
||||
SUPPORT_EFFECT +
|
||||
SUPPORT_FLASH +
|
||||
SUPPORT_COLOR +
|
||||
SUPPORT_TRANSITION +
|
||||
SUPPORT_WHITE_VALUE,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
];
|
||||
|
||||
class DemoMoreInfoLight extends PolymerElement {
|
||||
static get template() {
|
||||
@customElement("demo-more-info-light")
|
||||
class DemoMoreInfoLight extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
hass="[[hass]]"
|
||||
entities="[[_entities]]"
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_entities: {
|
||||
type: Array,
|
||||
value: ENTITIES.map((ent) => ent.entityId),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
this._setupDemo();
|
||||
}
|
||||
|
||||
private async _setupDemo() {
|
||||
const hass = provideHass(this);
|
||||
await hass.updateTranslations(null, "en");
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import "@material/mwc-button";
|
||||
import { html, LitElement, TemplateResult } from "lit-element";
|
||||
import { customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { ActionHandlerEvent } from "../../../src/data/lovelace";
|
||||
import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive";
|
||||
|
||||
@customElement("demo-util-long-press")
|
||||
export class DemoUtilLongPress extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
@@ -20,7 +21,7 @@ export class DemoUtilLongPress extends LitElement {
|
||||
|
||||
<textarea></textarea>
|
||||
|
||||
<div>(try pressing and scrolling too!)</div>
|
||||
<div>Try pressing and scrolling too!</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
@@ -62,5 +63,3 @@ export class DemoUtilLongPress extends LitElement {
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-util-long-press", DemoUtilLongPress);
|
||||
|
@@ -14,8 +14,6 @@ import "../../src/styles/polymer-ha-style";
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { DEMOS } from "../build/import-demos";
|
||||
|
||||
const fixPath = (path) => path.substr(2, path.length - 5);
|
||||
|
||||
class HaGallery extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
|
@@ -69,7 +69,7 @@ const STAGE_ICON = {
|
||||
const PERMIS_DESC = {
|
||||
stage: {
|
||||
title: "Add-on Stage",
|
||||
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon .path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon .path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon .path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
|
||||
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
|
||||
},
|
||||
rating: {
|
||||
title: "Add-on Security Rating",
|
||||
|
@@ -178,7 +178,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</div>`}
|
||||
${!this.supervisor.supervisor.healthy
|
||||
? html`<div class="error">
|
||||
Your installtion is running in an unhealthy state.
|
||||
Your installation is running in an unhealthy state.
|
||||
<button
|
||||
class="link"
|
||||
title="Learn more about why your system is marked as unhealthy"
|
||||
|
32
package.json
32
package.json
@@ -29,22 +29,22 @@
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "5.1.0",
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@material/chips": "=8.0.0-canary.774dcfc8e.0",
|
||||
"@material/mwc-button": "^0.19.0",
|
||||
"@material/mwc-checkbox": "^0.19.0",
|
||||
"@material/mwc-circular-progress": "^0.19.0",
|
||||
"@material/mwc-dialog": "^0.19.0",
|
||||
"@material/mwc-fab": "^0.19.0",
|
||||
"@material/mwc-formfield": "^0.19.0",
|
||||
"@material/mwc-icon-button": "^0.19.0",
|
||||
"@material/mwc-list": "^0.19.0",
|
||||
"@material/mwc-menu": "^0.19.0",
|
||||
"@material/mwc-radio": "^0.19.0",
|
||||
"@material/mwc-ripple": "^0.19.0",
|
||||
"@material/mwc-switch": "^0.19.0",
|
||||
"@material/mwc-tab": "^0.19.0",
|
||||
"@material/mwc-tab-bar": "^0.19.0",
|
||||
"@material/top-app-bar": "=8.0.0-canary.774dcfc8e.0",
|
||||
"@material/chips": "=9.0.0-canary.1c156d69d.0",
|
||||
"@material/mwc-button": "^0.20.0",
|
||||
"@material/mwc-checkbox": "^0.20.0",
|
||||
"@material/mwc-circular-progress": "^0.20.0",
|
||||
"@material/mwc-dialog": "^0.20.0",
|
||||
"@material/mwc-fab": "^0.20.0",
|
||||
"@material/mwc-formfield": "^0.20.0",
|
||||
"@material/mwc-icon-button": "^0.20.0",
|
||||
"@material/mwc-list": "^0.20.0",
|
||||
"@material/mwc-menu": "^0.20.0",
|
||||
"@material/mwc-radio": "^0.20.0",
|
||||
"@material/mwc-ripple": "^0.20.0",
|
||||
"@material/mwc-switch": "^0.20.0",
|
||||
"@material/mwc-tab": "^0.20.0",
|
||||
"@material/mwc-tab-bar": "^0.20.0",
|
||||
"@material/top-app-bar": "=9.0.0-canary.1c156d69d.0",
|
||||
"@mdi/js": "5.6.55",
|
||||
"@mdi/svg": "5.6.55",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20201203.0",
|
||||
version="20201229.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -1,4 +1,13 @@
|
||||
export const copyToClipboard = (str) => {
|
||||
export const copyToClipboard = async (str) => {
|
||||
if (navigator.clipboard) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(str);
|
||||
return;
|
||||
} catch {
|
||||
// just continue with the fallback coding below
|
||||
}
|
||||
}
|
||||
|
||||
const el = document.createElement("textarea");
|
||||
el.value = str;
|
||||
document.body.appendChild(el);
|
||||
|
@@ -98,6 +98,12 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public hasFab = false;
|
||||
|
||||
/**
|
||||
* Add an extra row at the bottom of the data table
|
||||
* @type {TemplateResult}
|
||||
*/
|
||||
@property({ attribute: false }) public appendRow?;
|
||||
|
||||
@property({ type: Boolean, attribute: "auto-height" })
|
||||
public autoHeight = false;
|
||||
|
||||
@@ -126,6 +132,8 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
|
||||
private _items: DataTableRowData[] = [];
|
||||
|
||||
private _checkableRowsCount?: number;
|
||||
|
||||
private _checkedRows: string[] = [];
|
||||
@@ -318,10 +326,13 @@ export class HaDataTable extends LitElement {
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
${scroll({
|
||||
items: !this.hasFab
|
||||
? this._filteredData
|
||||
: [...this._filteredData, ...[{ empty: true }]],
|
||||
items: this._items,
|
||||
renderItem: (row: DataTableRowData, index) => {
|
||||
if (row.append) {
|
||||
return html`
|
||||
<div class="mdc-data-table__row">${row.content}</div>
|
||||
`;
|
||||
}
|
||||
if (row.empty) {
|
||||
return html` <div class="mdc-data-table__row"></div> `;
|
||||
}
|
||||
@@ -447,6 +458,20 @@ export class HaDataTable extends LitElement {
|
||||
if (this.curRequest !== curRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.appendRow || this.hasFab) {
|
||||
this._items = [...data];
|
||||
|
||||
if (this.appendRow) {
|
||||
this._items.push({ append: true, content: this.appendRow });
|
||||
}
|
||||
|
||||
if (this.hasFab) {
|
||||
this._items.push({ empty: true });
|
||||
}
|
||||
} else {
|
||||
this._items = data;
|
||||
}
|
||||
this._filteredData = data;
|
||||
}
|
||||
|
||||
|
@@ -58,6 +58,14 @@ const sortData = (
|
||||
valB = valB.toUpperCase();
|
||||
}
|
||||
|
||||
// Ensure "undefined" is always sorted to the bottom
|
||||
if (valA === undefined && valB !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
if (valB === undefined && valA !== undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (valA < valB) {
|
||||
return sort * -1;
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@@ -12,6 +13,8 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -35,6 +38,7 @@ import {
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { mdiClose, mdiMenuUp, mdiMenuDown } from "@mdi/js";
|
||||
|
||||
interface Device {
|
||||
name: string;
|
||||
@@ -111,17 +115,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
});
|
||||
}
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
}
|
||||
private _init = false;
|
||||
|
||||
private _getDevices = memoizeOne(
|
||||
(
|
||||
@@ -134,7 +130,13 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
deviceFilter: this["deviceFilter"]
|
||||
): Device[] => {
|
||||
if (!devices.length) {
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
area: "",
|
||||
name: this.hass.localize("ui.components.device-picker.no_devices"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
@@ -225,6 +227,15 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||
};
|
||||
});
|
||||
if (!outputDevices.length) {
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
area: "",
|
||||
name: this.hass.localize("ui.components.device-picker.no_match"),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (outputDevices.length === 1) {
|
||||
return outputDevices;
|
||||
}
|
||||
@@ -232,6 +243,18 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
});
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
@@ -246,25 +269,33 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(!this._init && this.devices && this.areas && this.entities) ||
|
||||
(changedProps.has("_opened") && this._opened)
|
||||
) {
|
||||
this._init = true;
|
||||
(this._comboBox as any).items = this._getDevices(
|
||||
this.devices!,
|
||||
this.areas!,
|
||||
this.entities!,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.devices || !this.areas || !this.entities) {
|
||||
return html``;
|
||||
}
|
||||
const devices = this._getDevices(
|
||||
this.devices,
|
||||
this.areas,
|
||||
this.entities,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter
|
||||
);
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
.items=${devices}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@@ -282,34 +313,30 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.device-picker.clear"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
@click=${this._clearValue}
|
||||
no-ripple
|
||||
>
|
||||
Clear
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${devices.length > 0
|
||||
? html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.device-picker.show_devices"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
Toggle
|
||||
</ha-icon-button>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.device-picker.show_devices"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
@@ -346,7 +373,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > ha-icon-button {
|
||||
paper-input > mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
|
@@ -165,6 +165,24 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (!states.length) {
|
||||
return [
|
||||
{
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null },
|
||||
attributes: {
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
icon: "mdi:magnify",
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
);
|
||||
@@ -215,7 +233,6 @@ export class HaEntityPicker extends LitElement {
|
||||
.label=${this.label === undefined
|
||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.disabled=${this.disabled}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@@ -14,6 +15,8 @@ import {
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import {
|
||||
@@ -40,6 +43,7 @@ import {
|
||||
} from "../data/entity_registry";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
@@ -121,6 +125,10 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _opened?: boolean;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
private _init = false;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
@@ -159,6 +167,15 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
entityFilter: this["entityFilter"],
|
||||
noAdd: this["noAdd"]
|
||||
): AreaRegistryEntry[] => {
|
||||
if (!areas.length) {
|
||||
return [
|
||||
{
|
||||
area_id: "",
|
||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||
let inputEntities: EntityRegistryEntry[] | undefined;
|
||||
@@ -243,7 +260,9 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
entities = entities.filter((entity) => entityFilter!(entity));
|
||||
inputEntities = inputEntities!.filter((entity) =>
|
||||
entityFilter!(entity)
|
||||
);
|
||||
}
|
||||
|
||||
let outputAreas = areas;
|
||||
@@ -268,6 +287,15 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
|
||||
}
|
||||
|
||||
if (!outputAreas.length) {
|
||||
outputAreas = [
|
||||
{
|
||||
area_id: "",
|
||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return noAdd
|
||||
? outputAreas
|
||||
: [
|
||||
@@ -280,27 +308,35 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(!this._init && this._devices && this._areas && this._entities) ||
|
||||
(changedProps.has("_opened") && this._opened)
|
||||
) {
|
||||
this._init = true;
|
||||
(this._comboBox as any).items = this._getAreas(
|
||||
this._areas!,
|
||||
this._devices!,
|
||||
this._entities!,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter,
|
||||
this.entityFilter,
|
||||
this.noAdd
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._devices || !this._areas || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
const areas = this._getAreas(
|
||||
this._areas,
|
||||
this._devices,
|
||||
this._entities,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter,
|
||||
this.entityFilter,
|
||||
this.noAdd
|
||||
);
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="area_id"
|
||||
item-id-path="area_id"
|
||||
item-label-path="name"
|
||||
.items=${areas}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@@ -321,34 +357,28 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.area-picker.clear"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
@click=${this._clearValue}
|
||||
no-ripple
|
||||
>
|
||||
${this.hass.localize("ui.components.area-picker.clear")}
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${areas.length > 0
|
||||
? html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.area-picker.show_areas"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
${this.hass.localize("ui.components.area-picker.toggle")}
|
||||
</ha-icon-button>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize("ui.components.area-picker.toggle")}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
@@ -424,7 +454,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > ha-icon-button {
|
||||
paper-input > mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
|
@@ -63,7 +63,7 @@ class HaAttributes extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.data-entry .value {
|
||||
max-width: 50%;
|
||||
max-width: 60%;
|
||||
overflow-wrap: break-word;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -77,6 +77,9 @@ class HaAttributes extends LitElement {
|
||||
pre {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0px;
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -127,7 +127,7 @@ class HaHLSPlayer extends LitElement {
|
||||
|
||||
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
|
||||
// See https://tools.ietf.org/html/rfc8216 for HLS spec details
|
||||
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(?<isHevc>hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(?<streamUrl>.+)/g;
|
||||
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(.+)/g;
|
||||
const match = playlistRegexp.exec(masterPlaylist);
|
||||
const matchTwice = playlistRegexp.exec(masterPlaylist);
|
||||
|
||||
@@ -136,17 +136,13 @@ class HaHLSPlayer extends LitElement {
|
||||
let playlist_url: string;
|
||||
if (match !== null && matchTwice === null) {
|
||||
// Only send the regular playlist url if we match exactly once
|
||||
playlist_url = new URL(match.groups!.streamUrl, this.url).href;
|
||||
playlist_url = new URL(match[2], this.url).href;
|
||||
} else {
|
||||
playlist_url = this.url;
|
||||
}
|
||||
|
||||
// If codec is HEVC and ExoPlayer is supported, use ExoPlayer.
|
||||
if (
|
||||
this._useExoPlayer &&
|
||||
match !== null &&
|
||||
match.groups!.isHevc !== undefined
|
||||
) {
|
||||
if (this._useExoPlayer && match !== null && match[1] !== undefined) {
|
||||
this._renderHLSExoPlayer(playlist_url);
|
||||
} else if (hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, hls, playlist_url);
|
||||
|
@@ -10,7 +10,6 @@ class HaLabeledSlider extends PolymerElement {
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@@ -30,6 +29,7 @@ class HaLabeledSlider extends PolymerElement {
|
||||
ha-slider {
|
||||
flex-grow: 1;
|
||||
background-image: var(--ha-slider-background);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import type { HomeAssistant } from "../types";
|
||||
class HaRelativeTime extends UpdatingElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public datetime?: string;
|
||||
@property({ attribute: false }) public datetime?: string | Date;
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
|
@@ -77,7 +77,8 @@ export class HaAreaSelector extends LitElement {
|
||||
}
|
||||
if (this.selector.area.device?.integration) {
|
||||
if (
|
||||
!this._configEntries?.some((entry) =>
|
||||
this._configEntries &&
|
||||
!this._configEntries.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
)
|
||||
) {
|
||||
|
@@ -63,7 +63,8 @@ export class HaDeviceSelector extends LitElement {
|
||||
}
|
||||
if (this.selector.device.integration) {
|
||||
if (
|
||||
!this._configEntries?.some((entry) =>
|
||||
this._configEntries &&
|
||||
!this._configEntries.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
)
|
||||
) {
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
subscribeAreaRegistry,
|
||||
} from "../data/area_registry";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../data/device_registry";
|
||||
@@ -135,7 +136,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device?.name || device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
@@ -435,10 +436,19 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type: string,
|
||||
id: string
|
||||
): this["value"] {
|
||||
return {
|
||||
...value,
|
||||
[type]: ensureArray(value![type])!.filter((val) => val !== id),
|
||||
};
|
||||
const newVal = ensureArray(value![type])!.filter((val) => val !== id);
|
||||
if (newVal.length) {
|
||||
return {
|
||||
...value,
|
||||
[type]: newVal,
|
||||
};
|
||||
}
|
||||
const val = { ...value }!;
|
||||
delete val[type];
|
||||
if (Object.keys(val).length) {
|
||||
return val;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
|
||||
|
@@ -124,13 +124,17 @@ export const getLogbookMessage = (
|
||||
switch (domain) {
|
||||
case "device_tracker":
|
||||
case "person":
|
||||
return state === "not_home"
|
||||
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`)
|
||||
: hass.localize(
|
||||
`${LOGBOOK_LOCALIZE_PATH}.was_at_state`,
|
||||
"state",
|
||||
state
|
||||
);
|
||||
if (state === "not_home") {
|
||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
|
||||
}
|
||||
if (state === "home") {
|
||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
|
||||
}
|
||||
return hass.localize(
|
||||
`${LOGBOOK_LOCALIZE_PATH}.was_at_state`,
|
||||
"state",
|
||||
state
|
||||
);
|
||||
|
||||
case "sun":
|
||||
return state === "above_horizon"
|
||||
|
@@ -148,6 +148,11 @@ export interface CustomActionConfig extends BaseActionConfig {
|
||||
action: "fire-dom-event";
|
||||
}
|
||||
|
||||
export interface MultipleActionConfig extends BaseActionConfig {
|
||||
action: "multiple";
|
||||
actions: ActionConfig[];
|
||||
}
|
||||
|
||||
export interface BaseActionConfig {
|
||||
confirmation?: ConfirmationRestrictionConfig;
|
||||
}
|
||||
@@ -168,7 +173,8 @@ export type ActionConfig =
|
||||
| UrlActionConfig
|
||||
| MoreInfoActionConfig
|
||||
| NoActionConfig
|
||||
| CustomActionConfig;
|
||||
| CustomActionConfig
|
||||
| MultipleActionConfig;
|
||||
|
||||
type LovelaceUpdatedEvent = HassEventBase & {
|
||||
event_type: "lovelace_updated";
|
||||
|
@@ -38,3 +38,12 @@ export const addItem = (
|
||||
type: "shopping_list/items/add",
|
||||
name,
|
||||
});
|
||||
|
||||
export const reorderItems = (
|
||||
hass: HomeAssistant,
|
||||
itemIds: [string]
|
||||
): Promise<ShoppingListItem> =>
|
||||
hass.callWS({
|
||||
type: "shopping_list/items/reorder",
|
||||
item_ids: itemIds,
|
||||
});
|
||||
|
@@ -17,17 +17,17 @@ import "../../components/ha-switch";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { DialogParams } from "./show-dialog-box";
|
||||
import { DialogBoxParams } from "./show-dialog-box";
|
||||
|
||||
@customElement("dialog-box")
|
||||
class DialogBox extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _params?: DialogParams;
|
||||
@internalProperty() private _params?: DialogBoxParams;
|
||||
|
||||
@internalProperty() private _value?: string;
|
||||
|
||||
public async showDialog(params: DialogParams): Promise<void> {
|
||||
public async showDialog(params: DialogBoxParams): Promise<void> {
|
||||
this._params = params;
|
||||
if (params.prompt) {
|
||||
this._value = params.defaultValue;
|
||||
@@ -55,8 +55,8 @@ class DialogBox extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
?scrimClickAction=${this._params.prompt}
|
||||
?escapeKeyAction=${this._params.prompt}
|
||||
?scrimClickAction=${confirmPrompt}
|
||||
?escapeKeyAction=${confirmPrompt}
|
||||
@closed=${this._dialogClosed}
|
||||
defaultAction="ignore"
|
||||
.heading=${this._params.title
|
||||
@@ -140,10 +140,10 @@ class DialogBox extends LitElement {
|
||||
}
|
||||
|
||||
private _dialogClosed(ev) {
|
||||
if (ev.detail.action === "ignore") {
|
||||
if (this._params?.prompt && ev.detail.action === "ignore") {
|
||||
return;
|
||||
}
|
||||
this.closeDialog();
|
||||
this._dismiss();
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
|
@@ -1,31 +1,31 @@
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
interface BaseDialogParams {
|
||||
interface BaseDialogBoxParams {
|
||||
confirmText?: string;
|
||||
text?: string | TemplateResult;
|
||||
title?: string;
|
||||
warning?: boolean;
|
||||
}
|
||||
|
||||
export interface AlertDialogParams extends BaseDialogParams {
|
||||
export interface AlertDialogParams extends BaseDialogBoxParams {
|
||||
confirm?: () => void;
|
||||
}
|
||||
|
||||
export interface ConfirmationDialogParams extends BaseDialogParams {
|
||||
export interface ConfirmationDialogParams extends BaseDialogBoxParams {
|
||||
dismissText?: string;
|
||||
confirm?: () => void;
|
||||
cancel?: () => void;
|
||||
}
|
||||
|
||||
export interface PromptDialogParams extends BaseDialogParams {
|
||||
export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
inputLabel?: string;
|
||||
inputType?: string;
|
||||
defaultValue?: string;
|
||||
confirm?: (out?: string) => void;
|
||||
}
|
||||
|
||||
export interface DialogParams
|
||||
export interface DialogBoxParams
|
||||
extends ConfirmationDialogParams,
|
||||
PromptDialogParams {
|
||||
confirm?: (out?: string) => void;
|
||||
@@ -37,10 +37,10 @@ export const loadGenericDialog = () => import("./dialog-box");
|
||||
|
||||
const showDialogHelper = (
|
||||
element: HTMLElement,
|
||||
dialogParams: DialogParams,
|
||||
dialogParams: DialogBoxParams,
|
||||
extra?: {
|
||||
confirmation?: DialogParams["confirmation"];
|
||||
prompt?: DialogParams["prompt"];
|
||||
confirmation?: DialogBoxParams["confirmation"];
|
||||
prompt?: DialogBoxParams["prompt"];
|
||||
}
|
||||
) =>
|
||||
new Promise((resolve) => {
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
|
||||
@customElement("more-info-counter")
|
||||
class MoreInfoCounter extends LitElement {
|
||||
@@ -22,21 +23,29 @@ class MoreInfoCounter extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const disabled = UNAVAILABLE_STATES.includes(this.stateObj!.state);
|
||||
|
||||
return html`
|
||||
<div class="actions">
|
||||
<mwc-button
|
||||
.action="${"increment"}"
|
||||
@click="${this._handleActionClick}"
|
||||
@click=${this._handleActionClick}
|
||||
.disabled=${disabled}
|
||||
>
|
||||
${this.hass!.localize("ui.card.counter.actions.increment")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
.action="${"decrement"}"
|
||||
@click="${this._handleActionClick}"
|
||||
@click=${this._handleActionClick}
|
||||
.disabled=${disabled}
|
||||
>
|
||||
${this.hass!.localize("ui.card.counter.actions.decrement")}
|
||||
</mwc-button>
|
||||
<mwc-button .action="${"reset"}" @click="${this._handleActionClick}">
|
||||
<mwc-button
|
||||
.action="${"reset"}"
|
||||
@click=${this._handleActionClick}
|
||||
.disabled=${disabled}
|
||||
>
|
||||
${this.hass!.localize("ui.card.counter.actions.reset")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
@@ -53,8 +62,7 @@ class MoreInfoCounter extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.actions {
|
||||
margin: 0;
|
||||
padding-top: 20px;
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
@@ -75,7 +75,7 @@ class MoreInfoPerson extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.actions {
|
||||
margin: 36px 0 8px 0;
|
||||
margin: 8px 0;
|
||||
text-align: right;
|
||||
}
|
||||
ha-map {
|
||||
|
@@ -44,7 +44,7 @@ class MoreInfoSun extends LitElement {
|
||||
>
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetimeObj=${item === "ris" ? risingDate : settingDate}
|
||||
.datetime=${item === "ris" ? risingDate : settingDate}
|
||||
></ha-relative-time>
|
||||
</div>
|
||||
<div class="value">
|
||||
@@ -80,6 +80,7 @@ class MoreInfoSun extends LitElement {
|
||||
}
|
||||
ha-relative-time {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
ha-relative-time::first-letter {
|
||||
text-transform: lowercase;
|
||||
|
@@ -76,8 +76,7 @@ class MoreInfoTimer extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.actions {
|
||||
margin: 0;
|
||||
padding-top: 20px;
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
@@ -60,6 +60,12 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
*/
|
||||
@property({ type: Boolean }) public hasFab = false;
|
||||
|
||||
/**
|
||||
* Add an extra row at the bottom of the data table
|
||||
* @type {TemplateResult}
|
||||
*/
|
||||
@property({ attribute: false }) public appendRow?;
|
||||
|
||||
/**
|
||||
* Field with a unique id per entry in data.
|
||||
* @type {String}
|
||||
@@ -171,6 +177,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
.noDataText=${this.noDataText}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
.clickable=${this.clickable}
|
||||
.appendRow=${this.appendRow}
|
||||
>
|
||||
${!this.narrow
|
||||
? html`
|
||||
|
@@ -15,12 +15,14 @@ import {
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { Action } from "../../../../data/script";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -103,6 +105,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@internalProperty() private _yamlMode = false;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
@@ -111,6 +115,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
if (!this._uiModeAvailable && !this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
}
|
||||
|
||||
if (this._yamlMode && this._yamlEditor) {
|
||||
this._yamlEditor.setValue(this.action);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -187,7 +195,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<ul>
|
||||
${this._warnings.map((warning) => html`<li>${warning}</li>`)}
|
||||
</ul>
|
||||
You can still edit your config in yaml.
|
||||
You can still edit your config in YAML.
|
||||
</div>`
|
||||
: ""}
|
||||
${yamlMode
|
||||
|
@@ -19,12 +19,12 @@ import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
||||
import { ServiceAction } from "../../../../../data/script";
|
||||
import type { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { EntityId } from "../../../../lovelace/common/structs/is-entity-id";
|
||||
import { EntityIdOrAll } from "../../../../lovelace/common/structs/is-entity-id";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
|
||||
const actionStruct = object({
|
||||
service: optional(string()),
|
||||
entity_id: optional(EntityId),
|
||||
entity_id: optional(EntityIdOrAll),
|
||||
data: optional(any()),
|
||||
});
|
||||
|
||||
|
@@ -18,9 +18,6 @@ import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
import "./condition/ha-automation-condition";
|
||||
import "./action/ha-automation-action";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
@@ -151,7 +148,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
There is an error in this Blueprint: ${blueprint.error}
|
||||
</p>`
|
||||
: html`${blueprint?.metadata.description
|
||||
? html`<p class="card-content">
|
||||
? html`<p class="card-content pre-line">
|
||||
${blueprint.metadata.description}
|
||||
</p>`
|
||||
: ""}
|
||||
@@ -168,7 +165,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
.selector=${value.selector}
|
||||
.key=${key}
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ||
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
></ha-selector>`
|
||||
@@ -227,12 +224,18 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const input = { ...this.config.use_blueprint.input, [key]: value };
|
||||
|
||||
if (value === "" || value === undefined) {
|
||||
delete input[key];
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config!,
|
||||
use_blueprint: {
|
||||
...this.config.use_blueprint,
|
||||
input: { ...this.config.use_blueprint.input, [key]: value },
|
||||
input,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -264,6 +267,9 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
.padding {
|
||||
padding: 16px;
|
||||
}
|
||||
.pre-line {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.blueprint-picker-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -394,9 +395,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
return cleanConfig;
|
||||
}
|
||||
|
||||
private async _copyYaml() {
|
||||
private async _copyYaml(): Promise<void> {
|
||||
if (this._editor?.yaml) {
|
||||
navigator.clipboard.writeText(this._editor.yaml);
|
||||
await copyToClipboard(this._editor.yaml);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
internalProperty,
|
||||
query,
|
||||
TemplateResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
@@ -73,7 +74,9 @@ class DialogImportBlueprint extends LitElement {
|
||||
this._result.blueprint.metadata.domain
|
||||
)}
|
||||
<br />
|
||||
${this._result.blueprint.metadata.description}
|
||||
<p class="pre-line">
|
||||
${this._result.blueprint.metadata.description}
|
||||
</p>
|
||||
${this._result.validation_errors
|
||||
? html`
|
||||
<p class="error">
|
||||
@@ -104,7 +107,16 @@ class DialogImportBlueprint extends LitElement {
|
||||
<pre>${this._result.raw_data}</pre>
|
||||
</ha-expansion-panel>`
|
||||
: html`${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.import_introduction"
|
||||
"ui.panel.config.blueprint.add.import_introduction_link",
|
||||
"community_link",
|
||||
html`<a
|
||||
href="https://www.home-assistant.io/get-blueprints"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.community_forums"
|
||||
)}</a
|
||||
>`
|
||||
)}<paper-input
|
||||
id="input"
|
||||
.label=${this.hass.localize(
|
||||
@@ -199,8 +211,15 @@ class DialogImportBlueprint extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return haStyleDialog;
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.pre-line {
|
||||
white-space: pre-line;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlus, mdiHelpCircle, mdiDelete, mdiRobot } from "@mdi/js";
|
||||
import { mdiHelpCircle, mdiDelete, mdiRobot, mdiDownload } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
CSSResult,
|
||||
@@ -170,6 +170,23 @@ class HaBlueprintOverview extends LitElement {
|
||||
"ui.panel.config.blueprint.overview.no_blueprints"
|
||||
)}
|
||||
hasFab
|
||||
.appendRow=${html` <div
|
||||
class="mdc-data-table__cell"
|
||||
style="width: 100%; text-align: center;"
|
||||
role="cell"
|
||||
>
|
||||
<a
|
||||
href="https://www.home-assistant.io/get-blueprints"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<mwc-button
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.discover_more"
|
||||
)}</mwc-button
|
||||
>
|
||||
</a>
|
||||
</div>`}
|
||||
>
|
||||
<mwc-icon-button slot="toolbar-icon" @click=${this._showHelp}>
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
@@ -182,7 +199,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
extended
|
||||
@click=${this._addBlueprint}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
@@ -195,7 +212,10 @@ class HaBlueprintOverview extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.blueprint.overview.introduction")}
|
||||
<p>
|
||||
<a
|
||||
href="${documentationUrl(this.hass, "/docs/blueprint/editor/")}"
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/automation/using_blueprints/"
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
@@ -92,7 +92,7 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_computeTabs() {
|
||||
return configSections.general;
|
||||
return configSections.advanced;
|
||||
}
|
||||
|
||||
computeEntities(hass) {
|
||||
|
@@ -189,9 +189,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
),
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
area: device.area_id
|
||||
? areaLookup[device.area_id].name
|
||||
: this.hass.localize("ui.panel.config.devices.data_table.no_area"),
|
||||
area: device.area_id ? areaLookup[device.area_id].name : undefined,
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
|
@@ -148,7 +148,7 @@ export class HaConfigHelpers extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.automation}
|
||||
.tabs=${configSections.helpers}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._getItems(this._stateItems)}
|
||||
@row-click=${this._openEditDialog}
|
||||
|
@@ -197,7 +197,7 @@ class SystemHealthCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _copyInfo(ev: CustomEvent<ActionDetail>): void {
|
||||
private async _copyInfo(ev: CustomEvent<ActionDetail>): Promise<void> {
|
||||
const github = ev.detail.index === 1;
|
||||
let haContent: string | undefined;
|
||||
const domainParts: string[] = [];
|
||||
@@ -250,13 +250,15 @@ class SystemHealthCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
copyToClipboard(
|
||||
await copyToClipboard(
|
||||
`${github ? "## " : ""}System Health\n${haContent}\n\n${domainParts.join(
|
||||
"\n\n"
|
||||
)}`
|
||||
);
|
||||
|
||||
showToast(this, { message: this.hass.localize("ui.common.copied") });
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
|
@@ -141,11 +141,15 @@ class OZWNodeDashboard extends LitElement {
|
||||
${this._metadata.metadata.ResetHelp}
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card class="content" header="WakeUp">
|
||||
<div class="card-content">
|
||||
${this._metadata.metadata.WakeupHelp}
|
||||
</div>
|
||||
</ha-card>
|
||||
${this._metadata.metadata.WakeupHelp
|
||||
? html`
|
||||
<ha-card class="content" header="WakeUp">
|
||||
<div class="card-content">
|
||||
${this._metadata.metadata.WakeupHelp}
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ``}
|
||||
`
|
||||
: ``}
|
||||
`
|
||||
@@ -199,6 +203,10 @@ class OZWNodeDashboard extends LitElement {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.content:last-child {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
position: relative;
|
||||
padding-right: 40px;
|
||||
|
@@ -49,19 +49,17 @@ class ZHADevicePairingStatusCard extends LitElement {
|
||||
class="discovered ${classMap({
|
||||
initialized: this.device.pairing_status === INITIALIZED,
|
||||
})}"
|
||||
><div
|
||||
class="header"
|
||||
>
|
||||
<h1>
|
||||
><div class="header">
|
||||
<h4>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}`
|
||||
)}
|
||||
</h1>
|
||||
<h4>
|
||||
</h4>
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}_status_text`
|
||||
)}
|
||||
</h4>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
${[INTERVIEW_COMPLETE, CONFIGURED].includes(
|
||||
|
@@ -15,6 +15,12 @@ import { fetchDevices, ZHADevice } from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { Network, Edge, Node, EdgeOptions } from "vis-network";
|
||||
import "../../../../../common/search/search-input";
|
||||
import "../../../../../components/ha-button-menu";
|
||||
import "../../../../../components/device/ha-device-picker";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
|
||||
@customElement("zha-network-visualization-page")
|
||||
export class ZHANetworkVisualizationPage extends LitElement {
|
||||
@@ -28,9 +34,21 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
@internalProperty()
|
||||
private _devices: Map<string, ZHADevice> = new Map();
|
||||
|
||||
@internalProperty()
|
||||
private _devicesByDeviceId: Map<string, ZHADevice> = new Map();
|
||||
|
||||
@internalProperty()
|
||||
private _nodes: Node[] = [];
|
||||
|
||||
@internalProperty()
|
||||
private _network?: Network;
|
||||
|
||||
@internalProperty()
|
||||
private _filter?: string;
|
||||
|
||||
@internalProperty()
|
||||
private _zoomedDeviceId?: string;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
@@ -91,6 +109,27 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
"ui.panel.config.zha.visualization.header"
|
||||
)}
|
||||
>
|
||||
<div class="table-header">
|
||||
<search-input
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zha.visualization.highlight_label"
|
||||
)}
|
||||
>
|
||||
</search-input>
|
||||
<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._zoomedDeviceId}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zha.visualization.zoom_label"
|
||||
)}
|
||||
.includeDomains="['zha']"
|
||||
@value-changed=${this._zoomToDevice}
|
||||
></ha-device-picker>
|
||||
</div>
|
||||
<div id="visualization"></div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@@ -101,15 +140,18 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
this._devices = new Map(
|
||||
devices.map((device: ZHADevice) => [device.ieee, device])
|
||||
);
|
||||
this._devicesByDeviceId = new Map(
|
||||
devices.map((device: ZHADevice) => [device.device_reg_id, device])
|
||||
);
|
||||
this._updateDevices(devices);
|
||||
}
|
||||
|
||||
private _updateDevices(devices: ZHADevice[]) {
|
||||
const nodes: Node[] = [];
|
||||
this._nodes = [];
|
||||
const edges: Edge[] = [];
|
||||
|
||||
devices.forEach((device) => {
|
||||
nodes.push({
|
||||
this._nodes.push({
|
||||
id: device.ieee,
|
||||
label: this._buildLabel(device),
|
||||
shape: this._getShape(device),
|
||||
@@ -137,7 +179,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
}
|
||||
});
|
||||
|
||||
this._network?.setData({ nodes: nodes, edges: edges });
|
||||
this._network?.setData({ nodes: this._nodes, edges: edges });
|
||||
}
|
||||
|
||||
private _getLQI(lqi: number): EdgeOptions["color"] {
|
||||
@@ -181,7 +223,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
label += `<b>IEEE: </b>${device.ieee}`;
|
||||
label += `\n<b>Device Type: </b>${device.device_type.replace("_", " ")}`;
|
||||
if (device.nwk != null) {
|
||||
label += `\n<b>NWK: </b>${device.nwk}`;
|
||||
label += `\n<b>NWK: </b>${formatAsPaddedHex(device.nwk)}`;
|
||||
}
|
||||
if (device.manufacturer != null && device.model != null) {
|
||||
label += `\n<b>Device: </b>${device.manufacturer} ${device.model}`;
|
||||
@@ -194,6 +236,56 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
return label;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
const filterText = this._filter!.toLowerCase();
|
||||
if (!this._network) {
|
||||
return;
|
||||
}
|
||||
if (this._filter) {
|
||||
const filteredNodeIds: (string | number)[] = [];
|
||||
this._nodes.forEach((node) => {
|
||||
if (node.label && node.label.toLowerCase().includes(filterText)) {
|
||||
filteredNodeIds.push(node.id!);
|
||||
}
|
||||
});
|
||||
this._zoomedDeviceId = "";
|
||||
this._zoomOut();
|
||||
this._network.selectNodes(filteredNodeIds, true);
|
||||
} else {
|
||||
this._network.unselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private _zoomToDevice(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
this._zoomedDeviceId = event.detail.value;
|
||||
if (!this._network) {
|
||||
return;
|
||||
}
|
||||
this._filter = "";
|
||||
if (!this._zoomedDeviceId) {
|
||||
this._zoomOut();
|
||||
} else {
|
||||
const device: ZHADevice | undefined = this._devicesByDeviceId.get(
|
||||
this._zoomedDeviceId
|
||||
);
|
||||
if (device) {
|
||||
this._network.fit({
|
||||
nodes: [device.ieee],
|
||||
animation: { duration: 500, easingFunction: "easeInQuad" },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _zoomOut() {
|
||||
this._network!.fit({
|
||||
nodes: [],
|
||||
animation: { duration: 500, easingFunction: "easeOutQuad" },
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
@@ -208,6 +300,30 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
line-height: var(--paper-font-display1_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
.table-header {
|
||||
border-bottom: 1px solid --divider-color;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--header-height);
|
||||
}
|
||||
.search-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0 16px;
|
||||
}
|
||||
search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
flex: 1;
|
||||
}
|
||||
search-input.header {
|
||||
left: -8px;
|
||||
}
|
||||
ha-device-picker {
|
||||
flex: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import "../../../components/ha-header-bar";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiContentCopy, mdiClose } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import type { PaperTooltipElement } from "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -10,10 +9,10 @@ import {
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
@@ -27,6 +26,7 @@ import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||
import { formatSystemLogTime } from "./util";
|
||||
import { showToast } from "../../../util/toast";
|
||||
|
||||
class DialogSystemLogDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -35,8 +35,6 @@ class DialogSystemLogDetail extends LitElement {
|
||||
|
||||
@internalProperty() private _manifest?: IntegrationManifest;
|
||||
|
||||
@query("paper-tooltip") private _toolTip?: PaperTooltipElement;
|
||||
|
||||
public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._manifest = undefined;
|
||||
@@ -83,15 +81,6 @@ class DialogSystemLogDetail extends LitElement {
|
||||
<mwc-icon-button id="copy" @click=${this._copyLog} slot="actionItems">
|
||||
<ha-svg-icon .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip
|
||||
slot="actionItems"
|
||||
manual-mode
|
||||
for="copy"
|
||||
position="left"
|
||||
animation-delay="0"
|
||||
offset="4"
|
||||
>${this.hass.localize("ui.common.copied")}</paper-tooltip
|
||||
>
|
||||
</ha-header-bar>
|
||||
<div class="contents">
|
||||
<p>
|
||||
@@ -162,23 +151,15 @@ class DialogSystemLogDetail extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _copyLog(): void {
|
||||
private async _copyLog(): Promise<void> {
|
||||
const copyElement = this.shadowRoot?.querySelector(
|
||||
".contents"
|
||||
) as HTMLElement;
|
||||
|
||||
const selection = window.getSelection()!;
|
||||
const range = document.createRange();
|
||||
|
||||
range.selectNodeContents(copyElement);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
document.execCommand("copy");
|
||||
window.getSelection()!.removeAllRanges();
|
||||
|
||||
this._toolTip!.show();
|
||||
setTimeout(() => this._toolTip?.hide(), 3000);
|
||||
await copyToClipboard(copyElement.innerText);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
|
@@ -35,6 +35,7 @@ import "../../../components/ha-icon-input";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import {
|
||||
Action,
|
||||
deleteScript,
|
||||
@@ -543,9 +544,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
private async _copyYaml() {
|
||||
private async _copyYaml(): Promise<void> {
|
||||
if (this._editor?.yaml) {
|
||||
navigator.clipboard.writeText(this._editor.yaml);
|
||||
await copyToClipboard(this._editor.yaml);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -84,7 +84,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
${tag.last_scanned_datetime
|
||||
? html`<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetimeObj=${tag.last_scanned_datetime}
|
||||
.datetime=${tag.last_scanned_datetime}
|
||||
></ha-relative-time>`
|
||||
: this.hass.localize("ui.panel.config.tags.never_scanned")}
|
||||
</div>`
|
||||
@@ -103,7 +103,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
${last_scanned_datetime
|
||||
? html`<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetimeObj=${last_scanned_datetime}
|
||||
.datetime=${last_scanned_datetime}
|
||||
></ha-relative-time>`
|
||||
: this.hass.localize("ui.panel.config.tags.never_scanned")}
|
||||
`,
|
||||
|
@@ -119,27 +119,21 @@ class HaPanelHistory extends LitElement {
|
||||
todayEnd.setDate(todayEnd.getDate() + 1);
|
||||
todayEnd.setMilliseconds(todayEnd.getMilliseconds() - 1);
|
||||
|
||||
const todayCopy = new Date(today);
|
||||
|
||||
const yesterday = new Date(todayCopy.setDate(today.getDate() - 1));
|
||||
const yesterdayEnd = new Date(yesterday);
|
||||
yesterdayEnd.setDate(yesterdayEnd.getDate() + 1);
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
const yesterdayEnd = new Date(today);
|
||||
yesterdayEnd.setMilliseconds(yesterdayEnd.getMilliseconds() - 1);
|
||||
|
||||
const thisWeekStart = new Date(
|
||||
todayCopy.setDate(today.getDate() - today.getDay())
|
||||
);
|
||||
const thisWeekEnd = new Date(
|
||||
todayCopy.setDate(thisWeekStart.getDate() + 7)
|
||||
);
|
||||
const thisWeekStart = new Date(today);
|
||||
thisWeekStart.setDate(today.getDate() - today.getDay());
|
||||
const thisWeekEnd = new Date(thisWeekStart);
|
||||
thisWeekEnd.setDate(thisWeekStart.getDate() + 7);
|
||||
thisWeekEnd.setMilliseconds(thisWeekEnd.getMilliseconds() - 1);
|
||||
|
||||
const lastWeekStart = new Date(
|
||||
todayCopy.setDate(today.getDate() - today.getDay() - 7)
|
||||
);
|
||||
const lastWeekEnd = new Date(
|
||||
todayCopy.setDate(lastWeekStart.getDate() + 7)
|
||||
);
|
||||
const lastWeekStart = new Date(today);
|
||||
lastWeekStart.setDate(today.getDate() - today.getDay() - 7);
|
||||
const lastWeekEnd = new Date(lastWeekStart);
|
||||
lastWeekEnd.setDate(lastWeekStart.getDate() + 7);
|
||||
lastWeekEnd.setMilliseconds(lastWeekEnd.getMilliseconds() - 1);
|
||||
|
||||
this._ranges = {
|
||||
|
@@ -24,7 +24,7 @@ class HaPanelIframe extends PolymerElement {
|
||||
|
||||
<iframe
|
||||
src="[[panel.config.url]]"
|
||||
sandbox="allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts"
|
||||
sandbox="allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals"
|
||||
allowfullscreen="true"
|
||||
webkitallowfullscreen="true"
|
||||
mozallowfullscreen="true"
|
||||
|
@@ -147,27 +147,21 @@ export class HaPanelLogbook extends LitElement {
|
||||
todayEnd.setDate(todayEnd.getDate() + 1);
|
||||
todayEnd.setMilliseconds(todayEnd.getMilliseconds() - 1);
|
||||
|
||||
const todayCopy = new Date(today);
|
||||
|
||||
const yesterday = new Date(todayCopy.setDate(today.getDate() - 1));
|
||||
const yesterdayEnd = new Date(yesterday);
|
||||
yesterdayEnd.setDate(yesterdayEnd.getDate() + 1);
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
const yesterdayEnd = new Date(today);
|
||||
yesterdayEnd.setMilliseconds(yesterdayEnd.getMilliseconds() - 1);
|
||||
|
||||
const thisWeekStart = new Date(
|
||||
todayCopy.setDate(today.getDate() - today.getDay())
|
||||
);
|
||||
const thisWeekEnd = new Date(
|
||||
todayCopy.setDate(thisWeekStart.getDate() + 7)
|
||||
);
|
||||
const thisWeekStart = new Date(today);
|
||||
thisWeekStart.setDate(today.getDate() - today.getDay());
|
||||
const thisWeekEnd = new Date(thisWeekStart);
|
||||
thisWeekEnd.setDate(thisWeekStart.getDate() + 7);
|
||||
thisWeekEnd.setMilliseconds(thisWeekEnd.getMilliseconds() - 1);
|
||||
|
||||
const lastWeekStart = new Date(
|
||||
todayCopy.setDate(today.getDate() - today.getDay() - 7)
|
||||
);
|
||||
const lastWeekEnd = new Date(
|
||||
todayCopy.setDate(lastWeekStart.getDate() + 7)
|
||||
);
|
||||
const lastWeekStart = new Date(today);
|
||||
lastWeekStart.setDate(today.getDate() - today.getDay() - 7);
|
||||
const lastWeekEnd = new Date(lastWeekStart);
|
||||
lastWeekEnd.setDate(lastWeekStart.getDate() + 7);
|
||||
lastWeekEnd.setMilliseconds(lastWeekEnd.getMilliseconds() - 1);
|
||||
|
||||
this._ranges = {
|
||||
|
@@ -84,9 +84,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
public setConfig(config: ButtonCardConfig): void {
|
||||
if (!config.entity) {
|
||||
throw new Error("Entity must be specified");
|
||||
}
|
||||
if (config.entity && !isValidEntityId(config.entity)) {
|
||||
throw new Error("Invalid entity");
|
||||
}
|
||||
|
@@ -2,7 +2,14 @@ import { customElement } from "lit-element";
|
||||
import { HuiButtonCard } from "./hui-button-card";
|
||||
|
||||
@customElement("hui-entity-button-card")
|
||||
class HuiEntityButtonCard extends HuiButtonCard {}
|
||||
class HuiEntityButtonCard extends HuiButtonCard {
|
||||
public setConfig(config): void {
|
||||
if (!config.entity) {
|
||||
throw new Error("Entity must be specified");
|
||||
}
|
||||
super.setConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@@ -122,12 +122,14 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
// Use `stateObj.state` as value to keep formatting (e.g trailing zeros)
|
||||
// for consistent value display across gauge, entity, entity-row, etc.
|
||||
return html`
|
||||
<ha-card @click=${this._handleClick} tabindex="0">
|
||||
<ha-gauge
|
||||
.min=${this._config.min!}
|
||||
.max=${this._config.max!}
|
||||
.value=${state}
|
||||
.value=${stateObj.state}
|
||||
.language=${this.hass!.language}
|
||||
.label=${this._config!.unit ||
|
||||
this.hass?.states[this._config!.entity].attributes
|
||||
|
@@ -11,9 +11,12 @@ import {
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { repeat } from "lit-html/directives/repeat";
|
||||
import { guard } from "lit-html/directives/guard";
|
||||
import { mdiDrag, mdiSort, mdiPlus, mdiNotificationClearAll } from "@mdi/js";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
@@ -23,12 +26,15 @@ import {
|
||||
fetchItems,
|
||||
ShoppingListItem,
|
||||
updateItem,
|
||||
reorderItems,
|
||||
} from "../../../data/shopping-list";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { SensorCardConfig, ShoppingListCardConfig } from "./types";
|
||||
|
||||
let Sortable;
|
||||
|
||||
@customElement("hui-shopping-list-card")
|
||||
class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
@@ -49,6 +55,14 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
|
||||
@internalProperty() private _checkedItems?: ShoppingListItem[];
|
||||
|
||||
@internalProperty() private _reordering = false;
|
||||
|
||||
@internalProperty() private _renderEmptySortable = false;
|
||||
|
||||
private _sortable?;
|
||||
|
||||
@query("#sortable") private _sortableEl?: HTMLElement;
|
||||
|
||||
public getCardSize(): number {
|
||||
return (this._config ? (this._config.title ? 2 : 0) : 0) + 3;
|
||||
}
|
||||
@@ -101,15 +115,15 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
})}
|
||||
>
|
||||
<div class="addRow">
|
||||
<ha-icon
|
||||
<ha-svg-icon
|
||||
class="addButton"
|
||||
icon="hass:plus"
|
||||
.path=${mdiPlus}
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.lovelace.cards.shopping-list.add_item"
|
||||
)}
|
||||
@click=${this._addItem}
|
||||
>
|
||||
</ha-icon>
|
||||
</ha-svg-icon>
|
||||
<paper-input
|
||||
no-label-float
|
||||
class="addBox"
|
||||
@@ -118,28 +132,27 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
)}
|
||||
@keydown=${this._addKeyPress}
|
||||
></paper-input>
|
||||
<ha-svg-icon
|
||||
class="reorderButton"
|
||||
.path=${mdiSort}
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.lovelace.cards.shopping-list.reorder_items"
|
||||
)}
|
||||
@click=${this._toggleReorder}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</div>
|
||||
${repeat(
|
||||
this._uncheckedItems!,
|
||||
(item) => item.id,
|
||||
(item) =>
|
||||
html`
|
||||
<div class="editRow">
|
||||
<paper-checkbox
|
||||
tabindex="0"
|
||||
?checked=${item.complete}
|
||||
.itemId=${item.id}
|
||||
@click=${this._completeItem}
|
||||
></paper-checkbox>
|
||||
<paper-input
|
||||
no-label-float
|
||||
.value=${item.name}
|
||||
.itemId=${item.id}
|
||||
@change=${this._saveEdit}
|
||||
></paper-input>
|
||||
${this._reordering
|
||||
? html`
|
||||
<div id="sortable">
|
||||
${guard([this._uncheckedItems, this._renderEmptySortable], () =>
|
||||
this._renderEmptySortable
|
||||
? ""
|
||||
: this._renderItems(this._uncheckedItems!)
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
: this._renderItems(this._uncheckedItems!)}
|
||||
${this._checkedItems!.length > 0
|
||||
? html`
|
||||
<div class="divider"></div>
|
||||
@@ -149,16 +162,16 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
"ui.panel.lovelace.cards.shopping-list.checked_items"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon
|
||||
<ha-svg-icon
|
||||
class="clearall"
|
||||
tabindex="0"
|
||||
icon="hass:notification-clear-all"
|
||||
.path=${mdiNotificationClearAll}
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.lovelace.cards.shopping-list.clear_items"
|
||||
)}
|
||||
@click=${this._clearItems}
|
||||
>
|
||||
</ha-icon>
|
||||
</ha-svg-icon>
|
||||
</div>
|
||||
${repeat(
|
||||
this._checkedItems!,
|
||||
@@ -187,6 +200,44 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItems(items: ShoppingListItem[]) {
|
||||
return html`
|
||||
${repeat(
|
||||
items,
|
||||
(item) => item.id,
|
||||
(item) =>
|
||||
html`
|
||||
<div class="editRow" item-id=${item.id}>
|
||||
<paper-checkbox
|
||||
tabindex="0"
|
||||
?checked=${item.complete}
|
||||
.itemId=${item.id}
|
||||
@click=${this._completeItem}
|
||||
></paper-checkbox>
|
||||
<paper-input
|
||||
no-label-float
|
||||
.value=${item.name}
|
||||
.itemId=${item.id}
|
||||
@change=${this._saveEdit}
|
||||
></paper-input>
|
||||
${this._reordering
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.lovelace.cards.shopping-list.drag_and_drop"
|
||||
)}
|
||||
class="reorderButton"
|
||||
.path=${mdiDrag}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
@@ -248,6 +299,54 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
}
|
||||
}
|
||||
|
||||
private async _toggleReorder() {
|
||||
if (!Sortable) {
|
||||
const sortableImport = await import(
|
||||
"sortablejs/modular/sortable.core.esm"
|
||||
);
|
||||
Sortable = sortableImport.Sortable;
|
||||
}
|
||||
this._reordering = !this._reordering;
|
||||
await this.updateComplete;
|
||||
if (this._reordering) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _createSortable() {
|
||||
const sortableEl = this._sortableEl;
|
||||
this._sortable = new Sortable(sortableEl, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
dataIdAttr: "item-id",
|
||||
handle: "ha-svg-icon",
|
||||
onEnd: async (evt) => {
|
||||
// Since this is `onEnd` event, it's possible that
|
||||
// an item wa dragged away and was put back to its original position.
|
||||
if (evt.oldIndex !== evt.newIndex) {
|
||||
reorderItems(this.hass!, this._sortable.toArray()).catch(() =>
|
||||
this._fetchData()
|
||||
);
|
||||
// Move the shopping list item in memory.
|
||||
this._uncheckedItems!.splice(
|
||||
evt.newIndex,
|
||||
0,
|
||||
this._uncheckedItems!.splice(evt.oldIndex, 1)[0]
|
||||
);
|
||||
}
|
||||
this._renderEmptySortable = true;
|
||||
await this.updateComplete;
|
||||
while (sortableEl?.lastElementChild) {
|
||||
sortableEl.removeChild(sortableEl.lastElementChild);
|
||||
}
|
||||
this._renderEmptySortable = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
@@ -278,6 +377,11 @@ class HuiShoppingListCard extends SubscribeMixin(LitElement)
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reorderButton {
|
||||
padding-left: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
paper-checkbox {
|
||||
padding-left: 4px;
|
||||
padding-right: 20px;
|
||||
|
@@ -4,6 +4,7 @@ import { forwardHaptic } from "../../../data/haptics";
|
||||
import { ActionConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { toggleEntity } from "./entity/toggle-entity";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -11,7 +12,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const handleAction = (
|
||||
export const handleAction = async (
|
||||
node: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
config: {
|
||||
@@ -22,7 +23,7 @@ export const handleAction = (
|
||||
double_tap_action?: ActionConfig;
|
||||
},
|
||||
action: string
|
||||
): void => {
|
||||
): Promise<void> => {
|
||||
let actionConfig: ActionConfig | undefined;
|
||||
|
||||
if (action === "double_tap" && config.double_tap_action) {
|
||||
@@ -39,64 +40,78 @@ export const handleAction = (
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
actionConfig.confirmation &&
|
||||
(!actionConfig.confirmation.exemptions ||
|
||||
!actionConfig.confirmation.exemptions.some(
|
||||
(e) => e.user === hass!.user!.id
|
||||
))
|
||||
) {
|
||||
forwardHaptic("warning");
|
||||
const actionConfigs =
|
||||
actionConfig.action === "multiple"
|
||||
? Array.isArray(actionConfig.actions)
|
||||
? actionConfig.actions
|
||||
: [actionConfig.actions]
|
||||
: [actionConfig];
|
||||
|
||||
for await (actionConfig of actionConfigs) {
|
||||
if (
|
||||
!confirm(
|
||||
actionConfig.confirmation.text ||
|
||||
`Are you sure you want to ${actionConfig.action}?`
|
||||
)
|
||||
actionConfig.confirmation &&
|
||||
(!actionConfig.confirmation.exemptions ||
|
||||
!actionConfig.confirmation.exemptions.some(
|
||||
(e) => e.user === hass!.user!.id
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
forwardHaptic("warning");
|
||||
|
||||
switch (actionConfig.action) {
|
||||
case "more-info": {
|
||||
if (config.entity || config.camera_image) {
|
||||
fireEvent(node, "hass-more-info", {
|
||||
entityId: config.entity ? config.entity : config.camera_image!,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "navigate":
|
||||
if (actionConfig.navigation_path) {
|
||||
navigate(node, actionConfig.navigation_path);
|
||||
}
|
||||
break;
|
||||
case "url": {
|
||||
if (actionConfig.url_path) {
|
||||
window.open(actionConfig.url_path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "toggle": {
|
||||
if (config.entity) {
|
||||
toggleEntity(hass, config.entity!);
|
||||
forwardHaptic("light");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "call-service": {
|
||||
if (!actionConfig.service) {
|
||||
forwardHaptic("failure");
|
||||
if (
|
||||
!(await showConfirmationDialog(node, {
|
||||
text:
|
||||
actionConfig.confirmation.text ||
|
||||
hass.localize(
|
||||
"ui.panel.lovelace.cards.action_confirmation",
|
||||
"action",
|
||||
actionConfig.action
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const [domain, service] = actionConfig.service.split(".", 2);
|
||||
hass.callService(domain, service, actionConfig.service_data);
|
||||
forwardHaptic("light");
|
||||
break;
|
||||
}
|
||||
case "fire-dom-event": {
|
||||
fireEvent(node, "ll-custom", actionConfig);
|
||||
|
||||
switch (actionConfig.action) {
|
||||
case "more-info": {
|
||||
if (config.entity || config.camera_image) {
|
||||
fireEvent(node, "hass-more-info", {
|
||||
entityId: config.entity ? config.entity : config.camera_image!,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "navigate":
|
||||
if (actionConfig.navigation_path) {
|
||||
navigate(node, actionConfig.navigation_path);
|
||||
}
|
||||
break;
|
||||
case "url": {
|
||||
if (actionConfig.url_path) {
|
||||
window.open(actionConfig.url_path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "toggle": {
|
||||
if (config.entity) {
|
||||
toggleEntity(hass, config.entity!);
|
||||
forwardHaptic("light");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "call-service": {
|
||||
if (!actionConfig.service) {
|
||||
forwardHaptic("failure");
|
||||
return;
|
||||
}
|
||||
const [domain, service] = actionConfig.service.split(".", 2);
|
||||
hass.callService(domain, service, actionConfig.service_data);
|
||||
forwardHaptic("light");
|
||||
break;
|
||||
}
|
||||
case "fire-dom-event": {
|
||||
fireEvent(node, "ll-custom", actionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ export const handleStructError = (err: Error): string[] => {
|
||||
for (const failure of err.failures()) {
|
||||
if (failure.type === "never") {
|
||||
errors.push(
|
||||
`Key "${failure.path[0]}" is not supported by the UI editor.`
|
||||
`Key "${failure.path.join(".")}" is not supported by the UI editor.`
|
||||
);
|
||||
} else {
|
||||
errors.push(
|
||||
|
@@ -7,7 +7,7 @@ const isEntityId = (value: unknown, context: StructContext): StructResult => {
|
||||
if (!value.includes(".")) {
|
||||
return [
|
||||
context.fail({
|
||||
type: "entity id should be in the format 'domain.entity'",
|
||||
type: "Entity ID should be in the format 'domain.entity'",
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -15,3 +15,16 @@ const isEntityId = (value: unknown, context: StructContext): StructResult => {
|
||||
};
|
||||
|
||||
export const EntityId = struct("entity-id", isEntityId);
|
||||
|
||||
const isEntityIdOrAll = (
|
||||
value: unknown,
|
||||
context: StructContext
|
||||
): StructResult => {
|
||||
if (typeof value === "string" && value === "all") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isEntityId(value, context);
|
||||
};
|
||||
|
||||
export const EntityIdOrAll = struct("entity-id-all", isEntityIdOrAll);
|
||||
|
@@ -130,6 +130,15 @@ export class HuiActionEditor extends LitElement {
|
||||
</b>
|
||||
`
|
||||
: ""}
|
||||
${this.config?.action === "multiple"
|
||||
? html`
|
||||
<b>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.action-editor.editor_multiple_actions"
|
||||
)}
|
||||
</b>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-icon-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,21 +10,20 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
queryAssignedNodes,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
||||
import { swapCard, moveCard, addCard, deleteCard } from "../editor/config-util";
|
||||
import { confDeleteCard } from "../editor/delete-card";
|
||||
import { Lovelace, LovelaceCard } from "../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { mdiDotsVertical, mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button-menu";
|
||||
import { saveConfig } from "../../../data/lovelace";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
||||
import { addCard, deleteCard, moveCard, swapCard } from "../editor/config-util";
|
||||
import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog";
|
||||
import { Lovelace, LovelaceCard } from "../types";
|
||||
|
||||
@customElement("hui-card-options")
|
||||
export class HuiCardOptions extends LitElement {
|
||||
@@ -168,11 +168,7 @@ export class HuiCardOptions extends LitElement {
|
||||
}
|
||||
|
||||
private _editCard(): void {
|
||||
showEditCardDialog(this, {
|
||||
lovelaceConfig: this.lovelace!.config,
|
||||
saveConfig: this.lovelace!.saveConfig,
|
||||
path: this.path!,
|
||||
});
|
||||
fireEvent(this, "ll-edit-card", { path: this.path! });
|
||||
}
|
||||
|
||||
private _cardUp(): void {
|
||||
@@ -229,7 +225,7 @@ export class HuiCardOptions extends LitElement {
|
||||
}
|
||||
|
||||
private _deleteCard(): void {
|
||||
confDeleteCard(this, this.hass!, this.lovelace!, this.path!);
|
||||
fireEvent(this, "ll-delete-card", { path: this.path! });
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -164,7 +164,8 @@ class HuiGenericEntityRow extends LitElement {
|
||||
}
|
||||
.info {
|
||||
margin-left: 16px;
|
||||
flex: 1 0 60px;
|
||||
margin-right: 8px;
|
||||
flex: 1 0 30%;
|
||||
}
|
||||
.info,
|
||||
.info > * {
|
||||
@@ -181,7 +182,6 @@ class HuiGenericEntityRow extends LitElement {
|
||||
}
|
||||
.secondary,
|
||||
ha-relative-time {
|
||||
display: block;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
state-badge {
|
||||
|
@@ -57,7 +57,9 @@ class HuiTimestampDisplay extends LitElement {
|
||||
}
|
||||
|
||||
if (isNaN(this.ts.getTime())) {
|
||||
return html` Invalid date `;
|
||||
return html`${this.hass.localize(
|
||||
"ui.panel.lovelace.components.timestamp-display.invalid"
|
||||
)}`;
|
||||
}
|
||||
|
||||
const format = this._format;
|
||||
@@ -68,7 +70,9 @@ class HuiTimestampDisplay extends LitElement {
|
||||
if (format in FORMATS) {
|
||||
return html` ${FORMATS[format](this.ts, this.hass.language)} `;
|
||||
}
|
||||
return html` Invalid format `;
|
||||
return html`${this.hass.localize(
|
||||
"ui.panel.lovelace.components.timestamp-display.invalid_format"
|
||||
)}`;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
|
@@ -71,7 +71,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
switch: "toggle",
|
||||
vacuum: "toggle",
|
||||
// Temporary. Once climate is rewritten,
|
||||
// water heater should get it's own row.
|
||||
// water heater should get its own row.
|
||||
water_heater: "climate",
|
||||
input_datetime: "input-datetime",
|
||||
weather: "weather",
|
||||
|
@@ -8,7 +8,7 @@ export interface CreateCardDialogParams {
|
||||
entities?: string[]; // We can pass entity id's that will be added to the config when a card is picked
|
||||
}
|
||||
|
||||
const importCreateCardDialog = () => import("./hui-dialog-create-card");
|
||||
export const importCreateCardDialog = () => import("./hui-dialog-create-card");
|
||||
|
||||
export const showCreateCardDialog = (
|
||||
element: HTMLElement,
|
||||
|
@@ -6,7 +6,7 @@ export interface DeleteCardDialogParams {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
}
|
||||
|
||||
const importDeleteCardDialog = () => import("./hui-dialog-delete-card");
|
||||
export const importDeleteCardDialog = () => import("./hui-dialog-delete-card");
|
||||
|
||||
export const showDeleteCardDialog = (
|
||||
element: HTMLElement,
|
||||
|
@@ -8,7 +8,7 @@ export interface EditCardDialogParams {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
}
|
||||
|
||||
const importEditCardDialog = () => import("./hui-dialog-edit-card");
|
||||
export const importEditCardDialog = () => import("./hui-dialog-edit-card");
|
||||
|
||||
export const showEditCardDialog = (
|
||||
element: HTMLElement,
|
||||
|
@@ -46,6 +46,7 @@ const actions = [
|
||||
"url",
|
||||
"call-service",
|
||||
"none",
|
||||
"multiple",
|
||||
];
|
||||
|
||||
@customElement("hui-button-card-editor")
|
||||
@@ -115,7 +116,7 @@ export class HuiButtonCardEditor extends LitElement
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.entity"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.required"
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.hass=${this.hass}
|
||||
.value=${this._entity}
|
||||
|
@@ -82,6 +82,7 @@ export class HuiLightCardEditor extends LitElement
|
||||
"url",
|
||||
"call-service",
|
||||
"none",
|
||||
"multiple",
|
||||
];
|
||||
|
||||
return html`
|
||||
|
@@ -60,7 +60,7 @@ export class HuiPictureCardEditor extends LitElement
|
||||
return html``;
|
||||
}
|
||||
|
||||
const actions = ["navigate", "url", "call-service", "none"];
|
||||
const actions = ["navigate", "url", "call-service", "none", "multiple"];
|
||||
|
||||
return html`
|
||||
<div class="card-config">
|
||||
|
@@ -104,7 +104,14 @@ export class HuiPictureEntityCardEditor extends LitElement
|
||||
return html``;
|
||||
}
|
||||
|
||||
const actions = ["more-info", "toggle", "navigate", "call-service", "none"];
|
||||
const actions = [
|
||||
"more-info",
|
||||
"toggle",
|
||||
"navigate",
|
||||
"call-service",
|
||||
"none",
|
||||
"multiple",
|
||||
];
|
||||
const views = ["auto", "live"];
|
||||
const dir = computeRTLDirection(this.hass!);
|
||||
|
||||
|
@@ -108,7 +108,14 @@ export class HuiPictureGlanceCardEditor extends LitElement
|
||||
return html``;
|
||||
}
|
||||
|
||||
const actions = ["more-info", "toggle", "navigate", "call-service", "none"];
|
||||
const actions = [
|
||||
"more-info",
|
||||
"toggle",
|
||||
"navigate",
|
||||
"call-service",
|
||||
"none",
|
||||
"multiple",
|
||||
];
|
||||
const views = ["auto", "live"];
|
||||
|
||||
return html`
|
||||
|
@@ -98,6 +98,7 @@ export const actionConfigStruct = object({
|
||||
url_path: optional(string()),
|
||||
service: optional(string()),
|
||||
service_data: optional(object()),
|
||||
actions: optional(array()),
|
||||
});
|
||||
|
||||
const buttonEntitiesRowConfigStruct = object({
|
||||
|
@@ -48,9 +48,9 @@ export class HuiServiceButtonElement extends LitElement
|
||||
return html`
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
.domain="${this._domain}"
|
||||
.service="${this._service}"
|
||||
.serviceData="${this._config.service_data}"
|
||||
.domain=${this._domain}
|
||||
.service=${this._service}
|
||||
.serviceData=${this._config.service_data}
|
||||
>${this._config.title}</ha-call-service-button
|
||||
>
|
||||
`;
|
||||
|
@@ -25,7 +25,7 @@ import { handleAction } from "../common/handle-action";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
|
||||
interface SensorEntityConfig extends EntitiesCardEntityConfig {
|
||||
format?: "relative" | "date" | "time" | "datetime";
|
||||
format?: "relative" | "total" | "date" | "time" | "datetime";
|
||||
}
|
||||
|
||||
@customElement("hui-sensor-entity-row")
|
||||
|
@@ -173,8 +173,8 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_unsaved_changes"
|
||||
),
|
||||
dismissText: this.hass!.localize("ui.common.leave"),
|
||||
confirmText: this.hass!.localize("ui.common.stay"),
|
||||
dismissText: this.hass!.localize("ui.common.stay"),
|
||||
confirmText: this.hass!.localize("ui.common.leave"),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
|
@@ -1,3 +1,10 @@
|
||||
// hui-view dependencies for when in edit mode.
|
||||
import "../../../components/ha-fab";
|
||||
import "../components/hui-card-options";
|
||||
import { importCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
|
||||
import { importDeleteCardDialog } from "../editor/card-editor/show-delete-card-dialog";
|
||||
import { importEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
||||
|
||||
importCreateCardDialog();
|
||||
importDeleteCardDialog();
|
||||
importEditCardDialog();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user