20230223.0 (#15575)

This commit is contained in:
Bram Kragten 2023-02-23 19:01:26 +01:00 committed by GitHub
commit dd6437376d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 873 additions and 552 deletions

View File

@ -60,7 +60,6 @@
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off",
"no-unsafe-optional-chaining": "warn",
"prefer-regex-literals": ["warn"],
"import/prefer-default-export": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",

View File

@ -252,6 +252,22 @@ export class HcMain extends HassElement {
msg.urlPath = null;
}
this._lovelacePath = msg.viewPath;
if (msg.urlPath === "energy") {
this._lovelaceConfig = {
views: [
{
strategy: {
type: "energy",
options: { show_date_selection: true },
},
},
],
};
this._urlPath = "energy";
this._lovelacePath = 0;
this._sendStatus();
return;
}
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath;
this._lovelaceConfig = undefined;

View File

@ -66,7 +66,7 @@ const incrementalUnits = ["clients", "queries", "ads"];
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(
new RegExp("history/period/.+"),
/history\/period\/.+/,
(hass, _method, path, _parameters) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");

View File

@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
<div class="action">
<span>
${this._action
? describeAction(this.hass, this._action)
? describeAction(this.hass, [], this._action)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
@ -149,7 +149,7 @@ export class DemoAutomationDescribeAction extends LitElement {
${ACTIONS.map(
(conf) => html`
<div class="action">
<span>${describeAction(this.hass, conf as any)}</span>
<span>${describeAction(this.hass, [], conf as any)}</span>
<pre>${dump(conf)}</pre>
</div>
`

View File

@ -71,7 +71,7 @@
"@material/mwc-textfield": "^0.27.0",
"@material/mwc-top-app-bar-fixed": "^0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "=1.0.0-pre.2",
"@material/web": "=1.0.0-pre.3",
"@mdi/js": "7.1.96",
"@mdi/svg": "7.1.96",
"@polymer/app-layout": "^3.1.0",
@ -216,7 +216,7 @@
"lint-staged": "^13.1.2",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.29.0",
"magic-string": "^0.30.0",
"map-stream": "^0.0.7",
"merge-stream": "^2.0.0",
"mocha": "^10.2.0",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20230222.0"
version = "20230223.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -1,5 +1,5 @@
import { HassEntity } from "home-assistant-js-websocket";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { HomeAssistant } from "../../types";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
@ -15,7 +15,7 @@ export const computeAttributeValueDisplay = (
const attributeValue =
value !== undefined ? value : stateObj.attributes[attribute];
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
const translationKey = entity?.translation_key;
return (
@ -38,7 +38,7 @@ export const computeAttributeNameDisplay = (
): string => {
const entityId = stateObj.entity_id;
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
const translationKey = entity?.translation_key;
return (

View File

@ -1,6 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { FrontendLocaleData } from "../../data/translation";
import {
updateIsInstallingFromAttributes,
@ -49,7 +49,7 @@ export const computeStateDisplayFromEntityAttributes = (
return localize(`state.default.${state}`);
}
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
if (isNumericFromAttributes(attributes)) {

View File

@ -2,7 +2,7 @@ import {
HassEntity,
HassEntityAttributeBase,
} from "home-assistant-js-websocket";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
import { round } from "./round";
@ -92,11 +92,9 @@ export const formatNumber = (
*/
export const getNumberFormatOptions = (
entityState: HassEntity,
entity?: EntityRegistryEntry
entity?: EntityRegistryDisplayEntry
): Intl.NumberFormatOptions | undefined => {
const precision =
entity?.options?.sensor?.display_precision ??
entity?.options?.sensor?.suggested_display_precision;
const precision = entity?.display_precision;
if (precision != null) {
return {
maximumFractionDigits: precision,

View File

@ -1,4 +1,4 @@
const isTemplateRegex = new RegExp("{%|{{");
const isTemplateRegex = /{%|{{/;
export const isTemplate = (value: string): boolean =>
isTemplateRegex.test(value);

View File

@ -22,7 +22,7 @@ import {
isNumericState,
} from "../../common/number/format_number";
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { timerTimeRemaining } from "../../data/timer";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
@ -160,7 +160,7 @@ export class HaStateLabelBadge extends LitElement {
private _computeValue(
domain: string,
entityState: HassEntity,
entry?: EntityRegistryEntry
entry?: EntityRegistryDisplayEntry
) {
switch (domain) {
case "alarm_control_panel":
@ -200,7 +200,7 @@ export class HaStateLabelBadge extends LitElement {
private _computeShowIcon(
domain: string,
entityState: HassEntity,
entry?: EntityRegistryEntry
entry?: EntityRegistryDisplayEntry
): boolean {
if (entityState.state === UNAVAILABLE) {
return false;

View File

@ -12,10 +12,11 @@ import {
createAreaRegistryEntry,
} from "../data/area_registry";
import {
DeviceEntityLookup,
DeviceEntityDisplayLookup,
DeviceRegistryEntry,
getDeviceEntityDisplayLookup,
} from "../data/device_registry";
import { EntityRegistryEntry } from "../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
import {
showAlertDialog,
showPromptDialog,
@ -113,7 +114,7 @@ export class HaAreaPicker extends LitElement {
(
areas: AreaRegistryEntry[],
devices: DeviceRegistryEntry[],
entities: EntityRegistryEntry[],
entities: EntityRegistryDisplayEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"],
@ -133,111 +134,107 @@ export class HaAreaPicker extends LitElement {
];
}
const deviceEntityLookup: DeviceEntityLookup = {};
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
let inputDevices: DeviceRegistryEntry[] | undefined;
let inputEntities: EntityRegistryEntry[] | undefined;
let inputEntities: EntityRegistryDisplayEntry[] | undefined;
if (
includeDomains ||
excludeDomains ||
includeDeviceClasses ||
deviceFilter ||
entityFilter
) {
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
if (includeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
if (includeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
}
}
if (excludeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
if (excludeDomains) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
inputEntities = inputEntities!.filter(
(entity) => !excludeDomains.includes(computeDomain(entity.entity_id))
);
}
}
if (includeDeviceClasses) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
if (includeDeviceClasses) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
}
if (deviceFilter) {
inputDevices = inputDevices!.filter((device) =>
deviceFilter!(device)
);
});
}
}
if (deviceFilter) {
inputDevices = inputDevices!.filter((device) => deviceFilter!(device));
}
if (entityFilter) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
if (entityFilter) {
inputDevices = inputDevices!.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter(stateObj);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter(stateObj);
return entityFilter!(stateObj);
});
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter!(stateObj);
});
}
}
let outputAreas = areas;

View File

@ -1,3 +1,10 @@
import {
DIRECTION_HORIZONTAL,
DIRECTION_VERTICAL,
Manager,
Swipe,
Tap,
} from "@egjs/hammerjs";
import {
css,
CSSResultGroup,
@ -6,7 +13,7 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-svg-icon";
@ -30,8 +37,11 @@ export class HaControlSwitch extends LitElement {
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
@property({ type: String }) pathOff?: string;
private _mc?: HammerManager;
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this.setupListeners();
this.setAttribute("role", "switch");
if (!this.hasAttribute("tabindex")) {
this.setAttribute("tabindex", "0");
@ -53,14 +63,70 @@ export class HaControlSwitch extends LitElement {
connectedCallback(): void {
super.connectedCallback();
this.addEventListener("keydown", this._keydown);
this.addEventListener("click", this._toggle);
this.setupListeners();
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.destroyListeners();
}
@query("#switch")
private switch!: HTMLDivElement;
setupListeners() {
if (this.switch && !this._mc) {
this._mc = new Manager(this.switch, {
touchAction: this.vertical ? "pan-x" : "pan-y",
});
this._mc.add(
new Swipe({
direction: this.vertical ? DIRECTION_VERTICAL : DIRECTION_HORIZONTAL,
})
);
this._mc.add(new Tap({ event: "singletap" }));
if (this.vertical) {
this._mc.on("swipeup", () => {
if (this.disabled) return;
this.checked = !!this.reversed;
fireEvent(this, "change");
});
this._mc.on("swipedown", () => {
if (this.disabled) return;
this.checked = !this.reversed;
fireEvent(this, "change");
});
} else {
this._mc.on("swiperight", () => {
if (this.disabled) return;
this.checked = !this.reversed;
fireEvent(this, "change");
});
this._mc.on("swipeleft", () => {
if (this.disabled) return;
this.checked = !!this.reversed;
fireEvent(this, "change");
});
}
this._mc.on("singletap", () => {
if (this.disabled) return;
this._toggle();
});
this.addEventListener("keydown", this._keydown);
}
}
destroyListeners() {
if (this._mc) {
this._mc.destroy();
this._mc = undefined;
}
this.removeEventListener("keydown", this._keydown);
this.removeEventListener("click", this._toggle);
}
private _keydown(ev: any) {
@ -73,7 +139,7 @@ export class HaControlSwitch extends LitElement {
protected render(): TemplateResult {
return html`
<div class="switch">
<div id="switch" class="switch">
<div class="background"></div>
<div class="button" aria-hidden="true">
${this.checked

View File

@ -45,6 +45,7 @@ export class HaDialog extends DialogBase {
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.contentElement.removeEventListener("scroll", this._onScroll);
}

View File

@ -26,6 +26,9 @@ export class Gauge extends LitElement {
@property({ type: Number }) public value = 0;
@property({ attribute: false })
public formatOptions?: Intl.NumberFormatOptions;
@property({ type: String }) public valueText?: string;
@property() public locale!: FrontendLocaleData;
@ -132,7 +135,8 @@ export class Gauge extends LitElement {
${
this._segment_label
? this._segment_label
: this.valueText || formatNumber(this.value, this.locale)
: this.valueText ||
formatNumber(this.value, this.locale, this.formatOptions)
}${
this._segment_label
? ""

View File

@ -1,14 +1,10 @@
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
@ -18,13 +14,12 @@ import {
filterSelectorDevices,
filterSelectorEntities,
} 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;
@ -41,18 +36,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
private _hasIntegration(selector: AreaSelector) {
return (
(selector.area?.entity &&
@ -127,10 +112,12 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true;
}
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
)
: undefined;
return ensureArray(this.selector.area.device).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)

View File

@ -2,12 +2,11 @@ import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { AttributeSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../entity/ha-entity-attribute-picker";
@customElement("ha-selector-attribute")
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
export class HaSelectorAttribute extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public selector!: AttributeSelector;

View File

@ -1,14 +1,10 @@
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
@ -18,21 +14,18 @@ import {
filterSelectorDevices,
filterSelectorEntities,
} 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;
@ -45,14 +38,6 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
private _hasIntegration(selector: DeviceSelector) {
return (
(selector.device?.filter &&
@ -118,10 +103,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
if (!this.selector.device?.filter) {
return true;
}
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
)
: undefined;
return ensureArray(this.selector.device.filter).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)

View File

@ -1,6 +1,7 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import "@material/mwc-button/mwc-button";
import "@material/mwc-menu/mwc-menu-surface";
import {
mdiClose,
mdiDevices,
@ -9,13 +10,14 @@ import {
mdiUnfoldMoreVertical,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import { ensureArray } from "../common/array/ensure-array";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
@ -23,7 +25,7 @@ import {
computeDeviceName,
DeviceRegistryEntry,
} from "../data/device_registry";
import { EntityRegistryEntry } from "../data/entity_registry";
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
import { HomeAssistant } from "../types";
import "./device/ha-device-picker";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
@ -33,8 +35,6 @@ import "./ha-area-picker";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-svg-icon";
import { stopPropagation } from "../common/dom/stop_propagation";
import "@material/mwc-menu/mwc-menu-surface";
@customElement("ha-target-picker")
export class HaTargetPicker extends LitElement {
@ -551,7 +551,7 @@ export class HaTargetPicker extends LitElement {
return true;
}
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean {
if (entity.entity_category) {
return false;
}

View File

@ -6,6 +6,7 @@ import {
mdiProgressWrench,
mdiRecordCircleOutline,
} from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
@ -14,12 +15,16 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import { relativeTime } from "../../common/datetime/relative_time";
import { fireEvent } from "../../common/dom/fire_event";
import { toggleAttribute } from "../../common/dom/toggle_attribute";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { LogbookEntry } from "../../data/logbook";
import {
ChooseAction,
@ -193,6 +198,7 @@ class ActionRenderer {
constructor(
private hass: HomeAssistant,
private entityReg: EntityRegistryEntry[],
private entries: TemplateResult[],
private trace: AutomationTraceExtended,
private logbookRenderer: LogbookRenderer,
@ -298,7 +304,7 @@ class ActionRenderer {
this._renderEntry(
path,
describeAction(this.hass, data, actionType),
describeAction(this.hass, this.entityReg, data, actionType),
undefined,
data.enabled === false
);
@ -441,7 +447,9 @@ class ActionRenderer {
) as RepeatAction;
const disabled = repeatConfig.enabled === false;
const name = repeatConfig.alias || describeAction(this.hass, repeatConfig);
const name =
repeatConfig.alias ||
describeAction(this.hass, this.entityReg, repeatConfig);
this._renderEntry(repeatPath, name, undefined, disabled);
@ -577,6 +585,16 @@ export class HaAutomationTracer extends LitElement {
@property({ type: Boolean }) public allowPick = false;
@state() private _entityReg: EntityRegistryEntry[] = [];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected render(): TemplateResult {
if (!this.trace) {
return html``;
@ -592,6 +610,7 @@ export class HaAutomationTracer extends LitElement {
);
const actionRenderer = new ActionRenderer(
this.hass,
this._entityReg,
entries,
this.trace,
logbookRenderer,

View File

@ -4,7 +4,10 @@ import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import type { HomeAssistant } from "../types";
import type { EntityRegistryEntry } from "./entity_registry";
import type {
EntityRegistryDisplayEntry,
EntityRegistryEntry,
} from "./entity_registry";
import type { EntitySources } from "./entity_sources";
export interface DeviceRegistryEntry {
@ -25,6 +28,10 @@ export interface DeviceRegistryEntry {
configuration_url: string | null;
}
export interface DeviceEntityDisplayLookup {
[deviceId: string]: EntityRegistryDisplayEntry[];
}
export interface DeviceEntityLookup {
[deviceId: string]: EntityRegistryEntry[];
}
@ -147,9 +154,25 @@ export const getDeviceEntityLookup = (
return deviceEntityLookup;
};
export const getDeviceEntityDisplayLookup = (
entities: EntityRegistryDisplayEntry[]
): DeviceEntityDisplayLookup => {
const deviceEntityLookup: DeviceEntityDisplayLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
return deviceEntityLookup;
};
export const getDeviceIntegrationLookup = (
entitySources: EntitySources,
entities: EntityRegistryEntry[]
entities: EntityRegistryDisplayEntry[]
): Record<string, string[]> => {
const deviceIntegrations: Record<string, string[]> = {};

View File

@ -6,6 +6,35 @@ import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
type entityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry {
entity_id: string;
name?: string;
device_id?: string;
area_id?: string;
hidden?: boolean;
entity_category?: entityCategory;
translation_key?: string;
platform?: string;
display_precision?: number;
}
interface EntityRegistryDisplayEntryResponse {
entities: {
ei: string;
di?: string;
ai?: string;
ec?: number;
en?: string;
pl?: string;
tk?: string;
hb?: boolean;
dp?: number;
}[];
entity_categories: Record<number, entityCategory>;
}
export interface EntityRegistryEntry {
id: string;
entity_id: string;
@ -17,7 +46,7 @@ export interface EntityRegistryEntry {
area_id: string | null;
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: "config" | "diagnostic" | null;
entity_category: entityCategory | null;
has_entity_name: boolean;
original_name?: string;
unique_id: string;
@ -154,6 +183,11 @@ export const fetchEntityRegistry = (conn: Connection) =>
type: "config/entity_registry/list",
});
export const fetchEntityRegistryDisplay = (conn: Connection) =>
conn.sendMessagePromise<EntityRegistryDisplayEntryResponse>({
type: "config/entity_registry/list_for_display",
});
const subscribeEntityRegistryUpdates = (
conn: Connection,
store: Store<EntityRegistryEntry[]>
@ -182,6 +216,34 @@ export const subscribeEntityRegistry = (
onChange
);
const subscribeEntityRegistryDisplayUpdates = (
conn: Connection,
store: Store<EntityRegistryDisplayEntryResponse>
) =>
conn.subscribeEvents(
debounce(
() =>
fetchEntityRegistryDisplay(conn).then((entities) =>
store.setState(entities, true)
),
500,
true
),
"entity_registry_updated"
);
export const subscribeEntityRegistryDisplay = (
conn: Connection,
onChange: (entities: EntityRegistryDisplayEntryResponse) => void
) =>
createCollection<EntityRegistryDisplayEntryResponse>(
"_entityRegistryDisplay",
fetchEntityRegistryDisplay,
subscribeEntityRegistryDisplayUpdates,
conn,
onChange
);
export const sortEntityRegistryByName = (
entries: EntityRegistryEntry[],
language: string
@ -190,10 +252,20 @@ export const sortEntityRegistryByName = (
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
);
export const entityRegistryByEntityId = memoizeOne(
(entries: EntityRegistryEntry[]) => {
const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of entries) {
entities[entity.entity_id] = entity;
}
return entities;
}
);
export const entityRegistryById = memoizeOne(
(entries: HomeAssistant["entities"]) => {
const entities: HomeAssistant["entities"] = {};
for (const entity of Object.values(entries)) {
(entries: EntityRegistryEntry[]) => {
const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of entries) {
entities[entity.id] = entity;
}
return entities;

View File

@ -11,6 +11,7 @@ import { computeDeviceName } from "./device_registry";
import {
computeEntityRegistryName,
entityRegistryById,
EntityRegistryEntry,
} from "./entity_registry";
import { domainToName } from "./integration";
import {
@ -33,6 +34,7 @@ import {
export const describeAction = <T extends ActionType>(
hass: HomeAssistant,
entityRegistry: EntityRegistryEntry[],
action: ActionTypes[T],
actionType?: T,
ignoreAlias = false
@ -91,7 +93,7 @@ export const describeAction = <T extends ActionType>(
targets.push(targetThing);
}
} else {
const entityReg = entityRegistryById(hass.entities)[targetThing];
const entityReg = entityRegistryById(entityRegistry)[targetThing];
if (entityReg) {
targets.push(
computeEntityRegistryName(hass, entityReg) || targetThing

View File

@ -26,12 +26,9 @@ export interface ThreadRouterDiscoveryEvent {
}
class DiscoveryStream {
hass: HomeAssistant;
routers: { [key: string]: ThreadRouter };
constructor(hass: HomeAssistant) {
this.hass = hass;
constructor() {
this.routers = {};
}
@ -49,7 +46,7 @@ export const subscribeDiscoverThreadRouters = (
hass: HomeAssistant,
callbackFunction: (routers: ThreadRouter[]) => void
) => {
const stream = new DiscoveryStream(hass);
const stream = new DiscoveryStream();
return hass.connection.subscribeMessage<ThreadRouterDiscoveryEvent>(
(message) => callbackFunction(stream.processEvent(message)),
{

View File

@ -757,10 +757,14 @@ export const fetchZwaveNodeFirmwareUpdateCapabilities = (
export const uploadFirmwareAndBeginUpdate = async (
hass: HomeAssistant,
device_id: string,
file: File
file: File,
target?: number
) => {
const fd = new FormData();
fd.append("file", file);
if (target !== undefined) {
fd.append("target", target.toString());
}
const resp = await hass.fetchWithAuth(
`/api/zwave_js/firmware/upload/${device_id}`,
{

View File

@ -0,0 +1,24 @@
import { css } from "lit";
export const moreInfoControlStyle = css`
:host {
display: flex;
flex-direction: column;
flex: 1;
justify-content: space-between;
}
.controls {
display: flex;
flex-direction: column;
align-items: center;
}
.controls > *:not(:last-child) {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
`;

View File

@ -23,7 +23,6 @@ import {
lightSupportsColor,
lightSupportsColorMode,
} from "../../../../data/light";
import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import { LightColorPickerViewParams } from "./show-view-light-color-picker";
@ -78,117 +77,115 @@ class MoreInfoViewLightColorPicker extends LitElement {
lightSupportsColorMode(this.stateObj, LightColorMode.RGBW);
return html`
<div>
${this._modes.length > 1
${this._modes.length > 1
? html`
<mwc-tab-bar
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
@MDCTabBar:activated=${this._handleTabChanged}
>
${this._modes.map(
(value) =>
html`<mwc-tab
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
></mwc-tab>`
)}
</mwc-tab-bar>
`
: ""}
<div class="content">
${this._mode === LightColorMode.COLOR_TEMP
? html`
<mwc-tab-bar
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
@MDCTabBar:activated=${this._handleTabChanged}
<ha-control-slider
vertical
class="color_temp"
label=${this.hass.localize("ui.card.light.color_temperature")}
min="1"
max="100"
mode="cursor"
.value=${this._ctSliderValue}
@value-changed=${this._ctSliderChanged}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
>
${this._modes.map(
(value) =>
html`<mwc-tab
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
></mwc-tab>`
)}
</mwc-tab-bar>
</ha-control-slider>
`
: ""}
<div class="content">
${this._mode === LightColorMode.COLOR_TEMP
? html`
<ha-control-slider
vertical
class="color_temp"
label=${this.hass.localize("ui.card.light.color_temperature")}
min="1"
max="100"
mode="cursor"
.value=${this._ctSliderValue}
@value-changed=${this._ctSliderChanged}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
${this._mode === "color"
? html`
<div class="segmentationContainer">
<ha-color-picker
class="color"
@colorselected=${this._colorPicked}
.desiredRgbColor=${this._colorPickerColor}
throttle="500"
.hueSegments=${this._hueSegments}
.saturationSegments=${this._saturationSegments}
>
</ha-control-slider>
`
: ""}
${this._mode === "color"
? html`
<div class="segmentationContainer">
<ha-color-picker
class="color"
@colorselected=${this._colorPicked}
.desiredRgbColor=${this._colorPickerColor}
throttle="500"
.hueSegments=${this._hueSegments}
.saturationSegments=${this._saturationSegments}
>
</ha-color-picker>
<ha-icon-button
.path=${mdiPalette}
@click=${this._segmentClick}
class="segmentationButton"
></ha-icon-button>
</div>
</ha-color-picker>
<ha-icon-button
.path=${mdiPalette}
@click=${this._segmentClick}
class="segmentationButton"
></ha-icon-button>
</div>
${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider
${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.color_brightness"
)}
icon="hass:brightness-7"
max="100"
.value=${this._colorBrightnessSliderValue}
@change=${this._colorBrightnessSliderChanged}
pin
></ha-labeled-slider>`
: ""}
${supportsRgbw
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.color_brightness"
"ui.card.light.white_value"
)}
icon="hass:brightness-7"
icon="hass:file-word-box"
max="100"
.value=${this._colorBrightnessSliderValue}
@change=${this._colorBrightnessSliderChanged}
.name=${"wv"}
.value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>`
: ""}
${supportsRgbw
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"wv"}
.value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
${supportsRgbww
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.cold_white_value"
)}
icon="hass:file-word-box-outline"
max="100"
.name=${"cw"}
.value=${this._cwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.warm_white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"ww"}
.value=${this._wwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
`
: ""}
</div>
></ha-labeled-slider>
`
: ""}
${supportsRgbww
? html`
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.cold_white_value"
)}
icon="hass:file-word-box-outline"
max="100"
.name=${"cw"}
.value=${this._cwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
<ha-labeled-slider
.caption=${this.hass.localize(
"ui.card.light.warm_white_value"
)}
icon="hass:file-word-box"
max="100"
.name=${"ww"}
.value=${this._wwSliderValue}
@change=${this._wvSliderChanged}
pin
></ha-labeled-slider>
`
: ""}
`
: ""}
</div>
`;
}
@ -482,13 +479,18 @@ class MoreInfoViewLightColorPicker extends LitElement {
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
:host {
display: flex;
flex-direction: column;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
justify-content: center;
padding: 24px;
flex: 1;
}
.segmentationContainer {

View File

@ -94,12 +94,12 @@ export const computeShowLogBookComponent = (
return true;
};
export const computeShowNewMoreInfo = (stateObj: HassEntity) => {
export const computeShowNewMoreInfo = (stateObj: HassEntity): boolean => {
const domain = computeDomain(stateObj.entity_id);
if (domain === "group") {
const groupDomain = computeGroupDomain(stateObj as GroupEntity);
return (
groupDomain &&
groupDomain != null &&
groupDomain !== "group" &&
DOMAINS_WITH_NEW_MORE_INFO.includes(groupDomain)
);

View File

@ -12,6 +12,7 @@ import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
import { computeGroupDomain, GroupEntity } from "../../../data/group";
import "../../../state-summary/state-card-content";
import { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import {
domainMoreInfoType,
importMoreInfoControl,
@ -94,12 +95,15 @@ class MoreInfoGroup extends LitElement {
}
static get styles(): CSSResultGroup {
return css`
state-card-content {
display: block;
margin-top: 8px;
}
`;
return [
moreInfoControlStyle,
css`
state-card-content {
display: block;
margin-top: 8px;
}
`,
];
}
}

View File

@ -23,6 +23,7 @@ import "../../../components/ha-attributes";
import "../../../components/ha-button-menu";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import {
LightColorMode,
LightEntity,
@ -32,6 +33,7 @@ import {
lightSupportsColorMode,
} from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
import "../components/lights/ha-more-info-light-brightness";
@ -85,12 +87,12 @@ class MoreInfoLight extends LitElement {
: undefined;
return html`
<div class="content">
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
.stateOverride=${stateOverride}
></ha-more-info-state-header>
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
.stateOverride=${stateOverride}
></ha-more-info-state-header>
<div class="controls">
${supportsBrightness
? html`
<ha-more-info-light-brightness
@ -184,17 +186,19 @@ class MoreInfoLight extends LitElement {
</div>
`
: null}
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
extra-filters="brightness,color_temp,color_temp_kelvin,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,min_color_temp_kelvin,max_color_temp_kelvin,entity_id,supported_color_modes,color_mode"
></ha-attributes>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
extra-filters="brightness,color_temp,color_temp_kelvin,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,min_color_temp_kelvin,max_color_temp_kelvin,entity_id,supported_color_modes,color_mode"
></ha-attributes>
`;
}
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
this.hass.callService("light", service, {
entity_id: this.stateObj!.entity_id,
});
@ -230,41 +234,29 @@ class MoreInfoLight extends LitElement {
}
static get styles(): CSSResultGroup {
return css`
.content {
display: flex;
flex-direction: column;
align-items: center;
}
return [
moreInfoControlStyle,
css`
.buttons {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.buttons > * {
margin: 4px;
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.buttons > * {
margin: 4px;
}
ha-more-info-light-brightness,
ha-more-info-toggle {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
md-outlined-icon-button-toggle,
md-outlined-icon-button {
--ha-icon-display: block;
--md-sys-color-on-surface: var(--secondary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
--md-sys-color-outline: var(--secondary-text-color);
}
`;
md-outlined-icon-button-toggle,
md-outlined-icon-button {
--ha-icon-display: block;
--md-sys-color-on-surface: var(--secondary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
--md-sys-color-outline: var(--secondary-text-color);
}
`,
];
}
}

View File

@ -1,9 +1,10 @@
import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import { LightEntity } from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
@ -19,41 +20,27 @@ class MoreInfoSiren extends LitElement {
}
return html`
<div class="content">
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<div class="controls">
<ha-more-info-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiVolumeHigh}
.iconPathOff=${mdiVolumeOff}
></ha-more-info-toggle>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
static get styles(): CSSResultGroup {
return css`
.content {
display: flex;
flex-direction: column;
align-items: center;
}
ha-more-info-toggle {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
`;
return moreInfoControlStyle;
}
}

View File

@ -1,9 +1,10 @@
import { mdiPower, mdiPowerOff } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import { LightEntity } from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/ha-more-info-toggle";
@ -19,41 +20,27 @@ class MoreInfoSwitch extends LitElement {
}
return html`
<div class="content">
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<ha-more-info-state-header
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-more-info-state-header>
<div class="controls">
<ha-more-info-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiPower}
.iconPathOff=${mdiPowerOff}
></ha-more-info-toggle>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
static get styles(): CSSResultGroup {
return css`
.content {
display: flex;
flex-direction: column;
align-items: center;
}
ha-more-info-toggle {
margin-bottom: 24px;
}
ha-attributes {
width: 100%;
}
`;
return moreInfoControlStyle;
}
}

View File

@ -25,7 +25,11 @@ import "../../components/ha-icon-button";
import "../../components/ha-icon-button-prev";
import "../../components/ha-list-item";
import "../../components/ha-related-items";
import { EntityRegistryEntry } from "../../data/entity_registry";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../data/entity_registry";
import { haStyleDialog } from "../../resources/styles";
import "../../state-summary/state-card-content";
import { HomeAssistant } from "../../types";
@ -77,6 +81,8 @@ export class MoreInfoDialog extends LitElement {
@state() private _childView?: ChildView;
@state() private _entry?: ExtEntityRegistryEntry;
public showDialog(params: MoreInfoDialogParams) {
this._entityId = params.entityId;
if (!this._entityId) {
@ -86,10 +92,22 @@ export class MoreInfoDialog extends LitElement {
this._currView = params.view || "info";
this._childView = undefined;
this.large = false;
this._loadEntityRegistryEntry();
}
private async _loadEntityRegistryEntry() {
if (!this._entityId) {
return;
}
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this._entityId
);
}
public closeDialog() {
this._entityId = undefined;
this._entry = undefined;
this._childView = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -172,7 +190,10 @@ export class MoreInfoDialog extends LitElement {
idToPassThroughUrl = stateObj.attributes.id;
}
if (EDITABLE_DOMAINS_WITH_UNIQUE_ID.includes(domain)) {
idToPassThroughUrl = this.hass.entities[this._entityId!].unique_id;
if (!this._entry) {
return;
}
idToPassThroughUrl = this._entry.unique_id;
}
navigate(`/config/${domain}/edit/${idToPassThroughUrl}`);
@ -203,13 +224,7 @@ export class MoreInfoDialog extends LitElement {
const isInfoView = this._currView === "info" && !this._childView;
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${title}
hideActions
data-domain=${domain}
>
<ha-dialog open @closed=${this.closeDialog} .heading=${title} hideActions>
<div slot="heading" class="heading">
<ha-header-bar>
${isInfoView
@ -335,10 +350,14 @@ export class MoreInfoDialog extends LitElement {
@show-child-view=${this._showChildView}
>
${this._childView
? dynamicElement(this._childView.viewTag, {
hass: this.hass,
params: this._childView.viewParams,
})
? html`
<div class="child-view">
${dynamicElement(this._childView.viewTag, {
hass: this.hass,
params: this._childView.viewParams,
})}
</div>
`
: cache(
this._currView === "info"
? html`
@ -360,6 +379,8 @@ export class MoreInfoDialog extends LitElement {
<ha-more-info-settings
.hass=${this.hass}
.entityId=${this._entityId}
.entry=${this._entry}
@entity-entry-updated=${this._entryUpdated}
></ha-more-info-settings>
`
: this._currView === "related"
@ -385,12 +406,12 @@ export class MoreInfoDialog extends LitElement {
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_currView")) {
this.setAttribute("view", this._currView);
this._childView = undefined;
}
if (changedProps.has("_childView")) {
this.toggleAttribute("has-child-view", !!this._childView);
}
}
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
this._entry = ev.detail;
}
private _enlarge() {
@ -407,7 +428,6 @@ export class MoreInfoDialog extends LitElement {
--dialog-content-position: static;
--vertical-align-dialog: flex-start;
--dialog-content-padding: 0;
--content-padding: 24px;
}
ha-header-bar {
@ -417,6 +437,7 @@ export class MoreInfoDialog extends LitElement {
display: block;
border-bottom: none;
}
.content {
outline: none;
}
@ -426,22 +447,16 @@ export class MoreInfoDialog extends LitElement {
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
:host([view="settings"]) ha-dialog {
--content-padding: 0;
ha-related-items,
ha-more-info-history-and-logbook {
padding: 24px;
display: block;
}
:host([view="info"]) ha-dialog[data-domain="camera"] {
--content-padding: 0;
/* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */
--video-max-height: calc(100vh - 65px - 72px);
}
:host([has-child-view]) ha-dialog {
--content-padding: 0;
}
.content {
padding: var(--content-padding);
@media all and (max-width: 450px) {
.child-view > * {
min-height: calc(100vh - 56px);
}
}
.main-title {

View File

@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../../types";
import {
@ -34,6 +34,18 @@ export class MoreInfoHistoryAndLogbook extends LitElement {
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
ha-more-info-history,
ha-more-info-logbook {
display: block;
}
ha-more-info-history + ha-more-info-logbook {
margin-top: 16px;
}
`;
}
}
declare global {

View File

@ -16,6 +16,7 @@ import {
} from "./const";
import "./ha-more-info-history";
import "./ha-more-info-logbook";
import "./more-info-content";
@customElement("ha-more-info-info")
export class MoreInfoInfo extends LitElement {
@ -29,52 +30,59 @@ export class MoreInfoInfo extends LitElement {
const entityId = this.entityId;
const stateObj = this.hass.states[entityId];
const domain = computeDomain(entityId);
const newMoreInfo = computeShowNewMoreInfo(stateObj);
return html`
${!stateObj
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.unavailable"
)}
</ha-alert>`
: ""}
${stateObj?.attributes.restored && this._entityEntry
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.more_info_control.restored.no_longer_provided",
{
integration: this._entityEntry.platform,
}
)}
</ha-alert>`
: ""}
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
? ""
: html`
<state-card-content
in-dialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>
`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowHistoryComponent(this.hass, entityId)
? ""
: html`<ha-more-info-history
<div class="container" data-domain=${domain}>
${!stateObj
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.unavailable"
)}
</ha-alert>`
: ""}
${stateObj?.attributes.restored && this._entityEntry
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.dialogs.more_info_control.restored.no_longer_provided",
{
integration: this._entityEntry.platform,
}
)}
</ha-alert>`
: ""}
<div class="content">
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
? ""
: html`
<state-card-content
in-dialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>
`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowHistoryComponent(this.hass, entityId)
? ""
: html`<ha-more-info-history
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-history>`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowLogBookComponent(this.hass, entityId)
? ""
: html`<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-logbook>`}
<more-info-content
?full-height=${newMoreInfo}
.stateObj=${stateObj}
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-history>`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!computeShowLogBookComponent(this.hass, entityId)
? ""
: html`<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-logbook>`}
<more-info-content
.stateObj=${stateObj}
.hass=${this.hass}
></more-info-content>
></more-info-content>
<div class="toto"></div>
</div>
</div>
`;
}
@ -91,6 +99,40 @@ export class MoreInfoInfo extends LitElement {
static get styles() {
return css`
.container {
display: flex;
flex-direction: column;
}
@media all and (max-width: 450px) {
.container {
min-height: calc(100vh - 56px);
}
}
.content {
display: flex;
flex-direction: column;
flex: 1;
padding: 24px;
padding-bottom: max(env(safe-area-inset-bottom), 24px);
}
[data-domain="camera"] .content {
padding: 0;
/* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */
--video-max-height: calc(100vh - 65px - 72px);
}
more-info-content {
position: relative;
display: flex;
flex-direction: column;
}
more-info-content[full-height] {
flex: 1;
}
state-card-content,
ha-more-info-history,
ha-more-info-logbook:not(:last-child) {
@ -100,9 +142,6 @@ export class MoreInfoInfo extends LitElement {
ha-alert {
display: block;
margin: calc(-1 * var(--content-padding, 24px))
calc(-1 * var(--content-padding, 24px)) 16px
calc(-1 * var(--content-padding, 24px));
}
`;
}

View File

@ -6,12 +6,11 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../data/entity_registry";
import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const";
import "../../panels/config/entities/entity-registry-settings";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import "../../panels/config/entities/entity-registry-settings";
@customElement("ha-more-info-settings")
export class HaMoreInfoSettings extends LitElement {
@ -19,18 +18,18 @@ export class HaMoreInfoSettings extends LitElement {
@property({ attribute: false }) public entityId!: string;
@state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private _settingsElementTag?: string;
protected render() {
// loading.
if (this._entry === undefined) {
if (this.entry === undefined) {
return html``;
}
// No unique ID
if (this._entry === null) {
if (this.entry === null) {
return html`
<div class="content">
${this.hass.localize(
@ -54,53 +53,31 @@ export class HaMoreInfoSettings extends LitElement {
}
return html`
<div @entity-entry-updated=${this._entryUpdated}>
${dynamicElement(this._settingsElementTag, {
hass: this.hass,
entry: this._entry,
entityId: this.entityId,
})}
</div>
${dynamicElement(this._settingsElementTag, {
hass: this.hass,
entry: this.entry,
entityId: this.entityId,
})}
`;
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (changedProps.has("entityId")) {
this._entry = undefined;
if (this.entityId) {
this._getEntityReg();
}
}
}
private async _getEntityReg() {
try {
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this.entityId
);
public willUpdate(changedProps: PropertyValues) {
if (changedProps.has("entry")) {
this._loadPlatformSettingTabs();
} catch {
this._entry = null;
}
}
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
this._entry = ev.detail;
}
private async _loadPlatformSettingTabs(): Promise<void> {
if (!this._entry) {
if (!this.entry) {
return;
}
if (
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this.entry.platform)
) {
this._settingsElementTag = "entity-registry-settings";
return;
}
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
const tag = PLATFORMS_WITH_SETTINGS_TAB[this.entry.platform];
await import(`../../panels/config/entities/editor-tabs/settings/${tag}`);
this._settingsElementTag = tag;
}

View File

@ -13,10 +13,8 @@ import {
StaleWhileRevalidate,
} from "workbox-strategies";
const noFallBackRegEx = new RegExp(
"/(api|static|auth|frontend_latest|frontend_es5|local)/.*"
);
const noFallBackRegEx =
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;
// Clean up caches from older workboxes and old service workers.
// Will help with cleaning up Workbox v4 stuff
cleanupOutdatedCaches();
@ -33,22 +31,22 @@ function initRouting() {
// Cache static content (including translations) on first access.
registerRoute(
new RegExp("/(static|frontend_latest|frontend_es5)/.+"),
/\/(static|frontend_latest|frontend_es5)\/.+/,
new CacheFirst({ matchOptions: { ignoreSearch: true } })
);
// Get api from network.
registerRoute(new RegExp("/(api|auth)/.*"), new NetworkOnly());
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());
// Get manifest, service worker, onboarding from network.
registerRoute(
new RegExp("/(service_worker.js|manifest.json|onboarding.html)"),
/\/(service_worker.js|manifest.json|onboarding.html)/,
new NetworkOnly()
);
// For the root "/" we ignore search
registerRoute(
new RegExp(/\/(\?.*)?$/),
/\/(\?.*)?$/,
new StaleWhileRevalidate({ matchOptions: { ignoreSearch: true } })
);
@ -57,7 +55,7 @@ function initRouting() {
// First access might bring stale data from cache, but a single refresh will bring updated
// file.
registerRoute(
new RegExp(/\/.*/),
/\/.*/,
new StaleWhileRevalidate({
cacheName: "file-cache",
plugins: [

View File

@ -116,7 +116,7 @@ export const provideHass = (
}
mockAPI(
new RegExp("states/.+"),
/states\/.+/,
(
// @ts-ignore
method,

View File

@ -11,6 +11,7 @@ import {
mdiStopCircleOutline,
mdiSort,
} from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
@ -26,6 +27,10 @@ import "../../../../components/ha-icon-button";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { ACTION_TYPES } from "../../../../data/action";
import { validateConfig } from "../../../../data/config";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../../data/entity_registry";
import { Action, getActionType } from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n";
import { callExecuteScript } from "../../../../data/service";
@ -107,6 +112,8 @@ export default class HaAutomationActionRow extends LitElement {
@property({ type: Boolean }) public reOrderMode = false;
@state() private _entityReg: EntityRegistryEntry[] = [];
@state() private _warnings?: string[];
@state() private _uiModeAvailable = true;
@ -115,6 +122,14 @@ export default class HaAutomationActionRow extends LitElement {
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected willUpdate(changedProperties: PropertyValues) {
if (!changedProperties.has("action")) {
return;
@ -156,7 +171,9 @@ export default class HaAutomationActionRow extends LitElement {
class="action-icon"
.path=${ACTION_TYPES[type!]}
></ha-svg-icon>
${capitalizeFirstLetter(describeAction(this.hass, this.action))}
${capitalizeFirstLetter(
describeAction(this.hass, this._entityReg, this.action)
)}
</h3>
<slot name="icons" slot="icons"></slot>
@ -465,7 +482,7 @@ export default class HaAutomationActionRow extends LitElement {
),
inputType: "string",
placeholder: capitalizeFirstLetter(
describeAction(this.hass, this.action, undefined, true)
describeAction(this.hass, this._entityReg, this.action, undefined, true)
),
defaultValue: this.action.alias,
confirmText: this.hass.localize("ui.common.submit"),

View File

@ -49,6 +49,7 @@ import {
showAutomationEditor,
triggerAutomationActions,
} from "../../../data/automation";
import { fetchEntityRegistry } from "../../../data/entity_registry";
import {
showAlertDialog,
showConfirmationDialog,
@ -479,7 +480,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
this._readOnly = false;
this._config = this._normalizeConfig(config);
} catch (err: any) {
const entity = Object.values(this.hass.entities).find(
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
const entity = entityRegistry.find(
(ent) =>
ent.platform === "automation" && ent.unique_id === this.automationId
);

View File

@ -36,6 +36,15 @@ import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../../dialogs/generic/show-dialog-box";
import { HaFormSchema } from "../../../../../components/ha-form/types";
const firmwareTargetSchema: HaFormSchema[] = [
{
name: "firmware_target",
type: "integer",
valueMin: 0,
},
];
@customElement("dialog-zwave_js-update-firmware-node")
class DialogZWaveJSUpdateFirmwareNode extends LitElement {
@ -59,6 +68,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
@state() private _nodeStatus?: ZWaveJSNodeStatus;
@state() private _firmwareTarget?: number;
private _subscribedNodeStatus?: Promise<UnsubscribeFunc>;
private _subscribedNodeFirmwareUpdate?: Promise<UnsubscribeFunc>;
@ -80,6 +91,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._updateFinishedMessage = undefined;
this._firmwareFile = undefined;
this._nodeStatus = undefined;
this._firmwareTarget = undefined;
this._uploading = this._updateInProgress = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@ -104,6 +116,19 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
)}
@file-picked=${this._uploadFile}
></ha-file-upload>
${this._nodeStatus.is_controller_node
? html``
: html`<p>
${this.hass.localize(
"ui.panel.config.zwave_js.update_firmware.firmware_target_intro"
)}
</p>
<ha-form
.hass=${this.hass}
.data=${{ firmware_target: this._firmwareTarget }}
.schema=${firmwareTargetSchema}
@value-changed=${this._firmwareTargetChanged}
></ha-form>`}
<mwc-button
slot="primaryAction"
@click=${this._beginFirmwareUpdate}
@ -283,7 +308,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
await uploadFirmwareAndBeginUpdate(
this.hass,
this.device!.id,
this._firmwareFile!
this._firmwareFile!,
this._firmwareTarget
);
this._updateInProgress = true;
this._uploading = false;
@ -388,6 +414,10 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._subscribedNodeFirmwareUpdate = undefined;
}
private async _firmwareTargetChanged(ev) {
this._firmwareTarget = ev.detail.value.firmware_target;
}
private async _uploadFile(ev) {
this._firmwareFile = ev.detail.files[0];
}

View File

@ -1,14 +1,19 @@
import { HassEntities } from "home-assistant-js-websocket";
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { debounce } from "../../../common/util/debounce";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { ScriptEntity } from "../../../data/script";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../types";
import "./ha-script-editor";
import "./ha-script-picker";
@ -21,7 +26,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => {
};
@customElement("ha-config-script")
class HaConfigScript extends HassRouterPage {
class HaConfigScript extends SubscribeMixin(HassRouterPage) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean;
@ -32,6 +37,16 @@ class HaConfigScript extends HassRouterPage {
@property() public scripts: ScriptEntity[] = [];
@state() private _entityReg: EntityRegistryEntry[] = [];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
routes: {
@ -78,6 +93,7 @@ class HaConfigScript extends HassRouterPage {
pageEl.isWide = this.isWide;
pageEl.route = this.routeTail;
pageEl.showAdvanced = this.showAdvanced;
pageEl.entityRegistry = this._entityReg;
if (this.hass) {
if (!pageEl.scripts || !changedProps) {

View File

@ -39,6 +39,7 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import {
deleteScript,
getScriptStateConfig,
@ -75,6 +76,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@state() private _config?: ScriptConfig;
@state() private _entityId?: string;
@ -431,7 +434,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._config = this._normalizeConfig(config);
},
(resp) => {
const entity = Object.values(this.hass.entities).find(
const entity = this.entityRegistry.find(
(ent) =>
ent.platform === "script" && ent.unique_id === this.scriptId
);
@ -477,7 +480,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
getScriptStateConfig(this.hass, this.entityId).then((c) => {
this._config = this._normalizeConfig(c.config);
});
const regEntry = this.hass.entities[this.entityId];
const regEntry = this.entityRegistry.find(
(ent) => ent.entity_id === this.entityId
);
if (regEntry?.unique_id) {
this.scriptId = regEntry.unique_id;
}
@ -544,7 +549,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
if (!this.scriptId) {
return;
}
const entity = Object.values(this.hass.entities).find(
const entity = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
);
if (!entity) {

View File

@ -44,6 +44,7 @@ import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@customElement("ha-script-picker")
class HaScriptPicker extends LitElement {
@ -57,7 +58,9 @@ class HaScriptPicker extends LitElement {
@property() public route!: Route;
@property() private _activeFilters?: string[];
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@state() private _activeFilters?: string[];
@state() private _filteredScripts?: string[] | null;
@ -266,7 +269,7 @@ class HaScriptPicker extends LitElement {
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const entry = this.hass.entities[ev.detail.id];
const entry = this.entityRegistry.find((e) => e.entity_id === ev.detail.id);
if (entry) {
navigate(`/config/script/edit/${entry.unique_id}`);
} else {
@ -275,7 +278,12 @@ class HaScriptPicker extends LitElement {
}
private _runScript = async (script: any) => {
const entry = this.hass.entities[script.entity_id];
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (!entry) {
return;
}
await triggerScript(this.hass, entry.unique_id);
showToast(this, {
message: this.hass.localize(
@ -291,7 +299,9 @@ class HaScriptPicker extends LitElement {
}
private _showTrace(script: any) {
const entry = this.hass.entities[script.entity_id];
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (entry) {
navigate(`/config/script/trace/${entry.unique_id}`);
}
@ -317,7 +327,12 @@ class HaScriptPicker extends LitElement {
private async _duplicate(script: any) {
try {
const entry = this.hass.entities[script.entity_id];
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (!entry) {
return;
}
const config = await fetchScriptFileConfig(this.hass, entry.unique_id);
showScriptEditor({
...config,
@ -362,8 +377,12 @@ class HaScriptPicker extends LitElement {
private async _delete(script: any) {
try {
const entry = this.hass.entities[script.entity_id];
await deleteScript(this.hass, entry.unique_id);
const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id
);
if (entry) {
await deleteScript(this.hass, entry.unique_id);
}
} catch (err: any) {
await showAlertDialog(this, {
text:

View File

@ -39,6 +39,7 @@ import { HomeAssistant, Route } from "../../../types";
import "../../../layouts/hass-subpage";
import "../../../components/ha-button-menu";
import { fireEvent } from "../../../common/dom/fire_event";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@customElement("ha-script-trace")
export class HaScriptTrace extends LitElement {
@ -54,6 +55,8 @@ export class HaScriptTrace extends LitElement {
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@state() private _entityId?: string;
@state() private _traces?: ScriptTrace[];
@ -318,7 +321,7 @@ export class HaScriptTrace extends LitElement {
const params = new URLSearchParams(location.search);
this._loadTraces(params.get("run_id") || undefined);
this._entityId = Object.values(this.hass.entities).find(
this._entityId = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
)?.entity_id;
}
@ -335,7 +338,7 @@ export class HaScriptTrace extends LitElement {
if (this.scriptId) {
this._loadTraces();
this._entityId = Object.values(this.hass.entities).find(
this._entityId = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
)?.entity_id;
}

View File

@ -18,7 +18,7 @@ import "../lovelace/components/hui-energy-period-selector";
import { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view";
const LOVELACE_CONFIG: LovelaceConfig = {
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
views: [
{
strategy: {
@ -93,8 +93,8 @@ class PanelEnergy extends LitElement {
private _setLovelace() {
this._lovelace = {
config: LOVELACE_CONFIG,
rawConfig: LOVELACE_CONFIG,
config: ENERGY_LOVELACE_CONFIG,
rawConfig: ENERGY_LOVELACE_CONFIG,
editMode: false,
urlPath: "energy",
mode: "generated",

View File

@ -56,7 +56,7 @@ export class EnergyStrategy {
(source) => source.type === "water"
);
if (info.narrow) {
if (info.narrow || info.view.strategy?.options?.show_date_selection) {
view.cards!.push({
type: "energy-date-selection",
collection_key: "energy_dashboard",

View File

@ -884,6 +884,11 @@ class HuiEnergyDistrubutionCard
color: var(--secondary-text-color);
font-size: 12px;
opacity: 1;
height: 20px;
overflow: hidden;
text-overflow: ellipsis;
max-width: 80px;
white-space: nowrap;
}
line,
path {

View File

@ -13,6 +13,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { getNumberFormatOptions } from "../../../common/number/format_number";
import "../../../components/ha-card";
import "../../../components/ha-gauge";
import { UNAVAILABLE } from "../../../data/entity";
@ -129,6 +130,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
.min=${this._config.min!}
.max=${this._config.max!}
.value=${stateObj.state}
.formatOptions=${getNumberFormatOptions(
stateObj,
this.hass.entities[stateObj.entity_id]
)}
.locale=${this.hass!.locale}
.label=${this._config!.unit ||
this.hass?.states[this._config!.entity].attributes

View File

@ -278,8 +278,8 @@ const computeDefaultViewStates = (
.filter(
(entry) =>
entry.entity_category ||
HIDE_PLATFORM.has(entry.platform) ||
entry.hidden_by
(entry.platform && HIDE_PLATFORM.has(entry.platform)) ||
entry.hidden
)
.map((entry) => entry.entity_id)
);

View File

@ -62,7 +62,7 @@ const buttonEntitiesRowConfigStruct = object({
const castEntitiesRowConfigStruct = object({
type: literal("cast"),
view: union([string(), number()]),
view: optional(union([string(), number()])),
dashboard: optional(string()),
name: optional(string()),
icon: optional(string()),

View File

@ -55,7 +55,7 @@ export interface CastConfig {
type: "cast";
icon?: string;
name?: string;
view: string | number;
view?: string | number;
dashboard?: string;
// Hide the row if either unsupported browser or no API available.
hide_if_unavailable?: boolean;

View File

@ -29,13 +29,10 @@ class HuiCastRow extends LitElement implements LovelaceRow {
@state() private _noHTTPS = false;
public setConfig(config: CastConfig): void {
if (!config || config.view === undefined || config.view === null) {
throw new Error("View required");
}
this._config = {
icon: "hass:television",
icon: "mdi:television",
name: "Home Assistant Cast",
view: 0,
...config,
};
}
@ -123,7 +120,7 @@ class HuiCastRow extends LitElement implements LovelaceRow {
castSendShowLovelaceView(
this._castManager!,
this.hass.auth.data.hassUrl,
this._config!.view,
this._config!.view!,
this._config!.dashboard
);
}

View File

@ -14,7 +14,7 @@ import { polyfillsLoaded } from "../common/translations/localize";
import { subscribeAreaRegistry } from "../data/area_registry";
import { broadcastConnectionStatus } from "../data/connection-status";
import { subscribeDeviceRegistry } from "../data/device_registry";
import { subscribeEntityRegistry } from "../data/entity_registry";
import { subscribeEntityRegistryDisplay } from "../data/entity_registry";
import { subscribeFrontendUserData } from "../data/frontend";
import { forwardHaptic } from "../data/haptics";
import { DEFAULT_PANEL } from "../data/panel";
@ -188,10 +188,22 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
});
subscribeEntities(conn, (states) => this._updateHass({ states }));
subscribeEntityRegistry(conn, (entityReg) => {
subscribeEntityRegistryDisplay(conn, (entityReg) => {
const entities: HomeAssistant["entities"] = {};
for (const entity of entityReg) {
entities[entity.entity_id] = entity;
for (const entity of entityReg.entities) {
entities[entity.ei] = {
entity_id: entity.ei,
device_id: entity.di,
area_id: entity.ai,
translation_key: entity.tk,
platform: entity.pl,
entity_category: entity.ec
? entityReg.entity_categories[entity.ec]
: undefined,
name: entity.en,
hidden: entity.hb,
display_precision: entity.dp,
};
}
this._updateHass({ entities });
});

View File

@ -3691,6 +3691,8 @@
"warning_controller": "WARNING: Firmware updates can brick your controller if you do not use the right firmware files, or if you attempt to stop the firmware update before it completes. The Home Assistant and Z-Wave JS teams do not take any responsibility for any damages to your controller as a result of the firmware update and will not be able to help you if you brick your controller. Would you still like to continue?",
"introduction": "Select the firmware file you would like to use to update {device}.",
"introduction_controller": "Select the firmware file you would like to use to update {device}. Note that once you start a firmware update, you MUST wait for the update to complete.",
"firmware_target_intro": "Select the firmware target (0 for the Z-Wave chip, ≥1 for other chips if they exist) for this update.",
"firmware_target": "Firmware Target (chip)",
"upload_firmware": "Upload Firmware",
"upload_failed": "Upload Failed",
"begin_update": "Begin Firmware Update",

View File

@ -10,7 +10,7 @@ import {
import { LocalizeFunc } from "./common/translations/localize";
import { AreaRegistryEntry } from "./data/area_registry";
import { DeviceRegistryEntry } from "./data/device_registry";
import { EntityRegistryEntry } from "./data/entity_registry";
import { EntityRegistryDisplayEntry } from "./data/entity_registry";
import { CoreFrontendUserData } from "./data/frontend";
import { FrontendLocaleData, getHassTranslations } from "./data/translation";
import { Themes } from "./data/ws-themes";
@ -189,7 +189,7 @@ export interface HomeAssistant {
connection: Connection;
connected: boolean;
states: HassEntities;
entities: { [id: string]: EntityRegistryEntry };
entities: { [id: string]: EntityRegistryDisplayEntry };
devices: { [id: string]: DeviceRegistryEntry };
areas: { [id: string]: AreaRegistryEntry };
services: HassServices;

View File

@ -3095,13 +3095,13 @@ __metadata:
languageName: node
linkType: hard
"@material/web@npm:=1.0.0-pre.2":
version: 1.0.0-pre.2
resolution: "@material/web@npm:1.0.0-pre.2"
"@material/web@npm:=1.0.0-pre.3":
version: 1.0.0-pre.3
resolution: "@material/web@npm:1.0.0-pre.3"
dependencies:
lit: ^2.3.0
tslib: ^2.4.0
checksum: 7c6733fae5fb67c43d7c49fab70f7893defd95e4fcbe996d06057882e47c0121760546cc5d1c407a9dbd11c5f02f3f278016c52922e6a9e97db0c0b52d7133f2
checksum: d6286992cb0d63b094e638a3db484398195608b422bb2cb209102eaf87d220ed24bbe85d29933730054fd3cf99d318dbb3645ae9a2e271fe1a3c2833d829bf4c
languageName: node
linkType: hard
@ -9562,7 +9562,7 @@ fsevents@~2.3.2:
"@material/mwc-textfield": ^0.27.0
"@material/mwc-top-app-bar-fixed": ^0.27.0
"@material/top-app-bar": =14.0.0-canary.53b3cad2f.0
"@material/web": =1.0.0-pre.2
"@material/web": =1.0.0-pre.3
"@mdi/js": 7.1.96
"@mdi/svg": 7.1.96
"@octokit/auth-oauth-device": ^4.0.4
@ -9663,7 +9663,7 @@ fsevents@~2.3.2:
lit: ^2.6.1
lit-analyzer: ^1.2.1
lodash.template: ^4.5.0
magic-string: ^0.29.0
magic-string: ^0.30.0
map-stream: ^0.0.7
marked: ^4.2.12
memoize-one: ^6.0.0
@ -11590,12 +11590,12 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"magic-string@npm:^0.29.0":
version: 0.29.0
resolution: "magic-string@npm:0.29.0"
"magic-string@npm:^0.30.0":
version: 0.30.0
resolution: "magic-string@npm:0.30.0"
dependencies:
"@jridgewell/sourcemap-codec": ^1.4.13
checksum: 19e5398fcfc44804917127c72ad622c68a19a0a10cbdb8d4f9f9417584a087fe9e117140bfb2463d86743cf1ed9cf4182ae0b0ad1a7536f7fdda257ee4449ffb
checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d
languageName: node
linkType: hard