Compare commits

..

1 Commits

Author SHA1 Message Date
Zack
ef92ed927e Bumped version to 20220523.0 2022-05-23 18:24:56 -05:00
103 changed files with 1482 additions and 2611 deletions

View File

@@ -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.

View File

@@ -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
View File

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

View File

@@ -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");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
View 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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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"] {

View File

@@ -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}`
);

View File

@@ -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;

View File

@@ -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;
}
`,
];

View File

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

View File

@@ -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 {

View File

@@ -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 {

View File

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

View File

@@ -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}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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 = (

View File

@@ -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
);
};

View File

@@ -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;
}

View File

@@ -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 = (

View File

@@ -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 => {

View File

@@ -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;
}
`;
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;
}
`,

View File

@@ -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(

View File

@@ -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}`}

View File

@@ -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];
}
}

View File

@@ -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 }),
},
];

View File

@@ -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;
}
`,
];
}
}

View File

@@ -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;
};

View File

@@ -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;
}
`,
];
}
}

View File

@@ -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;
}
`,
];
}

View File

@@ -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,
}),
},
];
};

View File

@@ -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,
}));
};

View File

@@ -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;
}
`,
];
}
}

View File

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

View File

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

View File

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

View File

@@ -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);
}
`,
];

View File

@@ -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;
}
`,
];

View File

@@ -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(

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

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

View File

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

View File

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

View File

@@ -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);
}
`,
];

View File

@@ -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 {

View File

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

View File

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

View File

@@ -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);
}
`;
}

View File

@@ -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>`;
}
}

View File

@@ -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 {

View File

@@ -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;
}
`;
}

View File

@@ -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;

View File

@@ -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);
}
};

View File

@@ -148,7 +148,7 @@ export const handleAction = async (
hass.callService(
domain,
service,
actionConfig.data ?? actionConfig.service_data,
actionConfig.service_data,
actionConfig.target
);
forwardHaptic("light");

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
`;
}
}

View File

@@ -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!);
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);
}
`,
];

View File

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

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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,
]);

View File

@@ -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),
});

View File

@@ -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,
]);

View File

@@ -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;

View File

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

View File

@@ -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;
}

View File

@@ -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")!;

View File

@@ -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: {

View File

@@ -158,7 +158,6 @@ export const buttonLinkStyle = css`
text-align: left;
text-decoration: underline;
cursor: pointer;
outline: none;
}
`;

View File

@@ -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;

View File

@@ -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;

View File

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