Compare commits

..

1 Commits

Author SHA1 Message Date
Bram Kragten
4c2ca9224d Complete filtering of selectors 2020-12-01 11:13:08 +01:00
187 changed files with 3563 additions and 10428 deletions

View File

@@ -74,12 +74,12 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
```
## Problem-relevant frontend configuration
## Problem-relevant configuration
<!--
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.
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.
-->
```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.
-->

View File

@@ -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`
- Supervisor: [Instructions](https://developers.home-assistant.io/docs/supervisor/developing)
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
## Frontend development

View File

@@ -36,7 +36,6 @@ 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",
@@ -132,6 +131,22 @@ 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"

View File

@@ -9,29 +9,30 @@ class DemoMoreInfo extends PolymerElement {
static get template() {
return html`
<style>
.root {
:host {
display: flex;
align-items: start;
}
#card {
max-width: 400px;
width: 100vw;
}
ha-card {
width: 352px;
width: 333px;
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) {
.root {
:host {
flex-direction: column;
}
pre {
@@ -39,25 +40,21 @@ class DemoMoreInfo extends PolymerElement {
}
}
</style>
<div class="root">
<div id="card">
<ha-card>
<state-card-content
state-obj="[[_stateObj]]"
hass="[[hass]]"
in-dialog
></state-card-content>
<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>
</div>
<template is="dom-if" if="[[showConfig]]">
<pre>[[_jsonEntity(_stateObj)]]</pre>
</template>
</div>
<more-info-content
hass="[[hass]]"
state-obj="[[_stateObj]]"
></more-info-content>
</ha-card>
<template is="dom-if" if="[[showConfig]]">
<pre>[[_jsonEntity(_stateObj)]]</pre>
</template>
`;
}

View File

@@ -3,18 +3,12 @@ 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;
@@ -29,31 +23,20 @@ class DemoMoreInfos extends PolymerElement {
.filters {
margin-left: 60px;
}
ha-formfield {
margin-right: 16px;
}
</style>
<app-toolbar>
<div class="filters">
<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>
<ha-switch checked="{{_showConfig}}">Show entity</ha-switch>
</div>
</app-toolbar>
<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 class="cards">
<template is="dom-repeat" items="[[entities]]">
<demo-more-info
entity-id="[[item]]"
show-config="[[_showConfig]]"
hass="[[hass]]"
></demo-more-info>
</template>
</div>
`;
}
@@ -68,16 +51,6 @@ 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);

View File

@@ -7,8 +7,8 @@ export const createMediaPlayerEntities = () => [
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
supported_features: 195135,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
@@ -24,8 +24,8 @@ export const createMediaPlayerEntities = () => [
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
supported_features: 195135,
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
media_position: 0,

View File

@@ -1,72 +0,0 @@
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",
}),
];

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -76,19 +71,28 @@ const CONFIGS = [
},
];
@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>`;
class DemoAlarmPanelEntity extends PolymerElement {
static get template() {
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");
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");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -58,19 +53,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoConditional extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -222,19 +217,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoEntities extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -74,19 +69,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoButtonEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -94,21 +89,26 @@ const CONFIGS = [
},
];
@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>`;
class DemoFilter extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-entity-filter-card", DemoEntityFilter);
customElements.define("demo-hui-entity-filter-card", DemoFilter);

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -112,19 +107,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoGaugeEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -223,21 +218,26 @@ const CONFIGS = [
},
];
@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>`;
class DemoPicEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-glance-card", DemoGlanceEntity);
customElements.define("demo-hui-glance-card", DemoPicEntity);

View File

@@ -1,4 +1,6 @@
import { html, LitElement, customElement, TemplateResult } from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
const CONFIGS = [
@@ -35,10 +37,18 @@ const CONFIGS = [
},
];
@customElement("demo-hui-iframe-card")
class DemoIframe extends LitElement {
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
class DemoIframe extends PolymerElement {
static get template() {
return html` <demo-cards configs="[[_configs]]"></demo-cards> `;
}
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -68,19 +63,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoLightEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -166,19 +161,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoMap extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { mockTemplate } from "../../../demo/src/stubs/template";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -259,19 +254,23 @@ const CONFIGS = [
},
];
@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>`;
class DemoMarkdown extends PolymerElement {
static get template() {
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");
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
mockTemplate(hass);
}
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players";
@@ -151,33 +146,28 @@ const CONFIGS = [
entity: media_player.receiver_off
`,
},
{
heading: "Grid Full Size",
config: `
- type: grid
columns: 1
cards:
- type: media-control
entity: media_player.music_paused
`,
},
];
@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>`;
class DemoHuiMediControlCard extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(createMediaPlayerEntities());
}
}
customElements.define("demo-hui-media-control-card", DemoHuiMediaControlCard);
customElements.define("demo-hui-media-control-card", DemoHuiMediControlCard);

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players";
@@ -60,21 +55,26 @@ const CONFIGS = [
},
];
@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>`;
class DemoHuiMediaPlayerRows extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(createMediaPlayerEntities());
}
}
customElements.define("demo-hui-media-player-row", DemoHuiMediaPlayerRow);
customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows);

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -130,21 +125,26 @@ const CONFIGS = [
},
];
@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>`;
class DemoPicElements extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-picture-elements-card", DemoPictureElements);
customElements.define("demo-hui-picture-elements-card", DemoPicElements);

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -85,21 +80,26 @@ const CONFIGS = [
},
];
@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>`;
class DemoPicEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-picture-entity-card", DemoPictureEntity);
customElements.define("demo-hui-picture-entity-card", DemoPicEntity);

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -126,21 +121,26 @@ const CONFIGS = [
},
];
@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>`;
class DemoPicGlance extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-picture-glance-card", DemoPictureGlance);
customElements.define("demo-hui-picture-glance-card", DemoPicGlance);

View File

@@ -1,55 +0,0 @@
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);

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -25,19 +20,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoShoppingListEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.mockAPI("shopping_list", () => [
{ name: "list", id: 1, complete: false },

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { mockHistory } from "../../../demo/src/stubs/history";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -137,19 +132,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoStack extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockHistory(hass);
}

View File

@@ -1,11 +1,6 @@
import {
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards";
@@ -79,19 +74,24 @@ const CONFIGS = [
},
];
@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>`;
class DemoThermostatEntity extends PolymerElement {
static get template() {
return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
static get properties() {
return {
_configs: {
type: Object,
value: CONFIGS,
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,27 +1,10 @@
import {
html,
LitElement,
customElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card";
import {
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_COLOR,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
} from "../../../src/data/light";
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
import { getEntity } from "../../../src/fake_data/entity";
import {
provideHass,
MockHomeAssistant,
} from "../../../src/fake_data/provide_hass";
import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos";
import "../../../src/dialogs/more-info/more-info-content";
@@ -31,52 +14,38 @@ const ENTITIES = [
}),
getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light",
brightness: 200,
brightness: 80,
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"],
}),
];
@customElement("demo-more-info-light")
class DemoMoreInfoLight extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
class DemoMoreInfoLight extends PolymerElement {
static get template() {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
hass="[[hass]]"
entities="[[_entities]]"
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
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");
hass.addEntities(ENTITIES);
}
}

View File

@@ -1,10 +1,9 @@
import "@material/mwc-button";
import { customElement, html, LitElement, TemplateResult } from "lit-element";
import { 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`
@@ -21,7 +20,7 @@ export class DemoUtilLongPress extends LitElement {
<textarea></textarea>
<div>Try pressing and scrolling too!</div>
<div>(try pressing and scrolling too!)</div>
</ha-card>
`
)}
@@ -63,3 +62,5 @@ export class DemoUtilLongPress extends LitElement {
`;
}
}
customElements.define("demo-util-long-press", DemoUtilLongPress);

View File

@@ -14,6 +14,8 @@ 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`

View File

@@ -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",

View File

@@ -74,7 +74,9 @@ export class HassioUpdate extends LitElement {
"Supervisor",
this.supervisor.supervisor,
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
`https://github.com//home-assistant/hassio/releases/tag/${
this.supervisor.supervisor.version_latest
}`
)}
${this.supervisor.host.features.includes("hassos")
? this._renderUpdateCard(

View File

@@ -178,7 +178,7 @@ class HassioSupervisorInfo extends LitElement {
</div>`}
${!this.supervisor.supervisor.healthy
? html`<div class="error">
Your installation is running in an unhealthy state.
Your installtion is running in an unhealthy state.
<button
class="link"
title="Learn more about why your system is marked as unhealthy"

View File

@@ -29,22 +29,22 @@
"@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.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",
"@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",
"@mdi/js": "5.6.55",
"@mdi/svg": "5.6.55",
"@polymer/app-layout": "^3.0.2",

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20201229.0",
version="20201126.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@@ -1,6 +0,0 @@
export const ensureArray = (value?: any) => {
if (!value || Array.isArray(value)) {
return value;
}
return [value];
};

View File

@@ -67,10 +67,6 @@ export const computeStateDisplay = (
}
}
if (domain === "counter") {
return formatNumber(compareState, language);
}
return (
// Return device class translation
(stateObj.attributes.device_class &&

View File

@@ -1,13 +1,4 @@
export const copyToClipboard = async (str) => {
if (navigator.clipboard) {
try {
await navigator.clipboard.writeText(str);
return;
} catch {
// just continue with the fallback coding below
}
}
export const copyToClipboard = (str) => {
const el = document.createElement("textarea");
el.value = str;
document.body.appendChild(el);

View File

@@ -98,12 +98,6 @@ 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;
@@ -132,8 +126,6 @@ export class HaDataTable extends LitElement {
@query("slot[name='header']") private _header!: HTMLSlotElement;
private _items: DataTableRowData[] = [];
private _checkableRowsCount?: number;
private _checkedRows: string[] = [];
@@ -326,13 +318,10 @@ export class HaDataTable extends LitElement {
@scroll=${this._saveScrollPos}
>
${scroll({
items: this._items,
items: !this.hasFab
? this._filteredData
: [...this._filteredData, ...[{ empty: true }]],
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> `;
}
@@ -458,20 +447,6 @@ 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;
}

View File

@@ -58,14 +58,6 @@ 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;
}

View File

@@ -1,5 +1,4 @@
import "../ha-svg-icon";
import "@material/mwc-icon-button/mwc-icon-button";
import "../ha-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -13,8 +12,6 @@ import {
html,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
@@ -38,7 +35,6 @@ 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;
@@ -115,10 +111,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean })
private _opened?: boolean;
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
private _init = false;
private _getDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
@@ -130,13 +122,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
deviceFilter: this["deviceFilter"]
): Device[] => {
if (!devices.length) {
return [
{
id: "",
area: "",
name: this.hass.localize("ui.components.device-picker.no_devices"),
},
];
return [];
}
const deviceEntityLookup: DeviceEntityLookup = {};
@@ -158,9 +144,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
areaLookup[area.area_id] = area;
}
let inputDevices = devices.filter(
(device) => device.id === this.value || !device.disabled_by
);
let inputDevices = [...devices];
if (includeDomains) {
inputDevices = inputDevices.filter((device) => {
@@ -227,15 +211,6 @@ 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;
}
@@ -243,18 +218,6 @@ 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) => {
@@ -269,33 +232,25 @@ 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}
@@ -313,30 +268,34 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
>
${this.value
? html`
<mwc-icon-button
.label=${this.hass.localize(
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
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>
`
: ""}
<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>
`;
@@ -373,7 +332,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResult {
return css`
paper-input > mwc-icon-button {
paper-input > ha-icon-button {
--mdc-icon-button-size: 24px;
padding: 2px;
color: var(--secondary-text-color);

View File

@@ -101,18 +101,6 @@ export class HaEntityPicker extends LitElement {
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
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();
});
}
private _initedStates = false;
private _states: HassEntity[] = [];
@@ -165,24 +153,6 @@ 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;
}
);
@@ -233,6 +203,7 @@ 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"

View File

@@ -1,5 +1,4 @@
import "./ha-svg-icon";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -15,8 +14,6 @@ import {
property,
internalProperty,
TemplateResult,
PropertyValues,
query,
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import {
@@ -43,7 +40,6 @@ 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,
@@ -125,10 +121,6 @@ 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) => {
@@ -143,18 +135,6 @@ export class HaAreaPicker 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();
});
}
private _getAreas = memoizeOne(
(
areas: AreaRegistryEntry[],
@@ -167,15 +147,6 @@ 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;
@@ -190,12 +161,12 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
}
deviceEntityLookup[entity.device_id].push(entity);
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
inputDevices = [...devices];
inputEntities = entities.filter((entity) => !entity.device_id);
} else if (deviceFilter) {
inputDevices = devices;
inputDevices = [...devices];
} else if (entityFilter) {
inputEntities = entities.filter((entity) => entity.area_id);
inputEntities = entities.filter((entity) => !entity.device_id);
}
if (includeDomains) {
@@ -260,9 +231,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
}
if (entityFilter) {
inputEntities = inputEntities!.filter((entity) =>
entityFilter!(entity)
);
entities = entities.filter((entity) => entityFilter!(entity));
}
let outputAreas = areas;
@@ -287,15 +256,6 @@ 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
: [
@@ -308,35 +268,27 @@ 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}
@@ -357,28 +309,34 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
>
${this.value
? html`
<mwc-icon-button
.label=${this.hass.localize(
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.area-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
${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>
`
: ""}
<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>
`;
@@ -454,7 +412,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResult {
return css`
paper-input > mwc-icon-button {
paper-input > ha-icon-button {
--mdc-icon-button-size: 24px;
padding: 2px;
color: var(--secondary-text-color);

View File

@@ -63,7 +63,7 @@ class HaAttributes extends LitElement {
justify-content: space-between;
}
.data-entry .value {
max-width: 60%;
max-width: 50%;
overflow-wrap: break-word;
text-align: right;
}
@@ -77,9 +77,6 @@ class HaAttributes extends LitElement {
pre {
font-family: inherit;
font-size: inherit;
margin: 0px;
overflow-wrap: break-word;
white-space: pre-line;
}
`;
}

View File

@@ -52,7 +52,6 @@ class HaBluePrintPicker extends LitElement {
.label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.label")}
.disabled=${this.disabled}
horizontal-align="left"
>
<paper-listbox
slot="dropdown-content"
@@ -111,9 +110,6 @@ class HaBluePrintPicker extends LitElement {
paper-listbox {
min-width: 200px;
}
paper-item {
cursor: pointer;
}
`;
}
}

View File

@@ -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=".*?(hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(.+)/g;
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(?<isHevc>hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(?<streamUrl>.+)/g;
const match = playlistRegexp.exec(masterPlaylist);
const matchTwice = playlistRegexp.exec(masterPlaylist);
@@ -136,13 +136,17 @@ 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[2], this.url).href;
playlist_url = new URL(match.groups!.streamUrl, this.url).href;
} else {
playlist_url = this.url;
}
// If codec is HEVC and ExoPlayer is supported, use ExoPlayer.
if (this._useExoPlayer && match !== null && match[1] !== undefined) {
if (
this._useExoPlayer &&
match !== null &&
match.groups!.isHevc !== undefined
) {
this._renderHLSExoPlayer(playlist_url);
} else if (hls.isSupported()) {
this._renderHLSPolyfill(videoEl, hls, playlist_url);

View File

@@ -60,9 +60,8 @@ export class HaIconInput extends LitElement {
static get styles() {
return css`
ha-icon {
position: absolute;
bottom: 2px;
right: 0;
position: relative;
bottom: 4px;
}
`;
}

View File

@@ -10,6 +10,7 @@ class HaLabeledSlider extends PolymerElement {
<style>
:host {
display: block;
border-radius: 4px;
}
.title {
@@ -29,7 +30,6 @@ class HaLabeledSlider extends PolymerElement {
ha-slider {
flex-grow: 1;
background-image: var(--ha-slider-background);
border-radius: 4px;
}
</style>

View File

@@ -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 | Date;
@property({ attribute: false }) public datetime?: string;
private _interval?: number;

View File

@@ -1,45 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { ActionSelector } from "../../data/selector";
import { Action } from "../../data/script";
import "../../panels/config/automation/action/ha-automation-action";
@customElement("ha-selector-action")
export class HaActionSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: ActionSelector;
@property() public value?: Action;
@property() public label?: string;
protected render() {
return html`<ha-automation-action
.actions=${this.value || []}
.hass=${this.hass}
></ha-automation-action>`;
}
static get styles(): CSSResult {
return css`
ha-automation-action {
display: block;
margin-bottom: 16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-action": HaActionSelector;
}
}

View File

@@ -77,8 +77,7 @@ export class HaAreaSelector extends LitElement {
}
if (this.selector.area.device?.integration) {
if (
this._configEntries &&
!this._configEntries.some((entry) =>
!this._configEntries?.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {

View File

@@ -63,8 +63,7 @@ export class HaDeviceSelector extends LitElement {
}
if (this.selector.device.integration) {
if (
this._configEntries &&
!this._configEntries.some((entry) =>
!this._configEntries?.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {

View File

@@ -19,7 +19,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
@property() public selector!: EntitySelector;
@internalProperty() private _entityPlaformLookup?: Record<string, string>;
@internalProperty() private _entities?: Record<string, string>;
@property() public value?: any;
@@ -45,7 +45,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
this._entityPlaformLookup = entityLookup;
this._entities = entityLookup;
}),
];
}
@@ -66,9 +66,8 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
}
if (this.selector.entity.integration) {
if (
!this._entityPlaformLookup ||
this._entityPlaformLookup[entity.entity_id] !==
this.selector.entity.integration
!this._entities ||
this._entities[entity.entity_id] !== this.selector.entity.integration
) {
return false;
}

View File

@@ -1,153 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { TargetSelector } from "../../data/selector";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import { DeviceRegistryEntry } from "../../data/device_registry";
import "../ha-target-picker";
import "@material/mwc-list/mwc-list-item";
import "@polymer/paper-input/paper-input";
import "@material/mwc-list/mwc-list";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { Target } from "../../data/target";
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
@customElement("ha-selector-target")
export class HaTargetSelector extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public selector!: TargetSelector;
@property() public value?: Target;
@property() public label?: string;
@internalProperty() private _entityPlaformLookup?: Record<string, string>;
@internalProperty() private _configEntries?: ConfigEntry[];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
const entityLookup = {};
for (const confEnt of entities) {
if (!confEnt.platform) {
continue;
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
this._entityPlaformLookup = entityLookup;
}),
];
}
protected updated(changedProperties) {
if (changedProperties.has("selector")) {
const oldSelector = changedProperties.get("selector");
if (
oldSelector !== this.selector &&
this.selector.target.device?.integration
) {
this._loadConfigEntries();
}
}
}
protected render() {
return html`<ha-target-picker
.hass=${this.hass}
.value=${this.value}
.deviceFilter=${(device) => this._filterDevices(device)}
.entityRegFilter=${(entity: EntityRegistryEntry) =>
this._filterRegEntities(entity)}
.entityFilter=${(entity: HassEntity) => this._filterEntities(entity)}
.includeDeviceClasses=${this.selector.target.entity?.device_class
? [this.selector.target.entity.device_class]
: undefined}
.includeDomains=${this.selector.target.entity?.domain
? [this.selector.target.entity.domain]
: undefined}
></ha-target-picker>`;
}
private _filterEntities(entity: HassEntity): boolean {
if (this.selector.target.entity?.integration) {
if (
!this._entityPlaformLookup ||
this._entityPlaformLookup[entity.entity_id] !==
this.selector.target.entity.integration
) {
return false;
}
}
return true;
}
private _filterRegEntities(entity: EntityRegistryEntry): boolean {
if (this.selector.target.entity?.integration) {
if (entity.platform !== this.selector.target.entity.integration) {
return false;
}
}
return true;
}
private _filterDevices(device: DeviceRegistryEntry): boolean {
if (
this.selector.target.device?.manufacturer &&
device.manufacturer !== this.selector.target.device.manufacturer
) {
return false;
}
if (
this.selector.target.device?.model &&
device.model !== this.selector.target.device.model
) {
return false;
}
if (this.selector.target.device?.integration) {
if (
!this._configEntries?.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {
return false;
}
}
return true;
}
private async _loadConfigEntries() {
this._configEntries = (await getConfigEntries(this.hass)).filter(
(entry) => entry.domain === this.selector.target.device?.integration
);
}
static get styles(): CSSResult {
return css`
ha-target-picker {
margin: 0 -8px;
display: block;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-target": HaTargetSelector;
}
}

View File

@@ -5,11 +5,9 @@ import { HomeAssistant } from "../../types";
import "./ha-selector-entity";
import "./ha-selector-device";
import "./ha-selector-area";
import "./ha-selector-target";
import "./ha-selector-number";
import "./ha-selector-boolean";
import "./ha-selector-time";
import "./ha-selector-action";
import { Selector } from "../../data/selector";
@customElement("ha-selector")

View File

@@ -18,6 +18,11 @@ export class HaSettingsRow extends LitElement {
protected render(): SVGTemplateResult {
return html`
<style>
paper-item-body {
padding-right: 16px;
}
</style>
<paper-item-body
?two-line=${!this.threeLine}
?three-line=${this.threeLine}
@@ -38,14 +43,6 @@ export class HaSettingsRow extends LitElement {
align-self: auto;
align-items: center;
}
paper-item-body {
padding: 8px 16px 8px 0;
}
paper-item-body[two-line] {
min-height: calc(
var(--paper-item-body-two-line-min-height, 72px) - 16px
);
}
:host([narrow]) {
align-items: normal;
flex-direction: column;
@@ -55,9 +52,6 @@ export class HaSettingsRow extends LitElement {
::slotted(ha-switch) {
padding: 16px 0;
}
div[secondary] {
white-space: normal;
}
`;
}
}

View File

@@ -1,605 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
unsafeCSS,
} from "lit-element";
import { HomeAssistant } from "../types";
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import {
mdiSofa,
mdiDevices,
mdiClose,
mdiPlus,
mdiUnfoldMoreVertical,
} from "@mdi/js";
import "./ha-svg-icon";
import "./ha-icon";
import "@material/mwc-icon-button/mwc-icon-button";
import { classMap } from "lit-html/directives/class-map";
import "@material/mwc-button/mwc-button";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../data/area_registry";
import {
computeDeviceName,
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../data/entity_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { computeStateName } from "../common/entity/compute_state_name";
import { stateIcon } from "../common/entity/state_icon";
import { fireEvent } from "../common/dom/fire_event";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import { computeDomain } from "../common/entity/compute_domain";
import { Target } from "../data/target";
import { ensureArray } from "../common/ensure-array";
import "./entity/ha-entity-picker";
import "./device/ha-device-picker";
import "./ha-area-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "@polymer/paper-tooltip/paper-tooltip";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public value?: Target;
@property() public label?: string;
/**
* Show only targets with entities from specific domains.
* @type {Array}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show only targets with entities of these device classes.
* @type {Array}
* @attr include-device-classes
*/
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
@property() public entityRegFilter?: (entity: EntityRegistryEntry) => boolean;
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
@internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry };
@internalProperty() private _devices?: {
[deviceId: string]: DeviceRegistryEntry;
};
@internalProperty() private _entities?: EntityRegistryEntry[];
@internalProperty() private _addMode?: "area_id" | "entity_id" | "device_id";
@query("#input") private _inputElement?;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeAreaRegistry(this.hass.connection!, (areas) => {
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
for (const area of areas) {
areaLookup[area.area_id] = area;
}
this._areas = areaLookup;
}),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
for (const device of devices) {
deviceLookup[device.id] = device;
}
this._devices = deviceLookup;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
protected render() {
if (!this._areas || !this._devices || !this._entities) {
return html``;
}
return html`<div class="mdc-chip-set items">
${ensureArray(this.value?.area_id)?.map((area_id) => {
const area = this._areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
mdiSofa
);
})}
${ensureArray(this.value?.device_id)?.map((device_id) => {
const device = this._devices![device_id];
return this._renderChip(
"device_id",
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
mdiDevices
);
})}
${ensureArray(this.value?.entity_id)?.map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity ? stateIcon(entity) : undefined
);
})}
</div>
${this._renderPicker()}
<div class="mdc-chip-set">
<div
class="mdc-chip area_id add"
.type=${"area_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}</span
>
</span>
</span>
</div>
<div
class="mdc-chip device_id add"
.type=${"device_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}</span
>
</span>
</span>
</div>
<div
class="mdc-chip entity_id add"
.type=${"entity_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}</span
>
</span>
</span>
</div>
</div>`;
}
private async _showPicker(ev) {
this._addMode = ev.currentTarget.type;
await this.updateComplete;
setTimeout(() => {
this._inputElement?.open();
this._inputElement?.focus();
}, 0);
}
private _renderChip(
type: string,
id: string,
name: string,
icon?: string,
iconPath?: string
) {
return html`
<div
class="mdc-chip ${classMap({
[type]: true,
})}"
>
${iconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${iconPath}
></ha-svg-icon>`
: ""}
${icon
? html`<ha-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.icon=${icon}
></ha-icon>`
: ""}
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">${name}</span>
</span>
</span>
${type === "entity_id"
? ""
: html` <span role="gridcell">
<mwc-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${"Expand"}
.id=${id}
.type=${type}
@click=${this._handleExpand}
>
<ha-svg-icon .path=${mdiUnfoldMoreVertical}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip class="expand" animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}</paper-tooltip
>
</span>`}
<span role="gridcell">
<mwc-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${"Remove"}
.id=${id}
.type=${type}
@click=${this._handleRemove}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.remove_${type}`
)}</paper-tooltip
>
</span>
</div>
`;
}
private _renderPicker() {
switch (this._addMode) {
case "area_id":
return html`<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-area-picker>`;
case "device_id":
return html`<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-device-picker>`;
case "entity_id":
return html`<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-entity-picker>`;
}
return html``;
}
private _targetPicked(ev) {
ev.stopPropagation();
if (!ev.detail.value) {
return;
}
const value = ev.detail.value;
const target = ev.currentTarget;
target.value = "";
this._addMode = undefined;
fireEvent(this, "value-changed", {
value: this.value
? {
...this.value,
[target.type]: this.value[target.type]
? [...ensureArray(this.value[target.type]), value]
: value,
}
: { [target.type]: value },
});
}
private _handleExpand(ev) {
const target = ev.currentTarget as any;
const newDevices: string[] = [];
const newEntities: string[] = [];
if (target.type === "area_id") {
Object.values(this._devices!).forEach((device) => {
if (
device.area_id === target.id &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
this._entities!.forEach((entity) => {
if (
entity.area_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "device_id") {
this._entities!.forEach((entity) => {
if (
entity.device_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else {
return;
}
let value = this.value;
if (newEntities.length) {
value = this._addItems(value, "entity_id", newEntities);
}
if (newDevices.length) {
value = this._addItems(value, "device_id", newDevices);
}
value = this._removeItem(value, target.type, target.id);
fireEvent(this, "value-changed", { value });
}
private _handleRemove(ev) {
const target = ev.currentTarget as any;
fireEvent(this, "value-changed", {
value: this._removeItem(this.value, target.type, target.id),
});
}
private _addItems(
value: this["value"],
type: string,
ids: string[]
): this["value"] {
return {
...value,
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
};
}
private _removeItem(
value: this["value"],
type: string,
id: string
): this["value"] {
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 {
const devEntities = this._entities?.filter(
(entity) => entity.device_id === device.id
);
if (this.includeDomains) {
if (!devEntities || !devEntities.length) {
return false;
}
if (
!devEntities.some((entity) =>
this.includeDomains!.includes(computeDomain(entity.entity_id))
)
) {
return false;
}
}
if (this.includeDeviceClasses) {
if (!devEntities || !devEntities.length) {
return false;
}
if (
!devEntities.some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
this.includeDeviceClasses!.includes(
stateObj.attributes.device_class
)
);
})
) {
return false;
}
}
if (this.deviceFilter) {
return this.deviceFilter(device);
}
return true;
}
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
if (
this.includeDomains &&
!this.includeDomains.includes(computeDomain(entity.entity_id))
) {
return false;
}
if (this.includeDeviceClasses) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (
!stateObj.attributes.device_class ||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
) {
return false;
}
}
if (this.entityRegFilter) {
return this.entityRegFilter(entity);
}
return true;
}
static get styles(): CSSResult {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
color: var(--primary-text-color);
}
.items {
z-index: 2;
}
.mdc-chip.add {
color: rgba(0, 0, 0, 0.87);
}
.mdc-chip:not(.add) {
cursor: default;
}
.mdc-chip mwc-icon-button {
--mdc-icon-button-size: 24px;
display: flex;
align-items: center;
outline: none;
}
.mdc-chip mwc-icon-button ha-svg-icon {
border-radius: 50%;
background: var(--secondary-text-color);
}
.mdc-chip__icon.mdc-chip__icon--trailing {
width: 16px;
height: 16px;
--mdc-icon-size: 14px;
color: var(--card-background-color);
}
.mdc-chip__icon--leading {
display: flex;
align-items: center;
justify-content: center;
--mdc-icon-size: 20px;
border-radius: 50%;
padding: 6px;
margin-left: -14px !important;
}
.expand-btn {
margin-right: 0;
}
.mdc-chip.area_id:not(.add) {
border: 2px solid #fed6a4;
background: var(--card-background-color);
}
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.area_id.add {
background: #fed6a4;
}
.mdc-chip.device_id:not(.add) {
border: 2px solid #a8e1fb;
background: var(--card-background-color);
}
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.device_id.add {
background: #a8e1fb;
}
.mdc-chip.entity_id:not(.add) {
border: 2px solid #d2e7b9;
background: var(--card-background-color);
}
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.entity_id.add {
background: #d2e7b9;
}
.mdc-chip:hover {
z-index: 5;
}
paper-tooltip.expand {
min-width: 200px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker": HaTargetPicker;
}
}

View File

@@ -6,7 +6,7 @@ import { navigate } from "../common/navigate";
import { Context, HomeAssistant } from "../types";
import { BlueprintInput } from "./blueprint";
import { DeviceCondition, DeviceTrigger } from "./device_automation";
import { Action, MODES } from "./script";
import { Action } from "./script";
export interface AutomationEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
@@ -26,7 +26,7 @@ export interface ManualAutomationConfig {
trigger: Trigger[];
condition?: Condition[];
action: Action[];
mode?: typeof MODES[number];
mode?: "single" | "restart" | "queued" | "parallel";
max?: number;
}

View File

@@ -17,7 +17,6 @@ export interface DeviceRegistryEntry {
area_id?: string;
name_by_user?: string;
entry_type: "service" | null;
disabled_by: string | null;
}
export interface DeviceEntityLookup {
@@ -27,7 +26,6 @@ export interface DeviceEntityLookup {
export interface DeviceRegistryEntryMutableParams {
area_id?: string | null;
name_by_user?: string | null;
disabled_by?: string | null;
}
export const fallbackDeviceName = (

View File

@@ -124,17 +124,13 @@ export const getLogbookMessage = (
switch (domain) {
case "device_tracker":
case "person":
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
);
return state === "not_home"
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`)
: hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.was_at_state`,
"state",
state
);
case "sun":
return state === "above_horizon"

View File

@@ -148,11 +148,6 @@ export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
export interface MultipleActionConfig extends BaseActionConfig {
action: "multiple";
actions: ActionConfig[];
}
export interface BaseActionConfig {
confirmation?: ConfirmationRestrictionConfig;
}
@@ -173,8 +168,7 @@ export type ActionConfig =
| UrlActionConfig
| MoreInfoActionConfig
| NoActionConfig
| CustomActionConfig
| MultipleActionConfig;
| CustomActionConfig;
type LovelaceUpdatedEvent = HassEventBase & {
event_type: "lovelace_updated";

View File

@@ -7,13 +7,13 @@ import { navigate } from "../common/navigate";
import { HomeAssistant } from "../types";
import { Condition, Trigger } from "./automation";
export const MODES = ["single", "restart", "queued", "parallel"] as const;
export const MODES = ["single", "restart", "queued", "parallel"];
export const MODES_MAX = ["queued", "parallel"];
export interface ScriptEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
last_triggered: string;
mode: typeof MODES[number];
mode: "single" | "restart" | "queued" | "parallel";
current?: number;
max?: number;
};
@@ -23,7 +23,7 @@ export interface ScriptConfig {
alias: string;
sequence: Action[];
icon?: string;
mode?: typeof MODES[number];
mode?: "single" | "restart" | "queued" | "parallel";
max?: number;
}

View File

@@ -2,11 +2,9 @@ export type Selector =
| EntitySelector
| DeviceSelector
| AreaSelector
| TargetSelector
| NumberSelector
| BooleanSelector
| TimeSelector
| ActionSelector;
| TimeSelector;
export interface EntitySelector {
entity: {
@@ -43,21 +41,6 @@ export interface AreaSelector {
};
}
export interface TargetSelector {
target: {
entity?: {
integration?: EntitySelector["entity"]["integration"];
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
device?: {
integration?: DeviceSelector["device"]["integration"];
manufacturer?: DeviceSelector["device"]["manufacturer"];
model?: DeviceSelector["device"]["model"];
};
};
}
export interface NumberSelector {
number: {
min: number;
@@ -77,8 +60,3 @@ export interface TimeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
time: {};
}
export interface ActionSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
action: {};
}

View File

@@ -38,12 +38,3 @@ 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,
});

View File

@@ -1,5 +0,0 @@
export interface Target {
entity_id?: string[];
device_id?: string[];
area_id?: string[];
}

View File

@@ -20,7 +20,6 @@ export interface User {
export interface UpdateUserParams {
name?: User["name"];
is_active?: User["is_active"];
group_ids?: User["group_ids"];
}

View File

@@ -17,17 +17,17 @@ import "../../components/ha-switch";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { DialogBoxParams } from "./show-dialog-box";
import { DialogParams } from "./show-dialog-box";
@customElement("dialog-box")
class DialogBox extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _params?: DialogBoxParams;
@internalProperty() private _params?: DialogParams;
@internalProperty() private _value?: string;
public async showDialog(params: DialogBoxParams): Promise<void> {
public async showDialog(params: DialogParams): 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=${confirmPrompt}
?escapeKeyAction=${confirmPrompt}
?scrimClickAction=${this._params.prompt}
?escapeKeyAction=${this._params.prompt}
@closed=${this._dialogClosed}
defaultAction="ignore"
.heading=${this._params.title
@@ -140,10 +140,10 @@ class DialogBox extends LitElement {
}
private _dialogClosed(ev) {
if (this._params?.prompt && ev.detail.action === "ignore") {
if (ev.detail.action === "ignore") {
return;
}
this._dismiss();
this.closeDialog();
}
private _close(): void {

View File

@@ -1,31 +1,31 @@
import { TemplateResult } from "lit-html";
import { fireEvent } from "../../common/dom/fire_event";
interface BaseDialogBoxParams {
interface BaseDialogParams {
confirmText?: string;
text?: string | TemplateResult;
title?: string;
warning?: boolean;
}
export interface AlertDialogParams extends BaseDialogBoxParams {
export interface AlertDialogParams extends BaseDialogParams {
confirm?: () => void;
}
export interface ConfirmationDialogParams extends BaseDialogBoxParams {
export interface ConfirmationDialogParams extends BaseDialogParams {
dismissText?: string;
confirm?: () => void;
cancel?: () => void;
}
export interface PromptDialogParams extends BaseDialogBoxParams {
export interface PromptDialogParams extends BaseDialogParams {
inputLabel?: string;
inputType?: string;
defaultValue?: string;
confirm?: (out?: string) => void;
}
export interface DialogBoxParams
export interface DialogParams
extends ConfirmationDialogParams,
PromptDialogParams {
confirm?: (out?: string) => void;
@@ -37,10 +37,10 @@ export const loadGenericDialog = () => import("./dialog-box");
const showDialogHelper = (
element: HTMLElement,
dialogParams: DialogBoxParams,
dialogParams: DialogParams,
extra?: {
confirmation?: DialogBoxParams["confirmation"];
prompt?: DialogBoxParams["prompt"];
confirmation?: DialogParams["confirmation"];
prompt?: DialogParams["prompt"];
}
) =>
new Promise((resolve) => {

View File

@@ -10,7 +10,6 @@ import {
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { UNAVAILABLE_STATES } from "../../../data/entity";
@customElement("more-info-counter")
class MoreInfoCounter extends LitElement {
@@ -23,29 +22,21 @@ 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}
.disabled=${disabled}
@click="${this._handleActionClick}"
>
${this.hass!.localize("ui.card.counter.actions.increment")}
</mwc-button>
<mwc-button
.action="${"decrement"}"
@click=${this._handleActionClick}
.disabled=${disabled}
@click="${this._handleActionClick}"
>
${this.hass!.localize("ui.card.counter.actions.decrement")}
</mwc-button>
<mwc-button
.action="${"reset"}"
@click=${this._handleActionClick}
.disabled=${disabled}
>
<mwc-button .action="${"reset"}" @click="${this._handleActionClick}">
${this.hass!.localize("ui.card.counter.actions.reset")}
</mwc-button>
</div>
@@ -62,7 +53,8 @@ class MoreInfoCounter extends LitElement {
static get styles(): CSSResult {
return css`
.actions {
margin: 8px 0;
margin: 0;
padding-top: 20px;
display: flex;
flex-wrap: wrap;
justify-content: center;

View File

@@ -75,7 +75,7 @@ class MoreInfoPerson extends LitElement {
justify-content: space-between;
}
.actions {
margin: 8px 0;
margin: 36px 0 8px 0;
text-align: right;
}
ha-map {

View File

@@ -44,7 +44,7 @@ class MoreInfoSun extends LitElement {
>
<ha-relative-time
.hass=${this.hass}
.datetime=${item === "ris" ? risingDate : settingDate}
.datetimeObj=${item === "ris" ? risingDate : settingDate}
></ha-relative-time>
</div>
<div class="value">
@@ -80,7 +80,6 @@ class MoreInfoSun extends LitElement {
}
ha-relative-time {
display: inline-block;
white-space: nowrap;
}
ha-relative-time::first-letter {
text-transform: lowercase;

View File

@@ -76,7 +76,8 @@ class MoreInfoTimer extends LitElement {
static get styles(): CSSResult {
return css`
.actions {
margin: 8px 0;
margin: 0;
padding-top: 20px;
display: flex;
flex-wrap: wrap;
justify-content: center;

View File

@@ -60,12 +60,6 @@ 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}
@@ -177,7 +171,6 @@ export class HaTabsSubpageDataTable extends LitElement {
.noDataText=${this.noDataText}
.dir=${computeRTLDirection(this.hass)}
.clickable=${this.clickable}
.appendRow=${this.appendRow}
>
${!this.narrow
? html`

View File

@@ -17,7 +17,6 @@ import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
import { navigate } from "../../../common/navigate";
class DialogAreaDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -155,8 +154,6 @@ class DialogAreaDetail extends LitElement {
} finally {
this._submitting = false;
}
navigate(this, "/config/areas/dashboard");
}
static get styles(): CSSResult[] {

View File

@@ -15,14 +15,12 @@ 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";
@@ -105,8 +103,6 @@ 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;
@@ -115,10 +111,6 @@ 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() {
@@ -195,7 +187,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

View File

@@ -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 { EntityIdOrAll } from "../../../../lovelace/common/structs/is-entity-id";
import { EntityId } from "../../../../lovelace/common/structs/is-entity-id";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
const actionStruct = object({
service: optional(string()),
entity_id: optional(EntityIdOrAll),
entity_id: optional(EntityId),
data: optional(any()),
});

View File

@@ -39,7 +39,7 @@ export class HaWaitForTriggerAction extends LitElement
)}
>
<ha-switch
.checked=${continue_on_timeout ?? true}
.checked=${continue_on_timeout}
@change=${this._continueChanged}
></ha-switch>
</ha-formfield>

View File

@@ -18,9 +18,13 @@ 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";
import { navigate } from "../../../common/navigate";
import {
BlueprintOrError,
Blueprints,
@@ -59,7 +63,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
protected render() {
const blueprint = this._blueprint;
return html`<ha-config-section vertical .isWide=${this.isWide}>
return html`<ha-config-section .isWide=${this.isWide}>
${!this.narrow
? html` <span slot="header">${this.config.alias}</span> `
: ""}
@@ -115,7 +119,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
</ha-card>
</ha-config-section>
<ha-config-section vertical .isWide=${this.isWide}>
<ha-config-section .isWide=${this.isWide}>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"
@@ -140,6 +144,11 @@ export class HaBlueprintAutomationEditor extends LitElement {
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
<mwc-button @click=${this._navigateBlueprints}>
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.manage_blueprints"
)}
</mwc-button>
</div>
${this.config.use_blueprint.path
@@ -148,37 +157,41 @@ export class HaBlueprintAutomationEditor extends LitElement {
There is an error in this Blueprint: ${blueprint.error}
</p>`
: html`${blueprint?.metadata.description
? html`<p class="card-content pre-line">
${blueprint.metadata.description}
</p>`
? html`<p>${blueprint.metadata.description}</p>`
: ""}
${blueprint?.metadata?.input &&
Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
required
.value=${this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)
? html`<h3>
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.inputs"
)}
</h3>
${Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description"
>${value?.description}</span
>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ||
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
.value=${this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)}`
: html`<p class="padding">
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_inputs"
@@ -224,18 +237,12 @@ 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,
input: { ...this.config.use_blueprint.input, [key]: value },
},
},
});
@@ -260,18 +267,25 @@ export class HaBlueprintAutomationEditor extends LitElement {
});
}
private _navigateBlueprints() {
navigate(this, "/config/blueprint");
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
ha-card {
overflow: hidden;
}
.padding {
padding: 16px;
}
.pre-line {
white-space: pre-line;
}
.blueprint-picker-container {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
h3 {
margin: 16px;
@@ -290,10 +304,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
border-top: 1px solid var(--divider-color);
}
:host(:not([narrow])) ha-settings-row paper-input {
width: 60%;
width: 50%;
}
:host(:not([narrow])) ha-settings-row ha-selector {
width: 60%;
width: 50%;
}
`,
];

View File

@@ -32,7 +32,6 @@ 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,
@@ -207,7 +206,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
? html`<blueprint-automation-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.stateObj=${stateObj}
.config=${this._config}
@value-changed=${this._valueChanged}
@@ -215,7 +213,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
: html`<manual-automation-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.stateObj=${stateObj}
.config=${this._config}
@value-changed=${this._valueChanged}
@@ -395,12 +392,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
return cleanConfig;
}
private async _copyYaml(): Promise<void> {
private async _copyYaml() {
if (this._editor?.yaml) {
await copyToClipboard(this._editor.yaml);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
navigator.clipboard.writeText(this._editor.yaml);
}
}

View File

@@ -12,7 +12,6 @@ import {
internalProperty,
query,
TemplateResult,
css,
} from "lit-element";
import "../../../components/ha-dialog";
import { haStyleDialog } from "../../../resources/styles";
@@ -74,9 +73,7 @@ class DialogImportBlueprint extends LitElement {
this._result.blueprint.metadata.domain
)}
<br />
<p class="pre-line">
${this._result.blueprint.metadata.description}
</p>
${this._result.blueprint.metadata.description}
${this._result.validation_errors
? html`
<p class="error">
@@ -107,16 +104,7 @@ class DialogImportBlueprint extends LitElement {
<pre>${this._result.raw_data}</pre>
</ha-expansion-panel>`
: html`${this.hass.localize(
"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
>`
"ui.panel.config.blueprint.add.import_introduction"
)}<paper-input
id="input"
.label=${this.hass.localize(
@@ -211,15 +199,8 @@ class DialogImportBlueprint extends LitElement {
}
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
.pre-line {
white-space: pre-line;
}
`,
];
static get styles(): CSSResult {
return haStyleDialog;
}
}

View File

@@ -1,6 +1,6 @@
import "../../../components/ha-fab";
import "@material/mwc-icon-button";
import { mdiHelpCircle, mdiDelete, mdiRobot, mdiDownload } from "@mdi/js";
import { mdiPlus, mdiHelpCircle, mdiDelete, mdiRobot } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
CSSResult,
@@ -112,6 +112,7 @@ class HaBlueprintOverview extends LitElement {
create: {
title: "",
type: narrow ? "icon-button" : undefined,
width: narrow ? undefined : "180px",
template: (_, blueprint: any) =>
blueprint.error
? ""
@@ -125,9 +126,8 @@ class HaBlueprintOverview extends LitElement {
"ui.panel.config.blueprint.overview.use_blueprint"
)}
@click=${(ev) => this._createNew(ev)}
>
<ha-svg-icon .path=${mdiRobot}></ha-svg-icon>
</mwc-icon-button>`
><ha-svg-icon .path=${mdiRobot}></ha-svg-icon
></mwc-icon-button>`
: html`<mwc-button
.blueprint=${blueprint}
@click=${(ev) => this._createNew(ev)}
@@ -170,23 +170,6 @@ 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>
@@ -199,7 +182,7 @@ class HaBlueprintOverview extends LitElement {
extended
@click=${this._addBlueprint}
>
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
</hass-tabs-subpage-data-table>
`;
@@ -212,10 +195,7 @@ class HaBlueprintOverview extends LitElement {
${this.hass.localize("ui.panel.config.blueprint.overview.introduction")}
<p>
<a
href="${documentationUrl(
this.hass,
"/docs/automation/using_blueprints/"
)}"
href="${documentationUrl(this.hass, "/docs/blueprint/editor/")}"
target="_blank"
rel="noreferrer"
>

View File

@@ -92,7 +92,7 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
}
_computeTabs() {
return configSections.advanced;
return configSections.general;
}
computeEntities(hass) {

View File

@@ -8,6 +8,7 @@ import {
html,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -30,7 +31,7 @@ export class HaDeviceEntitiesCard extends LitElement {
@property() public entities!: EntityRegistryStateEntry[];
@property() public showDisabled = false;
@internalProperty() private _showDisabled = false;
private _entityRows: Array<LovelaceRow | HuiErrorCard> = [];
@@ -67,7 +68,7 @@ export class HaDeviceEntitiesCard extends LitElement {
})}
</div>
${disabledEntities.length
? !this.showDisabled
? !this._showDisabled
? html`
<button
class="show-more"
@@ -118,7 +119,7 @@ export class HaDeviceEntitiesCard extends LitElement {
}
private _toggleShowDisabled() {
this.showDisabled = !this.showDisabled;
this._showDisabled = !this._showDisabled;
}
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
@@ -226,9 +227,3 @@ export class HaDeviceEntitiesCard extends LitElement {
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-entities-card": HaDeviceEntitiesCard;
}
}

View File

@@ -19,11 +19,10 @@ import {
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
import { HomeAssistant } from "../../../../types";
import type { HaSwitch } from "../../../../components/ha-switch";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { computeDeviceName } from "../../../../data/device_registry";
import { fireEvent } from "../../../../common/dom/fire_event";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
import { haStyleDialog } from "../../../../resources/styles";
@customElement("dialog-device-registry-detail")
class DialogDeviceRegistryDetail extends LitElement {
@@ -37,8 +36,6 @@ class DialogDeviceRegistryDetail extends LitElement {
@internalProperty() private _areaId?: string;
@internalProperty() private _disabledBy!: string | null;
@internalProperty() private _submitting?: boolean;
public async showDialog(
@@ -48,7 +45,6 @@ class DialogDeviceRegistryDetail extends LitElement {
this._error = undefined;
this._nameByUser = this._params.device.name_by_user || "";
this._areaId = this._params.device.area_id;
this._disabledBy = this._params.device.disabled_by;
await this.updateComplete;
}
@@ -84,32 +80,6 @@ class DialogDeviceRegistryDetail extends LitElement {
.value=${this._areaId}
@value-changed=${this._areaPicked}
></ha-area-picker>
<div class="row">
<ha-switch
.checked=${!this._disabledBy}
@change=${this._disabledByChanged}
>
</ha-switch>
<div>
<div>
${this.hass.localize("ui.panel.config.devices.enabled_label")}
</div>
<div class="secondary">
${this._disabledBy && this._disabledBy !== "user"
? this.hass.localize(
"ui.panel.config.devices.enabled_cause",
"cause",
this.hass.localize(
`config_entry.disabled_by.${this._disabledBy}`
)
)
: ""}
${this.hass.localize(
"ui.panel.config.devices.enabled_description"
)}
</div>
</div>
</div>
</div>
</div>
<mwc-button
@@ -139,17 +109,12 @@ class DialogDeviceRegistryDetail extends LitElement {
this._areaId = event.detail.value;
}
private _disabledByChanged(ev: Event): void {
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
}
private async _updateEntry(): Promise<void> {
this._submitting = true;
try {
await this._params!.updateEntry({
name_by_user: this._nameByUser.trim() || null,
area_id: this._areaId || null,
disabled_by: this._disabledBy || null,
});
this._params = undefined;
} catch (err) {
@@ -163,7 +128,6 @@ class DialogDeviceRegistryDetail extends LitElement {
static get styles(): CSSResult[] {
return [
haStyle,
haStyleDialog,
css`
.form {
@@ -175,15 +139,6 @@ class DialogDeviceRegistryDetail extends LitElement {
.error {
color: var(--error-color);
}
ha-switch {
margin-right: 16px;
}
.row {
margin-top: 8px;
color: var(--primary-text-color);
display: flex;
align-items: center;
}
`,
];
}

View File

@@ -46,7 +46,6 @@ import "./device-detail/ha-device-entities-card";
import "./device-detail/ha-device-info-card";
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
import { brandsUrl } from "../../../util/brands-url";
import { haStyle } from "../../../resources/styles";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null;
@@ -247,28 +246,6 @@ export class HaConfigDevicePage extends LitElement {
.devices=${this.devices}
.device=${device}
>
${
device.disabled_by
? html`
<div>
<p class="warning">
${this.hass.localize(
"ui.panel.config.devices.enabled_cause",
"cause",
this.hass.localize(
`ui.panel.config.devices.disabled_by.${device.disabled_by}`
)
)}
</p>
</div>
<div class="card-actions" slot="actions">
<mwc-button unelevated @click=${this._enableDevice}>
${this.hass.localize("ui.common.enable")}
</mwc-button>
</div>
`
: html``
}
${this._renderIntegrationInfo(device, integrations)}
</ha-device-info-card>
@@ -278,7 +255,6 @@ export class HaConfigDevicePage extends LitElement {
<ha-device-entities-card
.hass=${this.hass}
.entities=${entities}
.showDisabled=${device.disabled_by !== null}
>
</ha-device-entities-card>
`
@@ -296,14 +272,9 @@ export class HaConfigDevicePage extends LitElement {
)}
<ha-icon-button
@click=${this._showAutomationDialog}
.disabled=${device.disabled_by}
title=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.automation.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.automation.create"
)}
title=${this.hass.localize(
"ui.panel.config.devices.automation.create"
)}
icon="hass:plus-circle"
></ha-icon-button>
</h1>
@@ -371,16 +342,9 @@ export class HaConfigDevicePage extends LitElement {
<ha-icon-button
@click=${this._createScene}
.disabled=${device.disabled_by}
title=${
device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.scene.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.scene.create"
)
}
title=${this.hass.localize(
"ui.panel.config.devices.scene.create"
)}
icon="hass:plus-circle"
></ha-icon-button>
</h1>
@@ -451,14 +415,9 @@ export class HaConfigDevicePage extends LitElement {
)}
<ha-icon-button
@click=${this._showScriptDialog}
.disabled=${device.disabled_by}
title=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.script.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.script.create"
)}
title=${this.hass.localize(
"ui.panel.config.devices.script.create"
)}
icon="hass:plus-circle"
></ha-icon-button>
</h1>
@@ -673,137 +632,128 @@ export class HaConfigDevicePage extends LitElement {
});
}
private async _enableDevice(): Promise<void> {
await updateDeviceRegistryEntry(this.hass, this.deviceId, {
disabled_by: null,
});
}
static get styles(): CSSResult {
return css`
.container {
display: flex;
flex-wrap: wrap;
margin: auto;
max-width: 1000px;
margin-top: 32px;
margin-bottom: 32px;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.container {
display: flex;
flex-wrap: wrap;
margin: auto;
max-width: 1000px;
margin-top: 32px;
margin-bottom: 32px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-header ha-icon-button {
margin-right: -8px;
color: var(--primary-color);
height: auto;
}
.card-header ha-icon-button {
margin-right: -8px;
color: var(--primary-color);
height: auto;
}
.device-info {
padding: 16px;
}
.device-info {
padding: 16px;
}
.show-more {
}
.show-more {
}
h1 {
margin: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
h1 {
margin: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
.header {
display: flex;
justify-content: space-between;
}
.header {
display: flex;
justify-content: space-between;
}
.column,
.fullwidth {
padding: 8px;
box-sizing: border-box;
}
.column {
width: 33%;
flex-grow: 1;
}
.fullwidth {
width: 100%;
flex-grow: 1;
}
.column,
.fullwidth {
padding: 8px;
box-sizing: border-box;
}
.column {
width: 33%;
flex-grow: 1;
}
.fullwidth {
width: 100%;
flex-grow: 1;
}
.header-right {
align-self: center;
}
.header-right {
align-self: center;
}
.header-right img {
height: 30px;
}
.header-right img {
height: 30px;
}
.header-right {
display: flex;
}
.header-right {
display: flex;
}
.header-right:first-child {
width: 100%;
justify-content: flex-end;
}
.header-right:first-child {
width: 100%;
justify-content: flex-end;
}
.header-right > *:not(:first-child) {
margin-left: 16px;
}
.header-right > *:not(:first-child) {
margin-left: 16px;
}
.battery {
align-self: center;
align-items: center;
display: flex;
}
.battery {
align-self: center;
align-items: center;
display: flex;
}
.column > *:not(:first-child) {
margin-top: 16px;
}
.column > *:not(:first-child) {
margin-top: 16px;
}
:host([narrow]) .column {
width: 100%;
}
:host([narrow]) .column {
width: 100%;
}
:host([narrow]) .container {
margin-top: 0;
}
:host([narrow]) .container {
margin-top: 0;
}
paper-item {
cursor: pointer;
font-size: var(--paper-font-body1_-_font-size);
}
paper-item {
cursor: pointer;
font-size: var(--paper-font-body1_-_font-size);
}
paper-item.no-link {
cursor: default;
}
paper-item.no-link {
cursor: default;
}
a {
text-decoration: none;
color: var(--primary-color);
}
a {
text-decoration: none;
color: var(--primary-color);
}
ha-card {
padding-bottom: 8px;
}
ha-card {
padding-bottom: 8px;
}
ha-card a {
color: var(--primary-text-color);
}
`,
];
ha-card a {
color: var(--primary-text-color);
}
`;
}
}

View File

@@ -1,9 +1,5 @@
import { mdiPlus, mdiFilterVariant, mdiCancel } from "@mdi/js";
import "@material/mwc-list/mwc-list-item";
import "@polymer/paper-tooltip/paper-tooltip";
import { mdiPlus } from "@mdi/js";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
@@ -11,9 +7,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize";
@@ -24,7 +18,6 @@ import {
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-button-menu";
import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry } from "../../../data/config_entries";
import {
@@ -41,7 +34,6 @@ import { domainToName } from "../../../data/integration";
import "../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import { haStyle } from "../../../resources/styles";
interface DeviceRowData extends DeviceRegistryEntry {
device?: DeviceRowData;
@@ -72,12 +64,6 @@ export class HaConfigDeviceDashboard extends LitElement {
window.location.search
);
@internalProperty() private _showDisabled = false;
@internalProperty() private _filter = "";
@internalProperty() private _numHiddenDevices = 0;
private _activeFilters = memoizeOne(
(
entries: ConfigEntry[],
@@ -88,10 +74,6 @@ export class HaConfigDeviceDashboard extends LitElement {
filters.forEach((value, key) => {
switch (key) {
case "config_entry": {
// If we are requested to show the devices for a given config entry,
// also show the disabled ones by default.
this._showDisabled = true;
const configEntry = entries.find(
(entry) => entry.entry_id === value
);
@@ -123,7 +105,6 @@ export class HaConfigDeviceDashboard extends LitElement {
entities: EntityRegistryEntry[],
areas: AreaRegistryEntry[],
filters: URLSearchParams,
showDisabled: boolean,
localize: LocalizeFunc
) => {
// Some older installations might have devices pointing at invalid entryIDs
@@ -136,9 +117,6 @@ export class HaConfigDeviceDashboard extends LitElement {
deviceLookup[device.id] = device;
}
// If nothing gets filtered, this is our correct count of devices
let startLength = outputDevices.length;
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
@@ -167,7 +145,6 @@ export class HaConfigDeviceDashboard extends LitElement {
outputDevices = outputDevices.filter((device) =>
device.config_entries.includes(value)
);
startLength = outputDevices.length;
const configEntry = entries.find((entry) => entry.entry_id === value);
if (configEntry) {
filterDomains.push(configEntry.domain);
@@ -175,10 +152,6 @@ export class HaConfigDeviceDashboard extends LitElement {
}
});
if (!showDisabled) {
outputDevices = outputDevices.filter((device) => !device.disabled_by);
}
outputDevices = outputDevices.map((device) => {
return {
...device,
@@ -189,7 +162,9 @@ export class HaConfigDeviceDashboard extends LitElement {
),
model: device.model || "<unknown>",
manufacturer: device.manufacturer || "<unknown>",
area: device.area_id ? areaLookup[device.area_id].name : undefined,
area: device.area_id
? areaLookup[device.area_id].name
: this.hass.localize("ui.panel.config.devices.data_table.no_area"),
integration: device.config_entries.length
? device.config_entries
.filter((entId) => entId in entryLookup)
@@ -207,19 +182,16 @@ export class HaConfigDeviceDashboard extends LitElement {
};
});
this._numHiddenDevices = startLength - outputDevices.length;
return { devicesOutput: outputDevices, filteredDomains: filterDomains };
}
);
private _columns = memoizeOne(
(narrow: boolean, showDisabled: boolean): DataTableColumnContainer => {
(narrow: boolean): DataTableColumnContainer => {
const columns: DataTableColumnContainer = narrow
? {
name: {
title: this.hass.localize(
"ui.panel.config.devices.data_table.device"
),
title: "Device",
sortable: true,
filterable: true,
direction: "asc",
@@ -305,24 +277,6 @@ export class HaConfigDeviceDashboard extends LitElement {
: html` - `;
},
};
if (showDisabled) {
columns.disabled_by = {
title: "",
type: "icon",
template: (disabled_by) =>
disabled_by
? html`<div
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize("ui.panel.config.devices.disabled")}
</paper-tooltip>
</div>`
: "",
};
}
return columns;
}
);
@@ -344,119 +298,9 @@ export class HaConfigDeviceDashboard extends LitElement {
this.entities,
this.areas,
this._searchParms,
this._showDisabled,
this.hass.localize
);
const includeZHAFab = filteredDomains.includes("zha");
const activeFilters = this._activeFilters(
this.entries,
this._searchParms,
this.hass.localize
);
const headerToolbar = html`
<search-input
no-label-float
no-underline
@value-changed=${this._handleSearchChange}
.filter=${this._filter}
.label=${this.hass.localize("ui.panel.config.devices.picker.search")}
></search-input
>${activeFilters
? html`<div class="active-filters">
${this.narrow
? html` <div>
<ha-icon icon="hass:filter-variant"></ha-icon>
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.filtering.filtering_by"
)}
${activeFilters.join(", ")}
${this._numHiddenDevices
? "(" +
this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
) +
")"
: ""}
</paper-tooltip>
</div>`
: `${this.hass.localize(
"ui.panel.config.filtering.filtering_by"
)} ${activeFilters.join(", ")}
${
this._numHiddenDevices
? "(" +
this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
) +
")"
: ""
}
`}
<mwc-button @click=${this._clearFilter}
>${this.hass.localize(
"ui.panel.config.filtering.clear"
)}</mwc-button
>
</div>`
: ""}
${this._numHiddenDevices && !activeFilters
? html`<div class="active-filters">
${this.narrow
? html` <div>
<ha-icon icon="hass:filter-variant"></ha-icon>
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
)}
</paper-tooltip>
</div>`
: `${this.hass.localize(
"ui.panel.config.devices.picker.filter.hidden_devices",
"number",
this._numHiddenDevices
)}`}
<mwc-button @click=${this._showAll}
>${this.hass.localize(
"ui.panel.config.devices.picker.filter.show_all"
)}</mwc-button
>
</div>`
: ""}
<ha-button-menu corner="BOTTOM_START" multi>
<mwc-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.config.devices.picker.filter.filter"
)}
.title=${this.hass!.localize(
"ui.panel.config.devices.picker.filter.filter"
)}
>
<ha-svg-icon .path=${mdiFilterVariant}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item
@request-selected="${this._showDisabledChanged}"
graphic="control"
.selected=${this._showDisabled}
>
<ha-checkbox
slot="graphic"
.checked=${this._showDisabled}
></ha-checkbox>
${this.hass!.localize(
"ui.panel.config.devices.picker.filter.show_disabled"
)}
</mwc-list-item>
</ha-button-menu>
`;
return html`
<hass-tabs-subpage-data-table
@@ -467,9 +311,13 @@ export class HaConfigDeviceDashboard extends LitElement {
: "/config"}
.tabs=${configSections.integrations}
.route=${this.route}
.columns=${this._columns(this.narrow, this._showDisabled)}
.columns=${this._columns(this.narrow)}
.data=${devicesOutput}
.filter=${this._filter}
.activeFilters=${this._activeFilters(
this.entries,
this._searchParms,
this.hass.localize
)}
@row-click=${this._handleRowClicked}
clickable
.hasFab=${includeZHAFab}
@@ -485,15 +333,6 @@ export class HaConfigDeviceDashboard extends LitElement {
</ha-fab>
</a>`
: html``}
<div
class=${classMap({
"search-toolbar": this.narrow,
"table-header": !this.narrow,
})}
slot="header"
>
${headerToolbar}
</div>
</hass-tabs-subpage-data-table>
`;
}
@@ -524,136 +363,6 @@ export class HaConfigDeviceDashboard extends LitElement {
const deviceId = ev.detail.id;
navigate(this, `/config/devices/device/${deviceId}`);
}
private _showDisabledChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showDisabled = ev.detail.selected;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
private _clearFilter() {
navigate(this, window.location.pathname, true);
}
private _showAll() {
this._showDisabled = true;
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
h2 {
margin-top: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
p {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
}
ha-data-table {
width: 100%;
--data-table-border-width: 0;
}
:host(:not([narrow])) ha-data-table {
height: calc(100vh - 1px - var(--header-height));
display: block;
}
ha-button-menu {
margin-right: 8px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
}
search-input {
margin-left: 16px;
flex-grow: 1;
position: relative;
top: 2px;
}
.search-toolbar search-input {
margin-left: 8px;
top: 1px;
}
.search-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
}
.search-toolbar ha-button-menu {
position: static;
}
.selected-txt {
font-weight: bold;
padding-left: 16px;
}
.table-header .selected-txt {
margin-top: 20px;
}
.search-toolbar .selected-txt {
font-size: 16px;
}
.header-btns > mwc-button,
.header-btns > ha-icon-button {
margin: 8px;
}
.active-filters {
color: var(--primary-text-color);
position: relative;
display: flex;
align-items: center;
padding: 2px 2px 2px 8px;
margin-left: 4px;
font-size: 14px;
}
.active-filters ha-icon {
color: var(--primary-color);
}
.active-filters mwc-button {
margin-left: 8px;
}
.active-filters::before {
background-color: var(--primary-color);
opacity: 0.12;
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
}
`,
];
}
}
declare global {

View File

@@ -111,19 +111,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
return html`
${!stateObj
? html`
<div class="container warning">
<div class="container">
${this.hass!.localize(
"ui.dialogs.entity_registry.editor.unavailable"
)}
${this._device?.disabled_by
? html`<br />${this.hass!.localize(
"ui.dialogs.entity_registry.editor.device_disabled"
)}<br /><mwc-button @click=${this._openDeviceSettings}>
${this.hass!.localize(
"ui.dialogs.entity_registry.editor.open_device_settings"
)}
</mwc-button>`
: ""}
</div>
`
: ""}
@@ -170,7 +161,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
<div class="row">
<ha-switch
.checked=${!this._disabledBy}
.disabled=${this._device?.disabled_by}
@change=${this._disabledByChanged}
>
</ha-switch>

View File

@@ -189,11 +189,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
? (name, entity: any) =>
html`
${name}<br />
<div class="secondary">
${entity.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) ||
entity.platform}
</div>
${entity.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) ||
entity.platform}
`
: undefined,
},

View File

@@ -5,8 +5,6 @@ import { classMap } from "lit-html/directives/class-map";
export class HaConfigSection extends LitElement {
@property() public isWide = false;
@property({ type: Boolean }) public vertical = false;
protected render() {
return html`
<div
@@ -18,8 +16,8 @@ export class HaConfigSection extends LitElement {
<div
class="together layout ${classMap({
narrow: !this.isWide,
vertical: this.vertical || !this.isWide,
horizontal: !this.vertical && this.isWide,
vertical: !this.isWide,
horizontal: this.isWide,
})}"
>
<div class="intro"><slot name="introduction"></slot></div>

View File

@@ -148,7 +148,7 @@ export class HaConfigHelpers extends LitElement {
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.helpers}
.tabs=${configSections.automation}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._stateItems)}
@row-click=${this._openEditDialog}

View File

@@ -197,7 +197,7 @@ class SystemHealthCard extends LitElement {
});
}
private async _copyInfo(ev: CustomEvent<ActionDetail>): Promise<void> {
private _copyInfo(ev: CustomEvent<ActionDetail>): void {
const github = ev.detail.index === 1;
let haContent: string | undefined;
const domainParts: string[] = [];
@@ -250,15 +250,13 @@ class SystemHealthCard extends LitElement {
}
}
await copyToClipboard(
copyToClipboard(
`${github ? "## " : ""}System Health\n${haContent}\n\n${domainParts.join(
"\n\n"
)}`
);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
showToast(this, { message: this.hass.localize("ui.common.copied") });
}
static get styles(): CSSResult {

View File

@@ -141,15 +141,11 @@ class OZWNodeDashboard extends LitElement {
${this._metadata.metadata.ResetHelp}
</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>
`
: ``}
<ha-card class="content" header="WakeUp">
<div class="card-content">
${this._metadata.metadata.WakeupHelp}
</div>
</ha-card>
`
: ``}
`
@@ -203,10 +199,6 @@ class OZWNodeDashboard extends LitElement {
margin-top: 24px;
}
.content:last-child {
margin-bottom: 24px;
}
.sectionHeader {
position: relative;
padding-right: 40px;

View File

@@ -49,17 +49,19 @@ class ZHADevicePairingStatusCard extends LitElement {
class="discovered ${classMap({
initialized: this.device.pairing_status === INITIALIZED,
})}"
><div class="header">
<h4>
><div
class="header"
>
<h1>
${this.hass!.localize(
`ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}`
)}
</h4>
<h1>
</h1>
<h4>
${this.hass!.localize(
`ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}_status_text`
)}
</h1>
</h4>
</div>
<div class="card-content">
${[INTERVIEW_COMPLETE, CONFIGURED].includes(

View File

@@ -15,12 +15,6 @@ 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 {
@@ -34,21 +28,9 @@ 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) {
@@ -109,27 +91,6 @@ 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>
`;
@@ -140,18 +101,15 @@ 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[]) {
this._nodes = [];
const nodes: Node[] = [];
const edges: Edge[] = [];
devices.forEach((device) => {
this._nodes.push({
nodes.push({
id: device.ieee,
label: this._buildLabel(device),
shape: this._getShape(device),
@@ -179,7 +137,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
}
});
this._network?.setData({ nodes: this._nodes, edges: edges });
this._network?.setData({ nodes: nodes, edges: edges });
}
private _getLQI(lqi: number): EdgeOptions["color"] {
@@ -223,7 +181,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>${formatAsPaddedHex(device.nwk)}`;
label += `\n<b>NWK: </b>${device.nwk}`;
}
if (device.manufacturer != null && device.model != null) {
label += `\n<b>Device: </b>${device.manufacturer} ${device.model}`;
@@ -236,56 +194,6 @@ 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`
@@ -300,30 +208,6 @@ 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;
}
`,
];
}

View File

@@ -2,6 +2,7 @@ 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,
@@ -9,10 +10,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 {
@@ -26,7 +27,6 @@ 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,6 +35,8 @@ 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;
@@ -81,6 +83,15 @@ 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>
@@ -151,15 +162,23 @@ class DialogSystemLogDetail extends LitElement {
}
}
private async _copyLog(): Promise<void> {
private _copyLog(): void {
const copyElement = this.shadowRoot?.querySelector(
".contents"
) as HTMLElement;
await copyToClipboard(copyElement.innerText);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
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);
}
static get styles(): CSSResult[] {

View File

@@ -68,8 +68,6 @@ class DialogPersonDetail extends LitElement {
@internalProperty() private _submitting = false;
@internalProperty() private _personExists = false;
private _deviceTrackersAvailable = memoizeOne((hass) => {
return Object.keys(hass.states).some(
(entityId) =>
@@ -81,7 +79,6 @@ class DialogPersonDetail extends LitElement {
this._params = params;
this._error = undefined;
if (this._params.entry) {
this._personExists = true;
this._name = this._params.entry.name || "";
this._userId = this._params.entry.user_id || undefined;
this._deviceTrackers = this._params.entry.device_trackers || [];
@@ -91,7 +88,6 @@ class DialogPersonDetail extends LitElement {
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
} else {
this._personExists = false;
this._name = "";
this._userId = undefined;
this._user = undefined;
@@ -402,7 +398,6 @@ class DialogPersonDetail extends LitElement {
await this._params!.updateEntry(values);
} else {
await this._params!.createEntry(values);
this._personExists = true;
}
this._params = undefined;
} catch (err) {
@@ -427,14 +422,6 @@ class DialogPersonDetail extends LitElement {
}
private _close(): void {
// If we do not have a person ID yet (= person creation dialog was just cancelled), but
// we already created a user ID for it, delete it now to not have it "free floating".
if (!this._personExists && this._userId) {
deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
}
this._params = undefined;
}

View File

@@ -35,7 +35,6 @@ 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,
@@ -544,12 +543,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
return this._config;
}
private async _copyYaml(): Promise<void> {
private async _copyYaml() {
if (this._editor?.yaml) {
await copyToClipboard(this._editor.yaml);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
navigator.clipboard.writeText(this._editor.yaml);
}
}

View File

@@ -84,7 +84,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
${tag.last_scanned_datetime
? html`<ha-relative-time
.hass=${this.hass}
.datetime=${tag.last_scanned_datetime}
.datetimeObj=${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}
.datetime=${last_scanned_datetime}
.datetimeObj=${last_scanned_datetime}
></ha-relative-time>`
: this.hass.localize("ui.panel.config.tags.never_scanned")}
`,

View File

@@ -241,7 +241,7 @@ export class DialogAddUser extends LitElement {
user = userResponse.user;
} catch (err) {
this._loading = false;
this._error = err.message;
this._error = err.code;
return;
}
@@ -255,7 +255,7 @@ export class DialogAddUser extends LitElement {
} catch (err) {
await deleteUser(this.hass, user.id);
this._loading = false;
this._error = err.message;
this._error = err.code;
return;
}

View File

@@ -13,7 +13,6 @@ import {
} from "lit-element";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-help-tooltip";
import "../../../components/ha-formfield";
import "../../../components/ha-switch";
import { adminChangePassword } from "../../../data/auth";
@@ -38,8 +37,6 @@ class DialogUserDetail extends LitElement {
@internalProperty() private _isAdmin?: boolean;
@internalProperty() private _isActive?: boolean;
@internalProperty() private _error?: string;
@internalProperty() private _params?: UserDetailDialogParams;
@@ -51,7 +48,6 @@ class DialogUserDetail extends LitElement {
this._error = undefined;
this._name = params.entry.name || "";
this._isAdmin = params.entry.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._isActive = params.entry.is_active;
await this.updateComplete;
}
@@ -95,6 +91,15 @@ class DialogUserDetail extends LitElement {
</span>
`
: ""}
${user.is_active
? html`
<span class="state"
>${this.hass.localize(
"ui.panel.config.users.editor.active"
)}</span
>
`
: ""}
</div>
<div class="form">
<paper-input
@@ -105,21 +110,17 @@ class DialogUserDetail extends LitElement {
"ui.panel.config.users.editor.name"
)}"
></paper-input>
<div class="row">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.users.editor.admin"
)}
.dir=${computeRTLDirection(this.hass)}
<ha-formfield
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-formfield>
</div>
</ha-switch>
</ha-formfield>
${!this._isAdmin
? html`
<br />
@@ -128,27 +129,6 @@ class DialogUserDetail extends LitElement {
)}
`
: ""}
<div class="row">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.users.editor.active"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isActive}
@change=${this._activeChanged}
>
</ha-switch>
</ha-formfield>
<ha-help-tooltip
.label=${this.hass.localize(
"ui.panel.config.users.editor.active_tooltip"
)}
>
</ha-help-tooltip>
</div>
</div>
</div>
@@ -212,16 +192,11 @@ class DialogUserDetail extends LitElement {
this._isAdmin = ev.target.checked;
}
private async _activeChanged(ev): Promise<void> {
this._isActive = ev.target.checked;
}
private async _updateEntry() {
this._submitting = true;
try {
await this._params!.updateEntry({
name: this._name.trim(),
is_active: this._isActive,
group_ids: [
this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER,
],
@@ -318,13 +293,8 @@ class DialogUserDetail extends LitElement {
.state:not(:first-child) {
margin-left: 8px;
}
.row {
display: flex;
padding: 8px 0;
}
ha-help-tooltip {
margin-left: 4px;
position: relative;
ha-switch {
margin-top: 8px;
}
`,
];

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