mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 10:19:25 +00:00
Compare commits
1 Commits
editor-hig
...
20220523.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ef92ed927e |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
<!--
|
||||
Provide details about the versions you are using, which helps us reproducing
|
||||
and finding the issue quicker. Version information is found in the
|
||||
Home Assistant frontend: Settings -> About.
|
||||
Home Assistant frontend: Configuration -> Info.
|
||||
|
||||
Browser version and operating system is important! Please try to replicate
|
||||
your issue in a different browser and be sure to include your findings.
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -64,7 +64,7 @@ body:
|
||||
label: What version of Home Assistant Core has the issue?
|
||||
placeholder: core-
|
||||
description: >
|
||||
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
|
||||
Can be found in the Configuration panel -> Info.
|
||||
- type: input
|
||||
attributes:
|
||||
label: What was the last working version of Home Assistant Core?
|
||||
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -181,7 +181,7 @@
|
||||
{
|
||||
"label": "Run HA Core for Supervisor in devcontainer",
|
||||
"type": "shell",
|
||||
"command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core",
|
||||
"command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core",
|
||||
"isBackground": true,
|
||||
"group": {
|
||||
"kind": "build",
|
||||
|
@@ -26,8 +26,8 @@ module.exports = {
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
||||
.match(/version\W+=\W"(\d{8}\.\d)"/);
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
|
||||
.match(/version\W+=\W(\d{8}\.\d)/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
|
@@ -1,9 +0,0 @@
|
||||
# These redirects are handled by Netlify
|
||||
#
|
||||
|
||||
# Some custom cards are not prefixing the instance URL when fetching data
|
||||
# and can end up fetching the data from the Cast domain instead of HA.
|
||||
# This will make sure that some common ones are replaced with a placeholder.
|
||||
/api/camera_proxy/* /images/google-nest-hub.png
|
||||
/api/camera_proxy_stream/* /images/google-nest-hub.png
|
||||
/api/media_player_proxy/* /images/google-nest-hub.png
|
@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
type: "state-icon",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "group.downstairs_lights",
|
||||
},
|
||||
service: "homeassistant.toggle",
|
||||
|
@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "script.air_cleaner_quiet",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "script.air_cleaner_auto",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "script.air_cleaner_turbo",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "script.ac_off",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "AC",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "script.ac_on",
|
||||
},
|
||||
service: "script.turn_on",
|
||||
@@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "scene.morning_lights",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "scene.morning_lights",
|
||||
},
|
||||
service: "scene.turn_on",
|
||||
@@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "scene.movie_time",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "scene.movie_time",
|
||||
},
|
||||
service: "scene.turn_on",
|
||||
@@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "light.downstairs_lights",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "light.downstairs_lights",
|
||||
},
|
||||
service: "light.toggle",
|
||||
@@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
entity: "light.upstairs_lights",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
data: {
|
||||
service_data: {
|
||||
entity_id: "light.upstairs_lights",
|
||||
},
|
||||
service: "light.toggle",
|
||||
|
@@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
data: {},
|
||||
service_data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_4"],
|
||||
},
|
||||
@@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
data: {},
|
||||
service_data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_2"],
|
||||
},
|
||||
@@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
data: {},
|
||||
service_data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_3"],
|
||||
},
|
||||
@@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = {
|
||||
params: {
|
||||
domain: "input_boolean",
|
||||
service: "toggle",
|
||||
data: {},
|
||||
service_data: {},
|
||||
target: {
|
||||
entity_id: ["input_boolean.toggle_4"],
|
||||
},
|
||||
|
@@ -249,7 +249,7 @@ const CONFIGS = [
|
||||
name: Bed light
|
||||
action_name: Toggle light
|
||||
service: light.toggle
|
||||
data:
|
||||
service_data:
|
||||
entity_id: light.bed_light
|
||||
- type: section
|
||||
label: Links
|
||||
|
@@ -199,7 +199,7 @@ const CONFIGS = [
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
service_data:
|
||||
entity_id: light.ceiling_lights
|
||||
- entity: sun.sun
|
||||
name: Regular
|
||||
|
@@ -40,7 +40,7 @@ const CONFIGS = [
|
||||
left: 90%
|
||||
padding: 0px
|
||||
service: light.turn_off
|
||||
data:
|
||||
service_data:
|
||||
entity_id: group.all_lights
|
||||
- type: icon
|
||||
icon: mdi:cctv
|
||||
@@ -88,7 +88,7 @@ const CONFIGS = [
|
||||
left: 90%
|
||||
padding: 0px
|
||||
service: light.turn_off
|
||||
data:
|
||||
service_data:
|
||||
entity_id: group.all_lights
|
||||
- type: icon
|
||||
icon: mdi:cctv
|
||||
|
@@ -89,8 +89,8 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.4.1",
|
||||
"@thomasloven/round-slider": "0.5.4",
|
||||
"@vaadin/combo-box": "^23.0.10",
|
||||
"@vaadin/vaadin-themable-mixin": "^23.0.10",
|
||||
"@vaadin/combo-box": "^22.0.4",
|
||||
"@vaadin/vaadin-themable-mixin": "^22.0.4",
|
||||
"@vibrant/color": "^3.2.1-alpha.1",
|
||||
"@vibrant/core": "^3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||
@@ -108,7 +108,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.1.5",
|
||||
"home-assistant-js-websocket": "^7.1.0",
|
||||
"home-assistant-js-websocket": "^7.0.3",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
@@ -1,30 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=62.3", "wheel~=0.37.1"]
|
||||
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220526.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
||||
]
|
||||
requires-python = ">=3.4.0"
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||
|
||||
[tool.setuptools]
|
||||
platforms = ["any"]
|
||||
zip-safe = false
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["hass_frontend*"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = true
|
||||
strict = true
|
||||
|
@@ -50,14 +50,14 @@ async function main(args) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setup = fs.readFileSync("pyproject.toml", "utf8");
|
||||
const version = setup.match(/version\W+=\W"(\d{8}\.\d)"/)[1];
|
||||
const setup = fs.readFileSync("setup.cfg", "utf8");
|
||||
const version = setup.match(/\d{8}\.\d+/)[0];
|
||||
const newVersion = method(version);
|
||||
|
||||
console.log("Current version:", version);
|
||||
console.log("New version:", newVersion);
|
||||
|
||||
fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
|
||||
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
|
26
setup.cfg
Normal file
26
setup.cfg
Normal file
@@ -0,0 +1,26 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220523.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
platforms = any
|
||||
description = The Home Assistant frontend
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/home-assistant/frontend
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
zip_safe = False
|
||||
include_package_data = True
|
||||
python_requires = >= 3.4.0
|
||||
|
||||
[options.packages.find]
|
||||
include =
|
||||
hass_frontend*
|
||||
|
||||
[mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = True
|
||||
strict = True
|
@@ -1,11 +1,6 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
|
||||
export const computeActiveState = (stateObj: HassEntity): string => {
|
||||
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
|
||||
return stateObj.state;
|
||||
}
|
||||
|
||||
const domain = stateObj.entity_id.split(".")[0];
|
||||
let state = stateObj.state;
|
||||
|
||||
|
@@ -29,8 +29,7 @@ import {
|
||||
mdiWeatherNight,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UpdateEntity, updateIsInstalling } from "../../data/update";
|
||||
import { weatherIcon } from "../../data/weather";
|
||||
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||
/**
|
||||
* Return the icon to be used for a domain.
|
||||
*
|
||||
@@ -102,15 +101,6 @@ export const domainIconWithoutDefault = (
|
||||
? mdiCheckCircleOutline
|
||||
: mdiCloseCircleOutline;
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "lock":
|
||||
switch (compareState) {
|
||||
case "unlocked":
|
||||
@@ -148,6 +138,15 @@ export const domainIconWithoutDefault = (
|
||||
break;
|
||||
}
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "sun":
|
||||
return stateObj?.state === "above_horizon"
|
||||
? FIXED_DOMAIN_ICONS[domain]
|
||||
@@ -159,9 +158,6 @@ export const domainIconWithoutDefault = (
|
||||
? mdiPackageDown
|
||||
: mdiPackageUp
|
||||
: mdiPackage;
|
||||
|
||||
case "weather":
|
||||
return weatherIcon(stateObj?.state);
|
||||
}
|
||||
|
||||
if (domain in FIXED_DOMAIN_ICONS) {
|
||||
|
@@ -31,7 +31,6 @@ const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
|
||||
<span>${item.friendly_name}</span>
|
||||
<span slot="secondary">${item.entity_id}</span>
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
@@ -2,7 +2,12 @@ import type { Button } from "@material/mwc-button";
|
||||
import "@material/mwc-menu";
|
||||
import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
queryAssignedElements,
|
||||
} from "lit/decorators";
|
||||
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
|
||||
import type { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
@@ -28,6 +33,12 @@ export class HaButtonMenu extends LitElement {
|
||||
|
||||
@query("mwc-menu", true) private _menu?: Menu;
|
||||
|
||||
@queryAssignedElements({
|
||||
slot: "trigger",
|
||||
selector: "ha-icon-button, mwc-button",
|
||||
})
|
||||
private _triggerButton!: Array<HaIconButton | Button>;
|
||||
|
||||
public get items() {
|
||||
return this._menu?.items;
|
||||
}
|
||||
@@ -40,14 +51,14 @@ export class HaButtonMenu extends LitElement {
|
||||
if (this._menu?.open) {
|
||||
this._menu.focusItemAtIndex(0);
|
||||
} else {
|
||||
this._triggerButton?.focus();
|
||||
this._triggerButton[0]?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
|
||||
<slot name="trigger"></slot>
|
||||
</div>
|
||||
<mwc-menu
|
||||
.corner=${this.corner}
|
||||
@@ -86,18 +97,6 @@ export class HaButtonMenu extends LitElement {
|
||||
this._menu!.show();
|
||||
}
|
||||
|
||||
private get _triggerButton() {
|
||||
return this.querySelector(
|
||||
'ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]'
|
||||
) as HaIconButton | Button | null;
|
||||
}
|
||||
|
||||
private _setTriggerAria() {
|
||||
if (this._triggerButton) {
|
||||
this._triggerButton.ariaHasPopup = "menu";
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@@ -66,13 +66,9 @@ export class HaChip extends LitElement {
|
||||
line-height: 14px;
|
||||
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
||||
}
|
||||
.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
|
||||
.mdc-chip.no-text
|
||||
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
|
||||
margin-right: -4px;
|
||||
margin-inline-start: -4px;
|
||||
margin-inline-end: 4px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
span[role="gridcell"] {
|
||||
|
@@ -3,7 +3,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatNumber } from "../common/number/format_number";
|
||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||
import { UNAVAILABLE_STATES } from "../data/entity";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-climate-state")
|
||||
@@ -16,22 +15,22 @@ class HaClimateState extends LitElement {
|
||||
const currentStatus = this._computeCurrentStatus();
|
||||
|
||||
return html`<div class="target">
|
||||
${!UNAVAILABLE_STATES.includes(this.stateObj.state)
|
||||
${this.stateObj.state !== "unknown"
|
||||
? html`<span class="state-label">
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||
? html`-
|
||||
${this.hass.localize(
|
||||
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||
) || this.stateObj.attributes.preset_mode}`
|
||||
: ""}
|
||||
</span>
|
||||
<div class="unit">${this._computeTarget()}</div>`
|
||||
: this._localizeState()}
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||
? html`-
|
||||
${this.hass.localize(
|
||||
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||
) || this.stateObj.attributes.preset_mode}`
|
||||
: ""}
|
||||
</span>`
|
||||
: ""}
|
||||
<div class="unit">${this._computeTarget()}</div>
|
||||
</div>
|
||||
|
||||
${currentStatus && !UNAVAILABLE_STATES.includes(this.stateObj.state)
|
||||
${currentStatus
|
||||
? html`<div class="current">
|
||||
${this.hass.localize("ui.card.climate.currently")}:
|
||||
<div class="unit">${currentStatus}</div>
|
||||
@@ -109,10 +108,6 @@ class HaClimateState extends LitElement {
|
||||
}
|
||||
|
||||
private _localizeState(): string {
|
||||
if (UNAVAILABLE_STATES.includes(this.stateObj.state)) {
|
||||
return this.hass.localize(`state.default.${this.stateObj.state}`);
|
||||
}
|
||||
|
||||
const stateString = this.hass.localize(
|
||||
`component.climate.state._.${this.stateObj.state}`
|
||||
);
|
||||
|
@@ -1,17 +1,13 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
|
||||
import type {
|
||||
ComboBoxLight,
|
||||
ComboBoxLightFilterChangedEvent,
|
||||
ComboBoxLightOpenedChangedEvent,
|
||||
ComboBoxLightValueChangedEvent,
|
||||
} from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-textfield";
|
||||
@@ -100,8 +96,6 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
|
||||
|
||||
private _overlayMutationObserver?: MutationObserver;
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
this._comboBox?.open();
|
||||
@@ -114,14 +108,6 @@ export class HaComboBox extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._overlayMutationObserver) {
|
||||
this._overlayMutationObserver.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public get selectedItem() {
|
||||
return this._comboBox.selectedItem;
|
||||
}
|
||||
@@ -207,64 +193,21 @@ export class HaComboBox extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) {
|
||||
const opened = ev.detail.value;
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
// delay this so we can handle click event before setting _opened
|
||||
setTimeout(() => {
|
||||
this._opened = opened;
|
||||
this._opened = ev.detail.value;
|
||||
}, 0);
|
||||
// @ts-ignore
|
||||
fireEvent(this, ev.type, ev.detail);
|
||||
|
||||
if (
|
||||
opened &&
|
||||
"MutationObserver" in window &&
|
||||
!this._overlayMutationObserver
|
||||
) {
|
||||
const overlay = document.querySelector<HTMLElement>(
|
||||
"vaadin-combo-box-overlay"
|
||||
);
|
||||
|
||||
if (!overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._overlayMutationObserver = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "inert"
|
||||
) {
|
||||
this._overlayMutationObserver?.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
// @ts-expect-error
|
||||
overlay.inert = false;
|
||||
} else if (mutation.type === "childList") {
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (node.nodeName === "VAADIN-COMBO-BOX-OVERLAY") {
|
||||
this._overlayMutationObserver?.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._overlayMutationObserver.observe(overlay, {
|
||||
attributes: true,
|
||||
});
|
||||
this._overlayMutationObserver.observe(document.body, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _filterChanged(ev: ComboBoxLightFilterChangedEvent) {
|
||||
private _filterChanged(ev: PolymerChangedEvent<string>) {
|
||||
// @ts-ignore
|
||||
fireEvent(this, ev.type, ev.detail, { composed: false });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ComboBoxLightValueChangedEvent) {
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
|
@@ -28,15 +28,10 @@ export class HaFormfield extends FormfieldBase {
|
||||
css`
|
||||
:host(:not([alignEnd])) ::slotted(ha-switch) {
|
||||
margin-right: 10px;
|
||||
margin-inline-end: 10px;
|
||||
margin-inline-start: inline;
|
||||
}
|
||||
.mdc-form-field > label {
|
||||
direction: var(--direction);
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: auto;
|
||||
padding-inline-start: 4px;
|
||||
padding-inline-end: 0;
|
||||
:host([dir="rtl"]:not([alignEnd])) ::slotted(ha-switch) {
|
||||
margin-left: 10px;
|
||||
margin-right: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -2,7 +2,6 @@ import "@material/mwc-icon-button";
|
||||
import type { IconButton } from "@material/mwc-icon-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-button")
|
||||
@@ -13,12 +12,7 @@ export class HaIconButton extends LitElement {
|
||||
@property({ type: String }) path?: string;
|
||||
|
||||
// Label that is used for ARIA support and as tooltip
|
||||
@property({ type: String }) label?: string;
|
||||
|
||||
// These should always be set as properties, not attributes,
|
||||
// so that only the <button> element gets the attribute
|
||||
@property({ type: String, attribute: "aria-haspopup" })
|
||||
override ariaHasPopup!: IconButton["ariaHasPopup"];
|
||||
@property({ type: String }) label = "";
|
||||
|
||||
@property({ type: Boolean }) hideTitle = false;
|
||||
|
||||
@@ -34,11 +28,11 @@ export class HaIconButton extends LitElement {
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
// Note: `ariaLabel` required despite the `mwc-icon-button` docs saying `label` should be enough
|
||||
return html`
|
||||
<mwc-icon-button
|
||||
aria-label=${ifDefined(this.label)}
|
||||
title=${ifDefined(this.hideTitle ? undefined : this.label)}
|
||||
aria-haspopup=${ifDefined(this.ariaHasPopup)}
|
||||
.ariaLabel=${this.label}
|
||||
.title=${this.hideTitle ? "" : this.label}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.path
|
||||
|
@@ -1,24 +1,15 @@
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import {
|
||||
EntitySources,
|
||||
fetchEntitySourcesWithCache,
|
||||
} from "../../data/entity_sources";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { AreaSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-area-picker";
|
||||
import "../ha-areas-picker";
|
||||
|
||||
@customElement("ha-selector-area")
|
||||
export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
export class HaAreaSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: AreaSelector;
|
||||
@@ -29,44 +20,29 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
@state() public _configEntries?: ConfigEntry[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.device_id !== null);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (
|
||||
changedProperties.has("selector") &&
|
||||
(this.selector.area.device?.integration ||
|
||||
this.selector.area.entity?.integration) &&
|
||||
!this._entitySources
|
||||
) {
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (
|
||||
oldSelector !== this.selector &&
|
||||
this.selector.area.device?.integration
|
||||
) {
|
||||
getConfigEntries(this.hass, {
|
||||
domain: this.selector.area.device.integration,
|
||||
}).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
(this.selector.area.device?.integration ||
|
||||
this.selector.area.entity?.integration) &&
|
||||
!this._entitySources
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (!this.selector.area.multiple) {
|
||||
return html`
|
||||
<ha-area-picker
|
||||
@@ -111,62 +87,39 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: EntityRegistryEntry): boolean => {
|
||||
const filterIntegration = this.selector.area.entity?.integration;
|
||||
if (
|
||||
filterIntegration &&
|
||||
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||
if (!this.selector.area.device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const {
|
||||
manufacturer: filterManufacturer,
|
||||
model: filterModel,
|
||||
integration: filterIntegration,
|
||||
} = this.selector.area.device;
|
||||
|
||||
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
|
||||
return false;
|
||||
}
|
||||
if (filterModel && device.model !== filterModel) {
|
||||
return false;
|
||||
}
|
||||
if (filterIntegration && this._entitySources && this._entities) {
|
||||
const deviceIntegrations = this._deviceIntegrations(
|
||||
this._entitySources,
|
||||
this._entities
|
||||
);
|
||||
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
|
||||
if (this.selector.area.entity?.integration) {
|
||||
if (entity.platform !== this.selector.area.entity.integration) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private _deviceIntegrations = memoizeOne(
|
||||
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
|
||||
const deviceIntegrations: Record<string, string[]> = {};
|
||||
|
||||
for (const entity of entities) {
|
||||
const source = entitySources[entity.entity_id];
|
||||
if (!source?.domain) {
|
||||
continue;
|
||||
}
|
||||
if (!deviceIntegrations[entity.device_id!]) {
|
||||
deviceIntegrations[entity.device_id!] = [];
|
||||
}
|
||||
deviceIntegrations[entity.device_id!].push(source.domain);
|
||||
}
|
||||
return deviceIntegrations;
|
||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||
if (
|
||||
this.selector.area.device?.manufacturer &&
|
||||
device.manufacturer !== this.selector.area.device.manufacturer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
if (
|
||||
this.selector.area.device?.model &&
|
||||
device.model !== this.selector.area.device.model
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.selector.area.device?.integration) {
|
||||
if (
|
||||
this._configEntries &&
|
||||
!this._configEntries.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,33 +1,18 @@
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ConfigEntry } from "../../data/config_entries";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import {
|
||||
EntitySources,
|
||||
fetchEntitySourcesWithCache,
|
||||
} from "../../data/entity_sources";
|
||||
import type { DeviceSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../device/ha-device-picker";
|
||||
import "../device/ha-devices-picker";
|
||||
|
||||
@customElement("ha-selector-device")
|
||||
export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
||||
export class HaDeviceSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: DeviceSelector;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -40,32 +25,20 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.device_id !== null);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties): void {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
changedProperties.has("selector") &&
|
||||
this.selector.device.integration &&
|
||||
!this._entitySources
|
||||
) {
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (oldSelector !== this.selector && this.selector.device?.integration) {
|
||||
getConfigEntries(this.hass, {
|
||||
domain: this.selector.device.integration,
|
||||
}).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this.selector.device.integration && !this._entitySources) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (!this.selector.device.multiple) {
|
||||
return html`
|
||||
<ha-device-picker
|
||||
@@ -107,48 +80,30 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||
const {
|
||||
manufacturer: filterManufacturer,
|
||||
model: filterModel,
|
||||
integration: filterIntegration,
|
||||
} = this.selector.device;
|
||||
|
||||
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
|
||||
if (
|
||||
this.selector.device?.manufacturer &&
|
||||
device.manufacturer !== this.selector.device.manufacturer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (filterModel && device.model !== filterModel) {
|
||||
if (
|
||||
this.selector.device?.model &&
|
||||
device.model !== this.selector.device.model
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (filterIntegration && this._entitySources && this._entities) {
|
||||
const deviceIntegrations = this._deviceIntegrations(
|
||||
this._entitySources,
|
||||
this._entities
|
||||
);
|
||||
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
|
||||
if (this.selector.device?.integration) {
|
||||
if (
|
||||
this._configEntries &&
|
||||
!this._configEntries.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private _deviceIntegrations = memoizeOne(
|
||||
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
|
||||
const deviceIntegrations: Record<string, string[]> = {};
|
||||
|
||||
for (const entity of entities) {
|
||||
const source = entitySources[entity.entity_id];
|
||||
if (!source?.domain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!deviceIntegrations[entity.device_id!]) {
|
||||
deviceIntegrations[entity.device_id!] = [];
|
||||
}
|
||||
deviceIntegrations[entity.device_id!].push(source.domain);
|
||||
}
|
||||
return deviceIntegrations;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@material/mwc-formfield/mwc-formfield";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
@@ -46,14 +47,14 @@ export class HaSelectSelector extends LitElement {
|
||||
${this.label}
|
||||
${options.map(
|
||||
(item: SelectOption) => html`
|
||||
<ha-formfield .label=${item.label}>
|
||||
<mwc-formfield .label=${item.label}>
|
||||
<ha-radio
|
||||
.checked=${item.value === this.value}
|
||||
.value=${item.value}
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._valueChanged}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</mwc-formfield>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
|
@@ -287,7 +287,9 @@ export class HaServiceControl extends LitElement {
|
||||
${shouldRenderServiceDataYaml
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize("ui.components.service-control.data")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.service-control.service_data"
|
||||
)}
|
||||
.name=${"data"}
|
||||
.defaultValue=${this._value?.data}
|
||||
@value-changed=${this._dataChanged}
|
||||
|
@@ -57,9 +57,6 @@ export class HaTextField extends TextFieldBase {
|
||||
.mdc-text-field__affix--suffix {
|
||||
padding-left: var(--text-field-suffix-padding-left, 12px);
|
||||
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||
padding-inline-start: var(--text-field-suffix-padding-left, 12px);
|
||||
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.mdc-text-field:not(.mdc-text-field--disabled)
|
||||
@@ -98,7 +95,6 @@ export class HaTextField extends TextFieldBase {
|
||||
.mdc-floating-label {
|
||||
inset-inline-start: 16px !important;
|
||||
inset-inline-end: initial !important;
|
||||
transform-origin: var(--float-start);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { getSignedPath } from "../../data/auth";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import type { MediaPlayerItem } from "../../data/media-player";
|
||||
import {
|
||||
browseMediaPlayer,
|
||||
@@ -46,7 +45,6 @@ import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../entity/ha-entity-picker";
|
||||
import "../ha-alert";
|
||||
import "../ha-button-menu";
|
||||
import "../ha-card";
|
||||
import "../ha-circular-progress";
|
||||
@@ -248,16 +246,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
],
|
||||
replace: true,
|
||||
});
|
||||
} else if (
|
||||
err.code === "entity_not_found" &&
|
||||
UNAVAILABLE_STATES.includes(this.hass.states[this.entityId]?.state)
|
||||
) {
|
||||
this._setError({
|
||||
message: this.hass.localize(
|
||||
`ui.components.media-browser.media_player_unavailable`
|
||||
),
|
||||
code: "entity_not_found",
|
||||
});
|
||||
} else {
|
||||
this._setError(err);
|
||||
}
|
||||
@@ -317,11 +305,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`
|
||||
<div class="container">
|
||||
<ha-alert alert-type="error">
|
||||
${this._renderError(this._error)}
|
||||
</ha-alert>
|
||||
</div>
|
||||
<div class="container">${this._renderError(this._error)}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -436,9 +420,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._error
|
||||
? html`
|
||||
<div class="container">
|
||||
<ha-alert alert-type="error">
|
||||
${this._renderError(this._error)}
|
||||
</ha-alert>
|
||||
${this._renderError(this._error)}
|
||||
</div>
|
||||
`
|
||||
: isTTSMediaSource(currentItem.media_content_id)
|
||||
|
@@ -34,7 +34,7 @@ const RECENT_THRESHOLD = 60000; // 1 minute
|
||||
const RECENT_CACHE: { [cacheKey: string]: RecentCacheResults } = {};
|
||||
const stateHistoryCache: { [cacheKey: string]: CachedResults } = {};
|
||||
|
||||
// Cached type 1 function. Without cache config.
|
||||
// Cached type 1 unction. Without cache config.
|
||||
export const getRecent = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@@ -103,14 +103,13 @@ export const getRecentWithCache = (
|
||||
language: string
|
||||
) => {
|
||||
const cacheKey = cacheConfig.cacheKey;
|
||||
const fullCacheKey = cacheKey + `_${cacheConfig.hoursToShow}`;
|
||||
const endTime = new Date();
|
||||
const startTime = new Date(endTime);
|
||||
startTime.setHours(startTime.getHours() - cacheConfig.hoursToShow);
|
||||
let toFetchStartTime = startTime;
|
||||
let appendingToCache = false;
|
||||
|
||||
let cache = stateHistoryCache[fullCacheKey];
|
||||
let cache = stateHistoryCache[cacheKey + `_${cacheConfig.hoursToShow}`];
|
||||
if (
|
||||
cache &&
|
||||
toFetchStartTime >= cache.startTime &&
|
||||
@@ -124,7 +123,7 @@ export const getRecentWithCache = (
|
||||
return cache.prom;
|
||||
}
|
||||
} else {
|
||||
cache = stateHistoryCache[fullCacheKey] = getEmptyCache(
|
||||
cache = stateHistoryCache[cacheKey] = getEmptyCache(
|
||||
language,
|
||||
startTime,
|
||||
endTime
|
||||
@@ -153,7 +152,7 @@ export const getRecentWithCache = (
|
||||
]);
|
||||
fetchedHistory = results[1];
|
||||
} catch (err: any) {
|
||||
delete stateHistoryCache[fullCacheKey];
|
||||
delete stateHistoryCache[cacheKey];
|
||||
throw err;
|
||||
}
|
||||
const stateHistory = computeHistory(hass, fetchedHistory, localize);
|
||||
|
@@ -20,20 +20,3 @@ export const BOARD_NAMES: Record<string, string> = {
|
||||
"intel-nuc": "Intel NUC",
|
||||
yellow: "Home Assistant Yellow",
|
||||
};
|
||||
|
||||
export interface HardwareInfo {
|
||||
hardware: HardwareInfoEntry[];
|
||||
}
|
||||
|
||||
export interface HardwareInfoEntry {
|
||||
board: HardwareInfoBoardInfo;
|
||||
name: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface HardwareInfoBoardInfo {
|
||||
manufacturer: string;
|
||||
model?: string;
|
||||
revision?: string;
|
||||
hassio_board_id?: string;
|
||||
}
|
||||
|
@@ -197,7 +197,7 @@ export const fetchRecent = (
|
||||
|
||||
export const fetchRecentWS = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string, // This may be CSV
|
||||
entityId: string,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
skipInitialState = false,
|
||||
@@ -213,7 +213,7 @@ export const fetchRecentWS = (
|
||||
include_start_time_state: !skipInitialState,
|
||||
minimal_response: minimalResponse,
|
||||
no_attributes: noAttributes || false,
|
||||
entity_ids: entityId.split(","),
|
||||
entity_ids: [entityId],
|
||||
});
|
||||
|
||||
export const fetchDate = (
|
||||
|
@@ -13,13 +13,6 @@ import { UNAVAILABLE_STATES } from "./entity";
|
||||
const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
|
||||
export const CONTINUOUS_DOMAINS = ["proximity", "sensor"];
|
||||
|
||||
export interface LogbookStreamMessage {
|
||||
events: LogbookEntry[];
|
||||
start_time?: number; // Start time of this historical chunk
|
||||
end_time?: number; // End time of this historical chunk
|
||||
partial?: boolean; // Indiciates more historical chunks are coming
|
||||
}
|
||||
|
||||
export interface LogbookEntry {
|
||||
// Base data
|
||||
when: number; // Python timestamp. Do *1000 to get JS timestamp.
|
||||
@@ -170,7 +163,7 @@ const getLogbookDataFromServer = (
|
||||
|
||||
export const subscribeLogbook = (
|
||||
hass: HomeAssistant,
|
||||
callbackFunction: (message: LogbookStreamMessage) => void,
|
||||
callbackFunction: (message: LogbookEntry[]) => void,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityIds?: string[],
|
||||
@@ -195,8 +188,8 @@ export const subscribeLogbook = (
|
||||
if (deviceIds?.length) {
|
||||
params.device_ids = deviceIds;
|
||||
}
|
||||
return hass.connection.subscribeMessage<LogbookStreamMessage>(
|
||||
(message) => callbackFunction(message),
|
||||
return hass.connection.subscribeMessage<LogbookEntry[]>(
|
||||
(message?) => callbackFunction(message),
|
||||
params
|
||||
);
|
||||
};
|
||||
|
@@ -131,9 +131,9 @@ export interface CallServiceActionConfig extends BaseActionConfig {
|
||||
action: "call-service";
|
||||
service: string;
|
||||
target?: HassServiceTarget;
|
||||
// "service_data" is kept for backwards compatibility. Replaced by "data".
|
||||
service_data?: Record<string, unknown>;
|
||||
data?: Record<string, unknown>;
|
||||
service_data?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NavigateActionConfig extends BaseActionConfig {
|
||||
@@ -159,7 +159,6 @@ export interface CustomActionConfig extends BaseActionConfig {
|
||||
}
|
||||
|
||||
export interface BaseActionConfig {
|
||||
action: string;
|
||||
confirmation?: ConfirmationRestrictionConfig;
|
||||
}
|
||||
|
||||
|
@@ -2,21 +2,9 @@ import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiGauge,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWeatherFog,
|
||||
mdiWeatherHail,
|
||||
mdiWeatherLightning,
|
||||
mdiWeatherLightningRainy,
|
||||
mdiWeatherNight,
|
||||
mdiWeatherNightPartlyCloudy,
|
||||
mdiWeatherPartlyCloudy,
|
||||
mdiWeatherPouring,
|
||||
mdiWeatherRainy,
|
||||
mdiWeatherSnowy,
|
||||
mdiWeatherSnowyRainy,
|
||||
mdiWeatherSunny,
|
||||
mdiWeatherWindy,
|
||||
mdiWeatherWindyVariant,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
@@ -69,21 +57,7 @@ export const weatherSVGs = new Set<string>([
|
||||
]);
|
||||
|
||||
export const weatherIcons = {
|
||||
"clear-night": mdiWeatherNight,
|
||||
cloudy: mdiWeatherCloudy,
|
||||
exceptional: mdiAlertCircleOutline,
|
||||
fog: mdiWeatherFog,
|
||||
hail: mdiWeatherHail,
|
||||
lightning: mdiWeatherLightning,
|
||||
"lightning-rainy": mdiWeatherLightningRainy,
|
||||
partlycloudy: mdiWeatherPartlyCloudy,
|
||||
pouring: mdiWeatherPouring,
|
||||
rainy: mdiWeatherRainy,
|
||||
snowy: mdiWeatherSnowy,
|
||||
"snowy-rainy": mdiWeatherSnowyRainy,
|
||||
sunny: mdiWeatherSunny,
|
||||
windy: mdiWeatherWindy,
|
||||
"windy-variant": mdiWeatherWindyVariant,
|
||||
};
|
||||
|
||||
export const weatherAttrIcons = {
|
||||
@@ -463,13 +437,6 @@ export const getWeatherStateIcon = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const weatherIcon = (state?: string, nightTime?: boolean): string =>
|
||||
!state
|
||||
? undefined
|
||||
: nightTime && state === "partlycloudy"
|
||||
? mdiWeatherNightPartlyCloudy
|
||||
: weatherIcons[state];
|
||||
|
||||
const DAY_IN_MILLISECONDS = 86400000;
|
||||
|
||||
export const isForecastHourly = (
|
||||
|
@@ -167,9 +167,6 @@ export interface ZwaveJSNodeMetadata {
|
||||
wakeup: string;
|
||||
reset: string;
|
||||
device_database_url: string;
|
||||
}
|
||||
|
||||
export interface ZwaveJSNodeComments {
|
||||
comments: ZWaveJSNodeComment[];
|
||||
}
|
||||
|
||||
@@ -230,20 +227,6 @@ export interface ZWaveJSHealNetworkStatusMessage {
|
||||
heal_node_status: { [key: number]: string };
|
||||
}
|
||||
|
||||
export interface ZWaveJSControllerStatisticsUpdatedMessage {
|
||||
event: "statistics updated";
|
||||
source: "controller";
|
||||
messages_tx: number;
|
||||
messages_rx: number;
|
||||
messages_dropped_tx: number;
|
||||
messages_dropped_rx: number;
|
||||
nak: number;
|
||||
can: number;
|
||||
timeout_ack: number;
|
||||
timeout_response: number;
|
||||
timeout_callback: number;
|
||||
}
|
||||
|
||||
export interface ZWaveJSRemovedNode {
|
||||
node_id: number;
|
||||
manufacturer: string;
|
||||
@@ -301,23 +284,12 @@ export const migrateZwave = (
|
||||
|
||||
export const fetchZwaveNetworkStatus = (
|
||||
hass: HomeAssistant,
|
||||
device_or_entry_id: {
|
||||
device_id?: string;
|
||||
entry_id?: string;
|
||||
}
|
||||
): Promise<ZWaveJSNetwork> => {
|
||||
if (device_or_entry_id.device_id && device_or_entry_id.entry_id) {
|
||||
throw new Error("Only one of device or entry ID should be supplied.");
|
||||
}
|
||||
if (!device_or_entry_id.device_id && !device_or_entry_id.entry_id) {
|
||||
throw new Error("Either device or entry ID should be supplied.");
|
||||
}
|
||||
return hass.callWS({
|
||||
entry_id: string
|
||||
): Promise<ZWaveJSNetwork> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/network_status",
|
||||
device_id: device_or_entry_id.device_id,
|
||||
entry_id: device_or_entry_id.entry_id,
|
||||
entry_id,
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchZwaveDataCollectionStatus = (
|
||||
hass: HomeAssistant,
|
||||
@@ -470,15 +442,6 @@ export const fetchZwaveNodeMetadata = (
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveNodeComments = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<ZwaveJSNodeComments> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/node_comments",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveNodeConfigParameters = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
@@ -584,19 +547,6 @@ export const subscribeHealZwaveNetworkProgress = (
|
||||
}
|
||||
);
|
||||
|
||||
export const subscribeZwaveControllerStatistics = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
callbackFunction: (message: ZWaveJSControllerStatisticsUpdatedMessage) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_controller_statistics",
|
||||
entry_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const getZwaveJsIdentifiersFromDevice = (
|
||||
device: DeviceRegistryEntry
|
||||
): ZWaveJSNodeIdentifiers | undefined => {
|
||||
|
@@ -78,7 +78,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
@click=${this._showBrowseMedia}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="browse-media-icon"
|
||||
.path=${mdiPlayBoxMultiple}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
@@ -212,7 +211,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
--mdc-theme-primary: currentColor;
|
||||
}
|
||||
@@ -244,10 +242,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
mwc-button > ha-svg-icon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.browse-media-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,23 @@
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiEye,
|
||||
mdiGauge,
|
||||
mdiThermometer,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWeatherFog,
|
||||
mdiWeatherHail,
|
||||
mdiWeatherLightning,
|
||||
mdiWeatherLightningRainy,
|
||||
mdiWeatherNight,
|
||||
mdiWeatherPartlyCloudy,
|
||||
mdiWeatherPouring,
|
||||
mdiWeatherRainy,
|
||||
mdiWeatherSnowy,
|
||||
mdiWeatherSnowyRainy,
|
||||
mdiWeatherSunny,
|
||||
mdiWeatherWindy,
|
||||
mdiWeatherWindyVariant,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
@@ -23,10 +37,27 @@ import {
|
||||
getWeatherUnit,
|
||||
getWind,
|
||||
isForecastHourly,
|
||||
weatherIcons,
|
||||
} from "../../../data/weather";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
const weatherIcons = {
|
||||
"clear-night": mdiWeatherNight,
|
||||
cloudy: mdiWeatherCloudy,
|
||||
exceptional: mdiAlertCircleOutline,
|
||||
fog: mdiWeatherFog,
|
||||
hail: mdiWeatherHail,
|
||||
lightning: mdiWeatherLightning,
|
||||
"lightning-rainy": mdiWeatherLightningRainy,
|
||||
partlycloudy: mdiWeatherPartlyCloudy,
|
||||
pouring: mdiWeatherPouring,
|
||||
rainy: mdiWeatherRainy,
|
||||
snowy: mdiWeatherSnowy,
|
||||
"snowy-rainy": mdiWeatherSnowyRainy,
|
||||
sunny: mdiWeatherSunny,
|
||||
windy: mdiWeatherWindy,
|
||||
"windy-variant": mdiWeatherWindyVariant,
|
||||
};
|
||||
|
||||
@customElement("more-info-weather")
|
||||
class MoreInfoWeather extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -204,7 +235,6 @@ class MoreInfoWeather extends LitElement {
|
||||
return css`
|
||||
ha-svg-icon {
|
||||
color: var(--paper-item-icon-color);
|
||||
margin-left: 8px;
|
||||
}
|
||||
.section {
|
||||
margin: 16px 0 8px 0;
|
||||
|
@@ -14,6 +14,7 @@ import {
|
||||
ApplicationCredential,
|
||||
} from "../../../data/application_credential";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { AddApplicationCredentialDialogParams } from "./show-dialog-add-application-credential";
|
||||
@@ -168,9 +169,11 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private async _handleDomainPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this._domain = ev.detail.value;
|
||||
private async _handleDomainPicked(ev: PolymerChangedEvent<string>) {
|
||||
const target = ev.target as any;
|
||||
if (target.selectedItem) {
|
||||
this._domain = target.selectedItem.id;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev: CustomEvent) {
|
||||
|
@@ -53,6 +53,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
title: localize(
|
||||
"ui.panel.config.application_credentials.picker.headers.name"
|
||||
),
|
||||
width: "40%",
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (_, entry: ApplicationCredential) => html`${entry.name}`,
|
||||
@@ -251,8 +252,6 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
.selected-txt {
|
||||
font-weight: bold;
|
||||
padding-left: 16px;
|
||||
padding-inline-start: 16px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.table-header .selected-txt {
|
||||
margin-top: 20px;
|
||||
|
@@ -19,6 +19,7 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../logbook/ha-logbook";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
@@ -42,16 +43,15 @@ import { SceneEntity } from "../../../data/scene";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
|
||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
name: string;
|
||||
@@ -761,10 +761,10 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
border-radius: 50%;
|
||||
}
|
||||
ha-logbook {
|
||||
height: 400px;
|
||||
height: 800px;
|
||||
}
|
||||
:host([narrow]) ha-logbook {
|
||||
height: 235px;
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
`,
|
||||
|
@@ -88,10 +88,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
<div class="account-row">
|
||||
<paper-item-body two-line>
|
||||
${this.cloudStatus.email.replace(
|
||||
/(\w{3})[\w.-]+@([\w.]+\w)/,
|
||||
"$1***@$2"
|
||||
)}
|
||||
${this.cloudStatus.email}
|
||||
<div secondary class="wrap">
|
||||
${this._subscription
|
||||
? this._subscription.human_description.replace(
|
||||
|
@@ -51,6 +51,11 @@ export class CloudRemotePref extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
const urlParts = remote_domain!.split(".");
|
||||
const hiddenURL = `https://${urlParts[0].substring(0, 5)}***.${
|
||||
urlParts[1]
|
||||
}.${urlParts[2]}.${urlParts[3]}`;
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
@@ -87,9 +92,8 @@ export class CloudRemotePref extends LitElement {
|
||||
target="_blank"
|
||||
class="break-word"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.remote.nabu_casa_url"
|
||||
)}</a
|
||||
>
|
||||
${hiddenURL}</a
|
||||
>.
|
||||
<ha-svg-icon
|
||||
.url=${`https://${remote_domain}`}
|
||||
|
@@ -8,7 +8,7 @@ import "../../../components/ha-navigation-list";
|
||||
import "../../../components/ha-tip";
|
||||
import { BackupContent, fetchBackupInfo } from "../../../data/backup";
|
||||
import { CloudStatus, fetchCloudStatus } from "../../../data/cloud";
|
||||
import { BOARD_NAMES, HardwareInfo } from "../../../data/hardware";
|
||||
import { BOARD_NAMES } from "../../../data/hardware";
|
||||
import { fetchHassioBackups, HassioBackup } from "../../../data/hassio/backup";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
@@ -156,8 +156,8 @@ class HaConfigSystemNavigation extends LitElement {
|
||||
this._fetchNetworkStatus();
|
||||
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
|
||||
this._fetchBackupInfo(isHassioLoaded);
|
||||
this._fetchHardwareInfo(isHassioLoaded);
|
||||
if (isHassioLoaded) {
|
||||
this._fetchHardwareInfo();
|
||||
this._fetchStorageInfo();
|
||||
}
|
||||
}
|
||||
@@ -202,17 +202,10 @@ class HaConfigSystemNavigation extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchHardwareInfo(isHassioLoaded: boolean) {
|
||||
if (isComponentLoaded(this.hass, "hardware")) {
|
||||
const hardwareInfo: HardwareInfo = await this.hass.callWS({
|
||||
type: "hardware/info",
|
||||
});
|
||||
this._boardName = hardwareInfo?.hardware?.[0].name;
|
||||
} else if (isHassioLoaded) {
|
||||
const osData: HassioHassOSInfo = await fetchHassioHassOsInfo(this.hass);
|
||||
if (osData.board) {
|
||||
this._boardName = BOARD_NAMES[osData.board];
|
||||
}
|
||||
private async _fetchHardwareInfo() {
|
||||
const osData: HassioHassOSInfo = await fetchHassioHassOsInfo(this.hass);
|
||||
if (osData.board) {
|
||||
this._boardName = BOARD_NAMES[osData.board];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,13 +0,0 @@
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
|
||||
export const getMQTTDeviceActions = (
|
||||
el: HTMLElement,
|
||||
device: DeviceRegistryEntry
|
||||
): DeviceAction[] => [
|
||||
{
|
||||
label: "MQTT Info",
|
||||
action: async () => showMQTTDeviceDebugInfoDialog(el, { device }),
|
||||
},
|
||||
];
|
@@ -0,0 +1,36 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
|
||||
@customElement("ha-device-actions-mqtt")
|
||||
export class HaDeviceActionsMqtt extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-button @click=${this._showDebugInfo}> MQTT Info </mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showDebugInfo(): Promise<void> {
|
||||
const device = this.device;
|
||||
await showMQTTDeviceDebugInfoDialog(this, { device });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -1,108 +0,0 @@
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice } from "../../../../../../data/zha";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZHADeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const zigbeeConnection = device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
|
||||
if (!zigbeeConnection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const zhaDevice = await fetchZHADevice(hass, zigbeeConnection[1]);
|
||||
|
||||
if (!zhaDevice) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions: DeviceAction[] = [];
|
||||
|
||||
if (zhaDevice.device_type !== "Coordinator") {
|
||||
actions.push({
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.reconfigure"),
|
||||
action: () => showZHAReconfigureDeviceDialog(el, { device: zhaDevice }),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
zhaDevice.power_source === "Mains" &&
|
||||
(zhaDevice.device_type === "Router" ||
|
||||
zhaDevice.device_type === "Coordinator")
|
||||
) {
|
||||
actions.push(
|
||||
...[
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.add"),
|
||||
action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
),
|
||||
action: () => showZHADeviceChildrenDialog(el, { device: zhaDevice! }),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (zhaDevice.device_type !== "Coordinator") {
|
||||
actions.push(
|
||||
...[
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
),
|
||||
action: () =>
|
||||
showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"),
|
||||
action: () => showZHAClusterDialog(el, { device: zhaDevice }),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
),
|
||||
action: () =>
|
||||
navigate(`/config/zha/visualization/${zhaDevice!.device_reg_id}`),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.remove"),
|
||||
classes: "warning",
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(el, {
|
||||
text: hass.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callService("zha", "remove", {
|
||||
ieee: zhaDevice.ieee,
|
||||
});
|
||||
|
||||
history.back();
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice, ZHADevice } from "../../../../../../data/zha";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
|
||||
|
||||
@customElement("ha-device-actions-zha")
|
||||
export class HaDeviceActionsZha extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _zhaDevice?: ZHADevice;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("device")) {
|
||||
const zigbeeConnection = this.device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
if (!zigbeeConnection) {
|
||||
return;
|
||||
}
|
||||
fetchZHADevice(this.hass, zigbeeConnection[1]).then((device) => {
|
||||
this._zhaDevice = device;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._zhaDevice) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._onReconfigureNodeClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.reconfigure"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.power_source === "Mains" &&
|
||||
(this._zhaDevice.device_type === "Router" ||
|
||||
this._zhaDevice.device_type === "Coordinator")
|
||||
? html`
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.buttons.add")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._handleDeviceChildrenClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._handleZigbeeInfoClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._showClustersDialog}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.clusters"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._onViewInVisualizationClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button class="warning" @click=${this._removeDevice}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.remove"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showClustersDialog(): Promise<void> {
|
||||
await showZHAClusterDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _onReconfigureNodeClick(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
showZHAReconfigureDeviceDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private _onAddDevicesClick() {
|
||||
navigate(`/config/zha/add/${this._zhaDevice!.ieee}`);
|
||||
}
|
||||
|
||||
private _onViewInVisualizationClick() {
|
||||
navigate(`/config/zha/visualization/${this._zhaDevice!.device_reg_id}`);
|
||||
}
|
||||
|
||||
private async _handleZigbeeInfoClicked() {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _handleDeviceChildrenClicked() {
|
||||
showZHADeviceChildrenDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _removeDevice() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.hass.callService("zha", "remove", {
|
||||
ieee: this._zhaDevice!.ieee,
|
||||
});
|
||||
|
||||
history.back();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -7,7 +7,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice, ZHADevice } from "../../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
@@ -41,39 +40,38 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-expansion-panel header="Zigbee info">
|
||||
<div>IEEE: ${this._zhaDevice.ieee}</div>
|
||||
<div>Nwk: ${formatAsPaddedHex(this._zhaDevice.nwk)}</div>
|
||||
<div>Device Type: ${this._zhaDevice.device_type}</div>
|
||||
<div>
|
||||
LQI:
|
||||
${this._zhaDevice.lqi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
RSSI:
|
||||
${this._zhaDevice.rssi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.last_seen")}:
|
||||
${this._zhaDevice.last_seen ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.power_source")}:
|
||||
${this._zhaDevice.power_source ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
${this._zhaDevice.quirk_applied
|
||||
? html`
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.quirk")}:
|
||||
${this._zhaDevice.quirk_class}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
<h4>Zigbee info</h4>
|
||||
<div>IEEE: ${this._zhaDevice.ieee}</div>
|
||||
<div>Nwk: ${formatAsPaddedHex(this._zhaDevice.nwk)}</div>
|
||||
<div>Device Type: ${this._zhaDevice.device_type}</div>
|
||||
<div>
|
||||
LQI:
|
||||
${this._zhaDevice.lqi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
RSSI:
|
||||
${this._zhaDevice.rssi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.last_seen")}:
|
||||
${this._zhaDevice.last_seen ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.power_source")}:
|
||||
${this._zhaDevice.power_source ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
${this._zhaDevice.quirk_applied
|
||||
? html`
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.quirk")}:
|
||||
${this._zhaDevice.quirk_class}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -88,11 +86,6 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
--expansion-panel-content-padding: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,68 +0,0 @@
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZwaveNodeStatus } from "../../../../../../data/zwave_js";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
|
||||
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
|
||||
import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZwaveDeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const configEntries = await getConfigEntries(hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const node = await fetchZwaveNodeStatus(hass, device.id);
|
||||
|
||||
if (!node || node.is_controller_node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
),
|
||||
href: `/config/zwave_js/node_config/${device.id}?config_entry=${entryId}`,
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSReinterviewNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"),
|
||||
action: () =>
|
||||
showZWaveJSHealNodeDialog(el, {
|
||||
device: device,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSRemoveFailedNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
@@ -1,20 +0,0 @@
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZwaveNodeComments } from "../../../../../../data/zwave_js";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { DeviceAlert } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZwaveDeviceAlerts = async (
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAlert[]> => {
|
||||
const nodeComments = await fetchZwaveNodeComments(hass, device.id);
|
||||
|
||||
if (!nodeComments?.comments?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return nodeComments.comments.map((comment) => ({
|
||||
level: comment.level,
|
||||
text: comment.text,
|
||||
}));
|
||||
};
|
@@ -0,0 +1,138 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
|
||||
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
|
||||
import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node";
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
|
||||
@customElement("ha-device-actions-zwave_js")
|
||||
export class HaDeviceActionsZWaveJS extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _entryId?: string;
|
||||
|
||||
@state() private _node?: ZWaveJSNodeStatus;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("device")) {
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
protected async _fetchNodeDetails() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._node = undefined;
|
||||
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
this.device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._entryId = configEntry.entry_id;
|
||||
|
||||
this._node = await fetchZwaveNodeStatus(this.hass, this.device.id);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._node) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<a
|
||||
.href=${`/config/zwave_js/node_config/${this.device.id}?config_entry=${this._entryId}`}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._reinterviewClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._healNodeClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.heal_node"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._removeFailedNode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _reinterviewClicked() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSReinterviewNodeDialog(this, {
|
||||
device_id: this.device.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async _healNodeClicked() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSHealNodeDialog(this, {
|
||||
entry_id: this._entryId!,
|
||||
device: this.device,
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeFailedNode() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSRemoveFailedNodeDialog(this, {
|
||||
device_id: this.device.id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -7,17 +7,16 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
ConfigEntry,
|
||||
getConfigEntries,
|
||||
} from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
nodeStatus,
|
||||
SecurityClass,
|
||||
ZWaveJSNodeStatus,
|
||||
SecurityClass,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
@@ -70,76 +69,73 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_info"
|
||||
)}
|
||||
>
|
||||
${this._multipleConfigEntries
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.source")}:
|
||||
${this._configEntry!.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
<h4>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.device_info.zwave_info")}
|
||||
</h4>
|
||||
${this._multipleConfigEntries
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.source")}:
|
||||
${this._configEntry!.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -154,11 +150,6 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
--expansion-panel-content-padding: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -541,9 +541,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
.clear {
|
||||
color: var(--primary-color);
|
||||
padding-left: 8px;
|
||||
padding-inline-start: 8px;
|
||||
text-transform: uppercase;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
haStyle,
|
||||
|
@@ -378,7 +378,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
</mwc-list-item>
|
||||
<mwc-list-item
|
||||
value="outlet"
|
||||
.selected=${this._deviceClass === "outlet"}
|
||||
.selected=${!this._deviceClass ||
|
||||
this._deviceClass === "outlet"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.device_classes.switch.outlet"
|
||||
|
@@ -976,8 +976,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
.selected-txt {
|
||||
font-weight: bold;
|
||||
padding-left: 16px;
|
||||
padding-inline-start: 16px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.table-header .selected-txt {
|
||||
margin-top: 20px;
|
||||
@@ -987,8 +985,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
.header-toolbar .header-btns {
|
||||
margin-right: -12px;
|
||||
margin-inline-end: -12px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.header-btns {
|
||||
display: flex;
|
||||
@@ -1003,9 +999,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
.clear {
|
||||
color: var(--primary-color);
|
||||
padding-left: 8px;
|
||||
padding-inline-start: 8px;
|
||||
text-transform: uppercase;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,18 +1,14 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-clickable-list-item";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-settings-row";
|
||||
import { BOARD_NAMES, HardwareInfo } from "../../../data/hardware";
|
||||
import { BOARD_NAMES } from "../../../data/hardware";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
@@ -32,8 +28,6 @@ import {
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hardwareBrandsUrl } from "../../../util/brands-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showhardwareAvailableDialog } from "./show-dialog-hardware-available";
|
||||
|
||||
@customElement("ha-config-hardware")
|
||||
@@ -48,36 +42,14 @@ class HaConfigHardware extends LitElement {
|
||||
|
||||
@state() private _hostData?: HassioHostInfo;
|
||||
|
||||
@state() private _hardwareInfo?: HardwareInfo;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._load();
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
this._load();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let boardId: string | undefined;
|
||||
let boardName: string | undefined;
|
||||
let imageURL: string | undefined;
|
||||
let documentationURL: string | undefined;
|
||||
|
||||
if (this._hardwareInfo?.hardware.length) {
|
||||
const boardData = this._hardwareInfo!.hardware[0];
|
||||
|
||||
boardId = boardData.board.hassio_board_id;
|
||||
boardName = boardData.name;
|
||||
documentationURL = boardData.url;
|
||||
imageURL = hardwareBrandsUrl({
|
||||
category: "boards",
|
||||
manufacturer: boardData.board.manufacturer,
|
||||
model: boardData.board.model,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
});
|
||||
} else if (this._OSData?.board) {
|
||||
boardId = this._OSData.board;
|
||||
boardName = BOARD_NAMES[this._OSData.board];
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/system"
|
||||
@@ -96,20 +68,6 @@ class HaConfigHardware extends LitElement {
|
||||
"ui.panel.config.hardware.available_hardware.title"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
${this._hostData
|
||||
? html`
|
||||
<mwc-list-item class="warning" @click=${this._hostReboot}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.reboot_host"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
<mwc-list-item class="warning" @click=${this._hostShutdown}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.shutdown_host"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
${this._error
|
||||
? html`
|
||||
@@ -118,55 +76,57 @@ class HaConfigHardware extends LitElement {
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${boardName
|
||||
${this._OSData || this._hostData
|
||||
? html`
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<mwc-list>
|
||||
<mwc-list-item
|
||||
graphic=${ifDefined(imageURL ? "medium" : undefined)}
|
||||
.twoline=${Boolean(boardId)}
|
||||
>
|
||||
${imageURL
|
||||
? html`<img slot="graphic" src=${imageURL} />`
|
||||
: ""}
|
||||
<span class="primary-text">
|
||||
${boardName ||
|
||||
this.hass.localize("ui.panel.config.hardware.board")}
|
||||
</span>
|
||||
${boardId
|
||||
? html`
|
||||
<span class="secondary-text" slot="secondary"
|
||||
>${boardId}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</mwc-list-item>
|
||||
${documentationURL
|
||||
? html`
|
||||
<ha-clickable-list-item
|
||||
.href=${documentationURL}
|
||||
openNewTab
|
||||
twoline
|
||||
hasMeta
|
||||
${this._OSData?.board
|
||||
? html`
|
||||
<div class="card-content">
|
||||
<ha-settings-row>
|
||||
<span slot="heading"
|
||||
>${BOARD_NAMES[this._OSData.board] ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.hardware.board"
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-clickable-list-item>
|
||||
`
|
||||
: ""}
|
||||
</mwc-list>
|
||||
</div>
|
||||
<div slot="description">
|
||||
<span class="value">${this._OSData.board}</span>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._hostData
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
${this._hostData.features.includes("reboot")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.reboot_host"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
${this._hostData.features.includes("shutdown")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._hostShutdown}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.shutdown_host"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
@@ -176,17 +136,9 @@ class HaConfigHardware extends LitElement {
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
|
||||
try {
|
||||
if (isComponentLoaded(this.hass, "hardware")) {
|
||||
this._hardwareInfo = await this.hass.callWS({ type: "hardware/info" });
|
||||
} else if (isHassioLoaded) {
|
||||
this._OSData = await fetchHassioHassOsInfo(this.hass);
|
||||
}
|
||||
|
||||
if (isHassioLoaded) {
|
||||
this._hostData = await fetchHassioHostInfo(this.hass);
|
||||
}
|
||||
this._OSData = await fetchHassioHassOsInfo(this.hass);
|
||||
this._hostData = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err: any) {
|
||||
this._error = err.message || err;
|
||||
}
|
||||
@@ -196,7 +148,10 @@ class HaConfigHardware extends LitElement {
|
||||
showhardwareAvailableDialog(this);
|
||||
}
|
||||
|
||||
private async _hostReboot(): Promise<void> {
|
||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.hardware.reboot_host"),
|
||||
text: this.hass.localize("ui.panel.config.hardware.reboot_host_confirm"),
|
||||
@@ -205,14 +160,10 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.panel.config.hardware.rebooting_host"),
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err: any) {
|
||||
@@ -226,9 +177,13 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _hostShutdown(): Promise<void> {
|
||||
private async _hostShutdown(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.hardware.shutdown_host"),
|
||||
text: this.hass.localize(
|
||||
@@ -239,16 +194,10 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.hardware.host_shutting_down"
|
||||
),
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err: any) {
|
||||
@@ -262,6 +211,7 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -284,18 +234,17 @@ class HaConfigHardware extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 0 16px;
|
||||
}
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
|
||||
.primary-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
.secondary-text {
|
||||
font-size: 14px;
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -202,9 +202,10 @@ class DialogZWaveJSHealNetwork extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, {
|
||||
entry_id: this.entry_id!,
|
||||
});
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(
|
||||
this.hass!,
|
||||
this.entry_id!
|
||||
);
|
||||
if (network.controller.is_heal_network_active) {
|
||||
this._status = "started";
|
||||
this._subscribed = subscribeHealZwaveNetworkProgress(
|
||||
|
@@ -22,6 +22,8 @@ import { ZWaveJSHealNodeDialogParams } from "./show-dialog-zwave_js-heal-node";
|
||||
class DialogZWaveJSHealNode extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private entry_id?: string;
|
||||
|
||||
@state() private device?: DeviceRegistryEntry;
|
||||
|
||||
@state() private _status?: string;
|
||||
@@ -29,11 +31,13 @@ class DialogZWaveJSHealNode extends LitElement {
|
||||
@state() private _error?: string;
|
||||
|
||||
public showDialog(params: ZWaveJSHealNodeDialogParams): void {
|
||||
this.entry_id = params.entry_id;
|
||||
this.device = params.device;
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.entry_id = undefined;
|
||||
this._status = undefined;
|
||||
this.device = undefined;
|
||||
this._error = undefined;
|
||||
@@ -42,7 +46,7 @@ class DialogZWaveJSHealNode extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.device) {
|
||||
if (!this.entry_id || !this.device) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -198,9 +202,10 @@ class DialogZWaveJSHealNode extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, {
|
||||
device_id: this.device!.id,
|
||||
});
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(
|
||||
this.hass!,
|
||||
this.entry_id!
|
||||
);
|
||||
if (network.controller.is_heal_network_active) {
|
||||
this._status = "network-healing";
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
|
||||
export interface ZWaveJSHealNodeDialogParams {
|
||||
entry_id: string;
|
||||
device: DeviceRegistryEntry;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCheckCircle,
|
||||
@@ -12,12 +11,9 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-expansion-panel";
|
||||
import "../../../../../components/ha-fab";
|
||||
import "../../../../../components/ha-help-tooltip";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
fetchZwaveDataCollectionStatus,
|
||||
fetchZwaveNetworkStatus,
|
||||
@@ -26,9 +22,7 @@ import {
|
||||
setZwaveDataCollectionPreference,
|
||||
stopZwaveExclusion,
|
||||
stopZwaveInclusion,
|
||||
subscribeZwaveControllerStatistics,
|
||||
ZWaveJSClient,
|
||||
ZWaveJSControllerStatisticsUpdatedMessage,
|
||||
ZWaveJSNetwork,
|
||||
ZwaveJSProvisioningEntry,
|
||||
} from "../../../../../data/zwave_js";
|
||||
@@ -47,10 +41,9 @@ import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node"
|
||||
import { configTabs } from "./zwave_js-config-router";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { computeRTL } from "../../../../../common/util/compute_rtl";
|
||||
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||
|
||||
@customElement("zwave_js-config-dashboard")
|
||||
class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
class ZWaveJSConfigDashboard extends LitElement {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
@@ -59,7 +52,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public configEntryId!: string;
|
||||
@property() public configEntryId?: string;
|
||||
|
||||
@state() private _configEntry?: ConfigEntry;
|
||||
|
||||
@@ -73,30 +66,12 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _dataCollectionOptIn?: boolean;
|
||||
|
||||
@state()
|
||||
private _statistics?: ZWaveJSControllerStatisticsUpdatedMessage;
|
||||
|
||||
protected firstUpdated() {
|
||||
if (this.hass) {
|
||||
this._fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||
return [
|
||||
subscribeZwaveControllerStatistics(
|
||||
this.hass,
|
||||
this.configEntryId,
|
||||
(message) => {
|
||||
if (!this.hasUpdated) {
|
||||
return;
|
||||
}
|
||||
this._statistics = message;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._configEntry) {
|
||||
return html``;
|
||||
@@ -236,178 +211,22 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
</ha-card>
|
||||
<ha-card header="Diagnostics">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.driver_version"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.client.driver_version}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_version"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.client.server_version}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.home_id"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.controller.home_id}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_url"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.client.ws_server_url}</span>
|
||||
</div>
|
||||
<br />
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.title"
|
||||
)}
|
||||
>
|
||||
<mwc-list noninteractive>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_tx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_rx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_dropped_tx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_dropped_rx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.nak.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.nak.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._statistics?.nak ?? 0}</span>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.can.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.can.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._statistics?.can ?? 0}</span>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.timeout_ack ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.timeout_response ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.timeout_callback ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
</mwc-list>
|
||||
</ha-expansion-panel>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.driver_version"
|
||||
)}:
|
||||
${this._network.client.driver_version}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_version"
|
||||
)}:
|
||||
${this._network.client.server_version}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.home_id"
|
||||
)}:
|
||||
${this._network.controller.home_id}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_url"
|
||||
)}:
|
||||
${this._network.client.ws_server_url}<br />
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button
|
||||
@@ -564,7 +383,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
this._configEntry = configEntries.find(
|
||||
(entry) => entry.entry_id === this.configEntryId
|
||||
(entry) => entry.entry_id === this.configEntryId!
|
||||
);
|
||||
|
||||
if (ERROR_STATES.includes(this._configEntry!.state)) {
|
||||
@@ -573,7 +392,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
const [network, dataCollectionStatus, provisioningEntries] =
|
||||
await Promise.all([
|
||||
fetchZwaveNetworkStatus(this.hass!, { entry_id: this.configEntryId }),
|
||||
fetchZwaveNetworkStatus(this.hass!, this.configEntryId),
|
||||
fetchZwaveDataCollectionStatus(this.hass!, this.configEntryId),
|
||||
fetchZwaveProvisioningEntries(this.hass!, this.configEntryId),
|
||||
]);
|
||||
@@ -689,16 +508,6 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
span[slot="meta"] {
|
||||
font-size: 0.95em;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -721,10 +530,6 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
mwc-list-item {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
}
|
||||
@@ -741,6 +546,12 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
button.dump {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
@@ -166,6 +166,17 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
${this._nodeMetadata.comments?.length > 0
|
||||
? html`
|
||||
<div>
|
||||
${this._nodeMetadata.comments.map(
|
||||
(comment) => html`<ha-alert .alertType=${comment.level}>
|
||||
${comment.text}
|
||||
</ha-alert>`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ``}
|
||||
<ha-card>
|
||||
${Object.entries(this._config).map(
|
||||
([id, item]) => html` <ha-settings-row
|
||||
|
@@ -87,7 +87,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
${dashboard.default
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
style="padding-left: 10px; padding-inline-start: 10px; direction: var(--direction);"
|
||||
style="padding-left: 10px;"
|
||||
.path=${mdiCheckCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<paper-tooltip animation-delay="0">
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-formfield";
|
||||
@@ -158,6 +159,7 @@ class DialogPersonDetail extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.local_only"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._localOnly}
|
||||
@@ -169,6 +171,7 @@ class DialogPersonDetail extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.admin"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.disabled=${this._user.system_generated ||
|
||||
|
@@ -126,9 +126,7 @@ class PanelEnergy extends LitElement {
|
||||
hui-energy-period-selector {
|
||||
width: 100%;
|
||||
padding-left: 16px;
|
||||
padding-inline-start: 16px;
|
||||
--disabled-text-color: rgba(var(--rgb-text-primary-color), 0.5);
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -556,6 +556,10 @@ class HaLogbookRenderer extends LitElement {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.narrow .date {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.rtl .date {
|
||||
direction: rtl;
|
||||
}
|
||||
@@ -586,12 +590,6 @@ class HaLogbookRenderer extends LitElement {
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.link {
|
||||
color: var(--paper-item-icon-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -609,6 +607,7 @@ class HaLogbookRenderer extends LitElement {
|
||||
|
||||
.narrow .entry {
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.narrow .icon-message state-badge {
|
||||
|
@@ -9,7 +9,6 @@ import "../../components/ha-circular-progress";
|
||||
import {
|
||||
clearLogbookCache,
|
||||
LogbookEntry,
|
||||
LogbookStreamMessage,
|
||||
subscribeLogbook,
|
||||
} from "../../data/logbook";
|
||||
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
||||
@@ -248,16 +247,17 @@ export class HaLogbook extends LitElement {
|
||||
}
|
||||
this._subscribed = subscribeLogbook(
|
||||
this.hass,
|
||||
(streamMessage) => {
|
||||
// "recent" means start time is a sliding window
|
||||
// so we need to calculate an expireTime to
|
||||
// purge old events
|
||||
this._processStreamMessage(
|
||||
streamMessage,
|
||||
"recent" in this.time
|
||||
? findStartOfRecentTime(new Date(), this.time.recent)
|
||||
: undefined
|
||||
);
|
||||
(newEntries?) => {
|
||||
if ("recent" in this.time) {
|
||||
// start time is a sliding window purge old ones
|
||||
this._processNewEntries(
|
||||
newEntries,
|
||||
findStartOfRecentTime(new Date(), this.time.recent)
|
||||
);
|
||||
} else if ("range" in this.time) {
|
||||
// start time is fixed, we can just append
|
||||
this._processNewEntries(newEntries, undefined);
|
||||
}
|
||||
},
|
||||
logbookPeriod.startTime.toISOString(),
|
||||
logbookPeriod.endTime.toISOString(),
|
||||
@@ -303,22 +303,17 @@ export class HaLogbook extends LitElement {
|
||||
)
|
||||
: this._logbookEntries;
|
||||
|
||||
private _processStreamMessage = (
|
||||
streamMessage: LogbookStreamMessage,
|
||||
private _processNewEntries = (
|
||||
newEntries: LogbookEntry[],
|
||||
purgeBeforePythonTime: number | undefined
|
||||
) => {
|
||||
// Put newest ones on top. Reverse works in-place so
|
||||
// make a copy first.
|
||||
const newEntries = [...streamMessage.events].reverse();
|
||||
newEntries = [...newEntries].reverse();
|
||||
if (!this._logbookEntries) {
|
||||
this._logbookEntries = newEntries;
|
||||
return;
|
||||
}
|
||||
if (!newEntries.length) {
|
||||
// Empty messages are still sent to
|
||||
// indicate no more historical events
|
||||
return;
|
||||
}
|
||||
const nonExpiredRecords = this._nonExpiredRecords(purgeBeforePythonTime);
|
||||
this._logbookEntries =
|
||||
newEntries[0].when >= this._logbookEntries[0].when
|
||||
|
@@ -83,13 +83,6 @@ export class HuiEnergySourcesTableCard
|
||||
let totalGas = 0;
|
||||
let totalGasCost = 0;
|
||||
|
||||
let totalGridCompare = 0;
|
||||
let totalGridCostCompare = 0;
|
||||
let totalSolarCompare = 0;
|
||||
let totalBatteryCompare = 0;
|
||||
let totalGasCompare = 0;
|
||||
let totalGasCostCompare = 0;
|
||||
|
||||
const types = energySourcesByType(this._data.prefs);
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
@@ -130,8 +123,6 @@ export class HuiEnergySourcesTableCard
|
||||
|
||||
const gasUnit = getEnergyGasUnit(this.hass, this._data.prefs) || "";
|
||||
|
||||
const compare = this._data.statsCompare !== undefined;
|
||||
|
||||
return html` <ha-card>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
@@ -151,28 +142,6 @@ export class HuiEnergySourcesTableCard
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.source"
|
||||
)}
|
||||
</th>
|
||||
${compare
|
||||
? html`<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.previous_energy"
|
||||
)}
|
||||
</th>
|
||||
${showCosts
|
||||
? html`<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.previous_cost"
|
||||
)}
|
||||
</th>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
@@ -204,14 +173,6 @@ export class HuiEnergySourcesTableCard
|
||||
) || 0;
|
||||
totalSolar += energy;
|
||||
|
||||
const compareEnergy =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalSolarCompare += compareEnergy;
|
||||
|
||||
const modifiedColor =
|
||||
idx > 0
|
||||
? this.hass.themes.darkMode
|
||||
@@ -237,16 +198,6 @@ export class HuiEnergySourcesTableCard
|
||||
? computeStateName(entity)
|
||||
: source.stat_energy_from}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(compareEnergy, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -263,17 +214,6 @@ export class HuiEnergySourcesTableCard
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
Solar total
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(totalSolarCompare, this.hass.locale)}
|
||||
kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -297,20 +237,6 @@ export class HuiEnergySourcesTableCard
|
||||
) || 0;
|
||||
totalBattery += energyFrom - energyTo;
|
||||
|
||||
const energyFromCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
const energyToCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_to]
|
||||
)) ||
|
||||
0;
|
||||
totalBatteryCompare += energyFromCompare - energyToCompare;
|
||||
|
||||
const modifiedFromColor =
|
||||
idx > 0
|
||||
? this.hass.themes.darkMode
|
||||
@@ -345,17 +271,6 @@ export class HuiEnergySourcesTableCard
|
||||
? computeStateName(entityFrom)
|
||||
: source.stat_energy_from}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(energyFromCompare, this.hass.locale)}
|
||||
kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -380,20 +295,6 @@ export class HuiEnergySourcesTableCard
|
||||
? computeStateName(entityTo)
|
||||
: source.stat_energy_from}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(
|
||||
energyToCompare * -1,
|
||||
this.hass.locale
|
||||
)}
|
||||
kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -412,20 +313,6 @@ export class HuiEnergySourcesTableCard
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.battery_total"
|
||||
)}
|
||||
</th>
|
||||
${compare
|
||||
? html` <td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(
|
||||
totalBatteryCompare,
|
||||
this.hass.locale
|
||||
)}
|
||||
kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -444,15 +331,6 @@ export class HuiEnergySourcesTableCard
|
||||
this._data!.stats[flow.stat_energy_from]
|
||||
) || 0;
|
||||
totalGrid += energy;
|
||||
|
||||
const compareEnergy =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[flow.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalGridCompare += compareEnergy;
|
||||
|
||||
const cost_stat =
|
||||
flow.stat_cost ||
|
||||
this._data!.info.cost_sensors[flow.stat_energy_from];
|
||||
@@ -465,16 +343,6 @@ export class HuiEnergySourcesTableCard
|
||||
totalGridCost += cost;
|
||||
}
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalGridCostCompare += costCompare;
|
||||
}
|
||||
|
||||
const modifiedColor =
|
||||
idx > 0
|
||||
? this.hass.themes.darkMode
|
||||
@@ -500,29 +368,6 @@ export class HuiEnergySourcesTableCard
|
||||
? computeStateName(entity)
|
||||
: flow.stat_energy_from}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(compareEnergy, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${costCompare !== null
|
||||
? formatNumber(
|
||||
costCompare,
|
||||
this.hass.locale,
|
||||
{
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
}
|
||||
)
|
||||
: ""}
|
||||
</td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -561,24 +406,6 @@ export class HuiEnergySourcesTableCard
|
||||
totalGridCost += cost;
|
||||
}
|
||||
|
||||
const energyCompare =
|
||||
((compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[flow.stat_energy_to]
|
||||
)) ||
|
||||
0) * -1;
|
||||
totalGridCompare += energyCompare;
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? (calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0) * -1
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalGridCostCompare += costCompare;
|
||||
}
|
||||
|
||||
const modifiedColor =
|
||||
idx > 0
|
||||
? this.hass.themes.darkMode
|
||||
@@ -602,29 +429,6 @@ export class HuiEnergySourcesTableCard
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
${entity ? computeStateName(entity) : flow.stat_energy_to}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(energyCompare, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${costCompare !== null
|
||||
? formatNumber(
|
||||
costCompare,
|
||||
this.hass.locale,
|
||||
{
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
}
|
||||
)
|
||||
: ""}
|
||||
</td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -653,28 +457,6 @@ export class HuiEnergySourcesTableCard
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.grid_total"
|
||||
)}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(totalGridCompare, this.hass.locale)}
|
||||
kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(
|
||||
totalGridCostCompare,
|
||||
this.hass.locale,
|
||||
{
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
}
|
||||
)}
|
||||
</td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -700,14 +482,6 @@ export class HuiEnergySourcesTableCard
|
||||
) || 0;
|
||||
totalGas += energy;
|
||||
|
||||
const energyCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalGasCompare += energyCompare;
|
||||
|
||||
const cost_stat =
|
||||
source.stat_cost ||
|
||||
this._data!.info.cost_sensors[source.stat_energy_from];
|
||||
@@ -719,16 +493,6 @@ export class HuiEnergySourcesTableCard
|
||||
totalGasCost += cost;
|
||||
}
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalGasCostCompare += costCompare;
|
||||
}
|
||||
|
||||
const modifiedColor =
|
||||
idx > 0
|
||||
? this.hass.themes.darkMode
|
||||
@@ -754,26 +518,6 @@ export class HuiEnergySourcesTableCard
|
||||
? computeStateName(entity)
|
||||
: source.stat_energy_from}
|
||||
</th>
|
||||
${compare
|
||||
? html` <td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(energyCompare, this.hass.locale)}
|
||||
${gasUnit}
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${costCompare !== null
|
||||
? formatNumber(costCompare, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})
|
||||
: ""}
|
||||
</td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -801,28 +545,6 @@ export class HuiEnergySourcesTableCard
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.gas_total"
|
||||
)}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(totalGasCompare, this.hass.locale)}
|
||||
${gasUnit}
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(
|
||||
totalGasCostCompare,
|
||||
this.hass.locale,
|
||||
{
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
}
|
||||
)}
|
||||
</td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
@@ -848,23 +570,6 @@ export class HuiEnergySourcesTableCard
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.total_costs"
|
||||
)}
|
||||
</th>
|
||||
${compare
|
||||
? html`<td class="mdc-data-table__cell">
|
||||
${formatNumber(
|
||||
totalGasCostCompare + totalGridCostCompare,
|
||||
this.hass.locale,
|
||||
{
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
}
|
||||
)}
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td class="mdc-data-table__cell"></td>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
|
@@ -215,8 +215,6 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
line-height: 1.2;
|
||||
padding-top: 16px;
|
||||
padding-left: 8px;
|
||||
padding-inline-start: 8px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -6,8 +6,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
@@ -55,8 +54,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
return { type: "entities", entities: foundEntities };
|
||||
}
|
||||
|
||||
@property() public editMode?: boolean | any;
|
||||
|
||||
@state() private _config?: EntitiesCardConfig;
|
||||
|
||||
private _hass?: HomeAssistant;
|
||||
@@ -220,15 +217,9 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
`}
|
||||
</h1>
|
||||
`}
|
||||
<div
|
||||
id="states"
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
highlight: this.editMode?.selectedRow !== undefined,
|
||||
})}
|
||||
>
|
||||
${this._configEntities!.map((entityConf, idx) =>
|
||||
this.renderEntity(entityConf, idx)
|
||||
<div id="states" class="card-content">
|
||||
${this._configEntities!.map((entityConf) =>
|
||||
this.renderEntity(entityConf)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -281,12 +272,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
#states > div {
|
||||
position: relative;
|
||||
}
|
||||
#states.highlight > div.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
#states.highlight > div {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0px 18px 0px 8px;
|
||||
@@ -308,10 +293,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderEntity(
|
||||
entityConf: LovelaceRowConfig,
|
||||
idx: number
|
||||
): TemplateResult {
|
||||
private renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
|
||||
const element = createRowElement(
|
||||
(!("type" in entityConf) || entityConf.type === "conditional") &&
|
||||
this._config!.state_color
|
||||
@@ -325,13 +307,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
element.hass = this._hass;
|
||||
}
|
||||
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
selected: this.editMode?.selectedRow === idx,
|
||||
})}
|
||||
>
|
||||
${element}
|
||||
</div>`;
|
||||
return html`<div>${element}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -372,22 +372,16 @@ class HuiShoppingListCard
|
||||
|
||||
.addButton {
|
||||
padding-right: 16px;
|
||||
padding-inline-end: 16px;
|
||||
cursor: pointer;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.reorderButton {
|
||||
padding-left: 16px;
|
||||
padding-inline-start: 16px;
|
||||
cursor: pointer;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
ha-checkbox {
|
||||
margin-left: -12px;
|
||||
margin-inline-start: -12px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
@@ -29,7 +28,7 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public editMode?: any;
|
||||
@property() public editMode?: boolean;
|
||||
|
||||
@property() protected _cards?: LovelaceCard[];
|
||||
|
||||
@@ -44,16 +43,8 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
this._cards = config.cards.map((card, idx) => {
|
||||
this._cards = config.cards.map((card) => {
|
||||
const element = this._createCardElement(card) as LovelaceCard;
|
||||
if (this.editMode !== undefined) {
|
||||
if (this.editMode?.selected === idx) {
|
||||
element.classList.add("selected");
|
||||
element.editMode = this.editMode.data;
|
||||
} else {
|
||||
element.editMode = true;
|
||||
}
|
||||
}
|
||||
return element;
|
||||
});
|
||||
}
|
||||
@@ -67,18 +58,12 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [idx, element] of this._cards.entries()) {
|
||||
for (const element of this._cards) {
|
||||
if (this.hass) {
|
||||
element.hass = this.hass;
|
||||
}
|
||||
if (this.editMode !== undefined) {
|
||||
if (this.editMode.selected === idx) {
|
||||
element.editMode = this.editMode.data ?? true;
|
||||
element.classList.add("selected");
|
||||
} else {
|
||||
element.editMode = true;
|
||||
element.classList.remove("selected");
|
||||
}
|
||||
element.editMode = this.editMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,12 +77,7 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
: ""}
|
||||
<div
|
||||
id="root"
|
||||
class=${classMap({ highlight: this.editMode?.selected !== undefined })}
|
||||
>
|
||||
${this._cards}
|
||||
</div>
|
||||
<div id="root">${this._cards}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -115,12 +95,6 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
||||
display: block;
|
||||
padding: 24px 16px 16px;
|
||||
}
|
||||
#root.highlight > *.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
#root.highlight > * {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -54,9 +54,7 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
|
||||
| "brightness";
|
||||
action_name?: string;
|
||||
service?: string;
|
||||
// "service_data" is kept for backwards compatibility. Replaced by "data".
|
||||
service_data?: Record<string, unknown>;
|
||||
data?: Record<string, unknown>;
|
||||
url?: string;
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
|
@@ -6,20 +6,16 @@ export const EXCLUDED_DOMAINS = ["zone", "persistent_notification"];
|
||||
const addFromAction = (entities: Set<string>, actionConfig: ActionConfig) => {
|
||||
if (
|
||||
actionConfig.action !== "call-service" ||
|
||||
(!actionConfig.target?.entity_id &&
|
||||
!actionConfig.service_data?.entity_id &&
|
||||
!actionConfig.data?.entity_id)
|
||||
!actionConfig.service_data ||
|
||||
!actionConfig.service_data.entity_id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let entityIds =
|
||||
actionConfig.service_data?.entity_id ??
|
||||
actionConfig.data?.entity_id ??
|
||||
actionConfig.target?.entity_id;
|
||||
let entityIds = actionConfig.service_data.entity_id;
|
||||
if (!Array.isArray(entityIds)) {
|
||||
entityIds = [entityIds];
|
||||
}
|
||||
for (const entityId of entityIds as Array<string>) {
|
||||
for (const entityId of entityIds) {
|
||||
entities.add(entityId);
|
||||
}
|
||||
};
|
||||
|
@@ -148,7 +148,7 @@ export const handleAction = async (
|
||||
hass.callService(
|
||||
domain,
|
||||
service,
|
||||
actionConfig.data ?? actionConfig.service_data,
|
||||
actionConfig.service_data,
|
||||
actionConfig.target
|
||||
);
|
||||
forwardHaptic("light");
|
||||
|
@@ -45,7 +45,7 @@ export class HuiActionEditor extends LitElement {
|
||||
private _serviceAction = memoizeOne(
|
||||
(config: CallServiceActionConfig): ServiceAction => ({
|
||||
service: this._service,
|
||||
data: config.data ?? config.service_data,
|
||||
data: config.service_data,
|
||||
target: config.target,
|
||||
})
|
||||
);
|
||||
@@ -179,18 +179,14 @@ export class HuiActionEditor extends LitElement {
|
||||
|
||||
private _serviceValueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = {
|
||||
...this.config!,
|
||||
service: ev.detail.value.service || "",
|
||||
data: ev.detail.value.data || {},
|
||||
target: ev.detail.value.target || {},
|
||||
};
|
||||
// "service_data" is allowed for backwards compatibility but replaced with "data" on write
|
||||
if ("service_data" in value) {
|
||||
delete value.service_data;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config!,
|
||||
service: ev.detail.value.service || "",
|
||||
service_data: ev.detail.value.data || {},
|
||||
target: ev.detail.value.target || {},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -311,8 +311,6 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
ha-button-toggle-group {
|
||||
padding-left: 8px;
|
||||
padding-inline-start: 8px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
mwc-button {
|
||||
flex-shrink: 0;
|
||||
|
@@ -193,9 +193,6 @@ export class HuiEntityEditor extends LitElement {
|
||||
.add-entity {
|
||||
display: block;
|
||||
margin-left: 31px;
|
||||
margin-inline-start: 31px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.entity {
|
||||
display: flex;
|
||||
|
@@ -155,7 +155,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
</div>`
|
||||
: html``}
|
||||
${this.catchInteraction ?? !DOMAINS_INPUT_ROW.includes(domain)
|
||||
? html`<div
|
||||
? html` <div
|
||||
class="text-content ${classMap({
|
||||
pointer,
|
||||
})}"
|
||||
@@ -165,7 +165,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
||||
})}
|
||||
>
|
||||
<div class="state"><slot></slot></div>
|
||||
<slot></slot>
|
||||
</div>`
|
||||
: html`<slot></slot>`}
|
||||
`;
|
||||
@@ -230,12 +230,6 @@ class HuiGenericEntityRow extends LitElement {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.state {
|
||||
text-align: right;
|
||||
}
|
||||
.state.rtl {
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -12,8 +12,6 @@ export class HuiCardPreview extends ReactiveElement {
|
||||
|
||||
@property() public config?: LovelaceCardConfig;
|
||||
|
||||
@property() public editMode = true;
|
||||
|
||||
private _element?: LovelaceCard;
|
||||
|
||||
private get _error() {
|
||||
@@ -83,9 +81,6 @@ export class HuiCardPreview extends ReactiveElement {
|
||||
this._element.hass = this.hass;
|
||||
}
|
||||
}
|
||||
if (changedProperties.has("editMode")) {
|
||||
this._element!.editMode = this.editMode;
|
||||
}
|
||||
}
|
||||
|
||||
private _createCard(configValue: LovelaceCardConfig): void {
|
||||
@@ -95,7 +90,6 @@ export class HuiCardPreview extends ReactiveElement {
|
||||
if (this.hass) {
|
||||
this._element!.hass = this.hass;
|
||||
}
|
||||
this._element!.editMode = this.editMode;
|
||||
|
||||
this.appendChild(this._element!);
|
||||
}
|
||||
|
@@ -43,13 +43,6 @@ declare global {
|
||||
interface HTMLElementEventMap {
|
||||
"reload-lovelace": HASSDomEvent<undefined>;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"edit-mode-changed": any;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"edit-mode-changed": HASSDomEvent<any>;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-dialog-edit-card")
|
||||
@@ -84,13 +77,10 @@ export class HuiDialogEditCard
|
||||
|
||||
@state() private _isEscapeEnabled = true;
|
||||
|
||||
@state() private _editMode = true;
|
||||
|
||||
public async showDialog(params: EditCardDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._GUImode = true;
|
||||
this._guiModeAvailable = true;
|
||||
this._editMode = true;
|
||||
const [view, card] = params.path;
|
||||
this._viewConfig = params.lovelaceConfig.views[view];
|
||||
this._cardConfig =
|
||||
@@ -215,7 +205,6 @@ export class HuiDialogEditCard
|
||||
.lovelace=${this._params.lovelaceConfig}
|
||||
.value=${this._cardConfig}
|
||||
@config-changed=${this._handleConfigChanged}
|
||||
@edit-mode-changed=${this._handleEditModeChanged}
|
||||
@GUImode-changed=${this._handleGUIModeChanged}
|
||||
@editor-save=${this._save}
|
||||
dialogInitialFocus
|
||||
@@ -225,7 +214,6 @@ export class HuiDialogEditCard
|
||||
<hui-card-preview
|
||||
.hass=${this.hass}
|
||||
.config=${this._cardConfig}
|
||||
.editMode=${this._editMode}
|
||||
class=${this._error ? "blur" : ""}
|
||||
></hui-card-preview>
|
||||
${this._error
|
||||
@@ -296,10 +284,6 @@ export class HuiDialogEditCard
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private _handleEditModeChanged(ev: HASSDomEvent<any>) {
|
||||
this._editMode = ev.detail ?? true;
|
||||
}
|
||||
|
||||
private _handleGUIModeChanged(ev: HASSDomEvent<GUIModeChangedEvent>): void {
|
||||
ev.stopPropagation();
|
||||
this._GUImode = ev.detail.guiMode;
|
||||
|
@@ -49,7 +49,6 @@ import {
|
||||
SubElementEditorConfig,
|
||||
} from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { buttonEntityConfigStruct } from "../structs/button-entity-struct";
|
||||
|
||||
const buttonEntitiesRowConfigStruct = object({
|
||||
type: literal("button"),
|
||||
@@ -77,9 +76,7 @@ const callServiceEntitiesRowConfigStruct = object({
|
||||
service: string(),
|
||||
icon: optional(string()),
|
||||
action_name: optional(string()),
|
||||
// "service_data" is kept for backwards compatibility. Replaced by "data".
|
||||
service_data: optional(any()),
|
||||
data: optional(any()),
|
||||
});
|
||||
|
||||
const conditionalEntitiesRowConfigStruct = object({
|
||||
@@ -113,7 +110,22 @@ const webLinkEntitiesRowConfigStruct = object({
|
||||
|
||||
const buttonsEntitiesRowConfigStruct = object({
|
||||
type: literal("buttons"),
|
||||
entities: array(buttonEntityConfigStruct),
|
||||
entities: array(
|
||||
union([
|
||||
object({
|
||||
entity: string(),
|
||||
name: optional(string()),
|
||||
icon: optional(string()),
|
||||
image: optional(string()),
|
||||
show_name: optional(boolean()),
|
||||
show_icon: optional(boolean()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
}),
|
||||
string(),
|
||||
])
|
||||
),
|
||||
});
|
||||
|
||||
const attributeEntitiesRowConfigStruct = object({
|
||||
@@ -408,15 +420,10 @@ export class HuiEntitiesCardEditor
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditSubElementEvent>): void {
|
||||
this._subElementEditorConfig = ev.detail.subElementConfig;
|
||||
|
||||
fireEvent(this, "edit-mode-changed", {
|
||||
selectedRow: ev.detail.subElementConfig?.index,
|
||||
});
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
this._subElementEditorConfig = undefined;
|
||||
fireEvent(this, "edit-mode-changed", true);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -161,8 +161,6 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
|
||||
css`
|
||||
.geo_location_sources {
|
||||
padding-left: 20px;
|
||||
padding-inline-start: 20px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -144,7 +144,6 @@ export class HuiStackCardEditor
|
||||
.lovelace=${this.lovelace}
|
||||
@config-changed=${this._handleConfigChanged}
|
||||
@GUImode-changed=${this._handleGUIModeChanged}
|
||||
@edit-mode-changed=${this._handleEditModeChanged}
|
||||
></hui-card-element-editor>
|
||||
`
|
||||
: html`
|
||||
@@ -167,9 +166,6 @@ export class HuiStackCardEditor
|
||||
this._setMode(true);
|
||||
this._guiModeAvailable = true;
|
||||
this._selectedCard = parseInt(ev.detail.selected, 10);
|
||||
if (this._cardEditorEl) {
|
||||
this._cardEditorEl.forceRebuild = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) {
|
||||
@@ -230,14 +226,6 @@ export class HuiStackCardEditor
|
||||
this._guiModeAvailable = ev.detail.guiModeAvailable;
|
||||
}
|
||||
|
||||
protected _handleEditModeChanged(ev: HASSDomEvent<any>) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "edit-mode-changed", {
|
||||
selected: this._selectedCard,
|
||||
data: ev.detail,
|
||||
});
|
||||
}
|
||||
|
||||
protected _toggleMode(): void {
|
||||
this._cardEditorEl?.toggleMode();
|
||||
}
|
||||
@@ -249,13 +237,6 @@ export class HuiStackCardEditor
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("_selectedCard"))
|
||||
fireEvent(this, "edit-mode-changed", {
|
||||
selected: this._selectedCard,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
configElementStyle,
|
||||
|
@@ -53,8 +53,6 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public lovelace?: LovelaceConfig;
|
||||
|
||||
public forceRebuild = false;
|
||||
|
||||
@state() private _yaml?: string;
|
||||
|
||||
@state() private _config?: T;
|
||||
@@ -294,11 +292,7 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
this._errors = undefined;
|
||||
this._warnings = undefined;
|
||||
|
||||
if (
|
||||
this._configElementType !== this.configElementType ||
|
||||
this.forceRebuild
|
||||
) {
|
||||
this.forceRebuild = false;
|
||||
if (this._configElementType !== this.configElementType) {
|
||||
// If the type has changed, we need to load a new GUI editor
|
||||
this._guiSupported = undefined;
|
||||
this._configElement = undefined;
|
||||
|
@@ -261,9 +261,6 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
display: block;
|
||||
margin-left: 31px;
|
||||
margin-right: 71px;
|
||||
margin-inline-start: 31px;
|
||||
margin-inline-end: 71px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.entity {
|
||||
display: flex;
|
||||
@@ -273,9 +270,6 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
.entity .handle {
|
||||
padding-right: 8px;
|
||||
cursor: move;
|
||||
padding-inline-end: 8px;
|
||||
padding-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.entity ha-entity-picker {
|
||||
|
@@ -1,16 +1,14 @@
|
||||
import {
|
||||
array,
|
||||
boolean,
|
||||
dynamic,
|
||||
enums,
|
||||
literal,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
type,
|
||||
union,
|
||||
boolean,
|
||||
optional,
|
||||
array,
|
||||
literal,
|
||||
enums,
|
||||
type,
|
||||
} from "superstruct";
|
||||
import { BaseActionConfig } from "../../../../data/lovelace";
|
||||
|
||||
const actionConfigStructUser = object({
|
||||
user: string(),
|
||||
@@ -34,7 +32,6 @@ const actionConfigStructService = object({
|
||||
action: literal("call-service"),
|
||||
service: string(),
|
||||
service_data: optional(object()),
|
||||
data: optional(object()),
|
||||
target: optional(
|
||||
object({
|
||||
entity_id: optional(union([string(), array(string())])),
|
||||
@@ -67,23 +64,10 @@ export const actionConfigStructType = object({
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
});
|
||||
|
||||
export const actionConfigStruct = dynamic<any>((value) => {
|
||||
if (value && typeof value === "object" && "action" in value) {
|
||||
switch ((value as BaseActionConfig).action!) {
|
||||
case "call-service": {
|
||||
return actionConfigStructService;
|
||||
}
|
||||
case "fire-dom-event": {
|
||||
return actionConfigStructCustom;
|
||||
}
|
||||
case "navigate": {
|
||||
return actionConfigStructNavigate;
|
||||
}
|
||||
case "url": {
|
||||
return actionConfigStructUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actionConfigStructType;
|
||||
});
|
||||
export const actionConfigStruct = union([
|
||||
actionConfigStructType,
|
||||
actionConfigStructUrl,
|
||||
actionConfigStructNavigate,
|
||||
actionConfigStructService,
|
||||
actionConfigStructCustom,
|
||||
]);
|
||||
|
@@ -1,14 +0,0 @@
|
||||
import { boolean, object, optional, string } from "superstruct";
|
||||
import { actionConfigStruct } from "./action-struct";
|
||||
|
||||
export const buttonEntityConfigStruct = object({
|
||||
entity: string(),
|
||||
name: optional(string()),
|
||||
icon: optional(string()),
|
||||
image: optional(string()),
|
||||
show_name: optional(boolean()),
|
||||
show_icon: optional(boolean()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
});
|
@@ -1,15 +1,6 @@
|
||||
import {
|
||||
array,
|
||||
dynamic,
|
||||
number,
|
||||
object,
|
||||
optional,
|
||||
union,
|
||||
string,
|
||||
} from "superstruct";
|
||||
import { object, string, optional, array, number, union } from "superstruct";
|
||||
import { actionConfigStruct } from "../editor/structs/action-struct";
|
||||
import { buttonEntityConfigStruct } from "../editor/structs/button-entity-struct";
|
||||
import { LovelaceHeaderFooterConfig } from "./types";
|
||||
import { entitiesConfigStruct } from "../editor/structs/entities-struct";
|
||||
|
||||
export const pictureHeaderFooterConfigStruct = object({
|
||||
type: string(),
|
||||
@@ -21,7 +12,7 @@ export const pictureHeaderFooterConfigStruct = object({
|
||||
|
||||
export const buttonsHeaderFooterConfigStruct = object({
|
||||
type: string(),
|
||||
entities: array(buttonEntityConfigStruct),
|
||||
entities: array(entitiesConfigStruct),
|
||||
});
|
||||
|
||||
export const graphHeaderFooterConfigStruct = object({
|
||||
@@ -31,25 +22,8 @@ export const graphHeaderFooterConfigStruct = object({
|
||||
hours_to_show: optional(number()),
|
||||
});
|
||||
|
||||
export const headerFooterConfigStructs = dynamic<any>((value) => {
|
||||
if (value && typeof value === "object" && "type" in value) {
|
||||
switch ((value as LovelaceHeaderFooterConfig).type!) {
|
||||
case "buttons": {
|
||||
return buttonsHeaderFooterConfigStruct;
|
||||
}
|
||||
case "graph": {
|
||||
return graphHeaderFooterConfigStruct;
|
||||
}
|
||||
case "picture": {
|
||||
return pictureHeaderFooterConfigStruct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No "type" property => we fallback to a union of all potential types
|
||||
return union([
|
||||
buttonsHeaderFooterConfigStruct,
|
||||
graphHeaderFooterConfigStruct,
|
||||
pictureHeaderFooterConfigStruct,
|
||||
]);
|
||||
});
|
||||
export const headerFooterConfigStructs = union([
|
||||
pictureHeaderFooterConfigStruct,
|
||||
buttonsHeaderFooterConfigStruct,
|
||||
graphHeaderFooterConfigStruct,
|
||||
]);
|
||||
|
@@ -946,10 +946,8 @@ class HUIRoot extends LitElement {
|
||||
.edit-icon {
|
||||
color: var(--accent-color);
|
||||
padding-left: 8px;
|
||||
padding-inline-start: 8px;
|
||||
vertical-align: middle;
|
||||
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
.edit-icon.view {
|
||||
display: none;
|
||||
|
@@ -23,7 +23,7 @@ export class HuiCallServiceRow extends HuiButtonRow {
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
service: callServiceConfig.service,
|
||||
data: callServiceConfig.service_data,
|
||||
service_data: callServiceConfig.service_data,
|
||||
},
|
||||
...callServiceConfig,
|
||||
type: "button",
|
||||
|
@@ -38,7 +38,7 @@ export interface LovelaceBadge extends HTMLElement {
|
||||
export interface LovelaceCard extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
isPanel?: boolean;
|
||||
editMode?: any;
|
||||
editMode?: boolean;
|
||||
getCardSize(): number | Promise<number>;
|
||||
setConfig(config: LovelaceCardConfig): void;
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
mdiStop,
|
||||
mdiVolumeHigh,
|
||||
} from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -33,7 +32,6 @@ import "../../components/ha-button-menu";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-icon-button";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import { subscribeEntityRegistry } from "../../data/entity_registry";
|
||||
import {
|
||||
BROWSER_PLAYER,
|
||||
cleanupMediaTitle,
|
||||
@@ -53,7 +51,6 @@ import {
|
||||
} from "../../data/media-player";
|
||||
import { ResolvedMediaSource } from "../../data/media_source";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../lovelace/components/hui-marquee";
|
||||
import {
|
||||
@@ -68,7 +65,7 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement("ha-bar-media-player")
|
||||
export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
export class BarMediaPlayer extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
@@ -86,9 +83,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _browserPlayer?: BrowserMediaPlayer;
|
||||
|
||||
@state()
|
||||
private _hiddenEntities = new Set<string>();
|
||||
|
||||
private _progressInterval?: number;
|
||||
|
||||
private _browserPlayerVolume = 0.8;
|
||||
@@ -467,8 +461,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
return Object.values(this.hass!.states).filter(
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "media_player" &&
|
||||
supportsFeature(entity, SUPPORT_BROWSE_MEDIA) &&
|
||||
!this._hiddenEntities.has(entity.entity_id)
|
||||
supportsFeature(entity, SUPPORT_BROWSE_MEDIA)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -494,28 +487,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
protected override hassSubscribe(): (
|
||||
| UnsubscribeFunc
|
||||
| Promise<UnsubscribeFunc>
|
||||
)[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
const hiddenEntities = new Set<string>();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (
|
||||
entry.hidden_by &&
|
||||
computeDomain(entry.entity_id) === "media_player"
|
||||
) {
|
||||
hiddenEntities.add(entry.entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
this._hiddenEntities = hiddenEntities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _handleControlClick(e: MouseEvent): void {
|
||||
const action = (e.currentTarget! as HTMLElement).getAttribute("action")!;
|
||||
|
||||
|
@@ -123,7 +123,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/resources",
|
||||
},
|
||||
oauth: {
|
||||
oauth2_authorize_callback: {
|
||||
redirect: "/auth/external/callback",
|
||||
navigate_outside_spa: true,
|
||||
params: {
|
||||
|
@@ -158,7 +158,6 @@ export const buttonLinkStyle = css`
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -90,8 +90,6 @@ export class StateCardDisplay extends LitElement {
|
||||
text-align: right;
|
||||
flex: 0 0 auto;
|
||||
overflow-wrap: break-word;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
:host([rtl]) .state {
|
||||
margin-right: 16px;
|
||||
|
@@ -24,9 +24,8 @@ class StateCardInputNumber extends mixinBehaviors(
|
||||
@apply --paper-font-body1;
|
||||
color: var(--primary-text-color);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
text-align: right;
|
||||
line-height: 40px;
|
||||
}
|
||||
.sliderstate {
|
||||
min-width: 45px;
|
||||
|
@@ -23,9 +23,8 @@ class StateCardNumber extends mixinBehaviors(
|
||||
@apply --paper-font-body1;
|
||||
color: var(--primary-text-color);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
text-align: right;
|
||||
line-height: 40px;
|
||||
}
|
||||
.sliderstate {
|
||||
min-width: 45px;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user