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"], "no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off", "prefer-promise-reject-errors": "off",
"no-unsafe-optional-chaining": "warn", "no-unsafe-optional-chaining": "warn",
"prefer-regex-literals": ["warn"],
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"import/no-default-export": "off", "import/no-default-export": "off",
"import/no-unresolved": "off", "import/no-unresolved": "off",

View File

@ -252,6 +252,22 @@ export class HcMain extends HassElement {
msg.urlPath = null; msg.urlPath = null;
} }
this._lovelacePath = msg.viewPath; 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) { if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath; this._urlPath = msg.urlPath;
this._lovelaceConfig = undefined; this._lovelaceConfig = undefined;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,10 @@
import {
DIRECTION_HORIZONTAL,
DIRECTION_VERTICAL,
Manager,
Swipe,
Tap,
} from "@egjs/hammerjs";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -6,7 +13,7 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import "./ha-svg-icon"; 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) // 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; @property({ type: String }) pathOff?: string;
private _mc?: HammerManager;
protected firstUpdated(changedProperties: PropertyValues): void { protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties); super.firstUpdated(changedProperties);
this.setupListeners();
this.setAttribute("role", "switch"); this.setAttribute("role", "switch");
if (!this.hasAttribute("tabindex")) { if (!this.hasAttribute("tabindex")) {
this.setAttribute("tabindex", "0"); this.setAttribute("tabindex", "0");
@ -53,14 +63,70 @@ export class HaControlSwitch extends LitElement {
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.addEventListener("keydown", this._keydown); this.setupListeners();
this.addEventListener("click", this._toggle);
} }
disconnectedCallback(): void { disconnectedCallback(): void {
super.disconnectedCallback(); 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("keydown", this._keydown);
this.removeEventListener("click", this._toggle);
} }
private _keydown(ev: any) { private _keydown(ev: any) {
@ -73,7 +139,7 @@ export class HaControlSwitch extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="switch"> <div id="switch" class="switch">
<div class="background"></div> <div class="background"></div>
<div class="button" aria-hidden="true"> <div class="button" aria-hidden="true">
${this.checked ${this.checked

View File

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

View File

@ -26,6 +26,9 @@ export class Gauge extends LitElement {
@property({ type: Number }) public value = 0; @property({ type: Number }) public value = 0;
@property({ attribute: false })
public formatOptions?: Intl.NumberFormatOptions;
@property({ type: String }) public valueText?: string; @property({ type: String }) public valueText?: string;
@property() public locale!: FrontendLocaleData; @property() public locale!: FrontendLocaleData;
@ -132,7 +135,8 @@ export class Gauge extends LitElement {
${ ${
this._segment_label this._segment_label
? 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 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 { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array"; import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry"; import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry"; import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { import {
EntitySources, EntitySources,
fetchEntitySourcesWithCache, fetchEntitySourcesWithCache,
@ -18,13 +14,12 @@ import {
filterSelectorDevices, filterSelectorDevices,
filterSelectorEntities, filterSelectorEntities,
} from "../../data/selector"; } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "../ha-area-picker"; import "../ha-area-picker";
import "../ha-areas-picker"; import "../ha-areas-picker";
@customElement("ha-selector-area") @customElement("ha-selector-area")
export class HaAreaSelector extends SubscribeMixin(LitElement) { export class HaAreaSelector extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public selector!: AreaSelector; @property() public selector!: AreaSelector;
@ -41,18 +36,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
@state() private _entitySources?: EntitySources; @state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); 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) { private _hasIntegration(selector: AreaSelector) {
return ( return (
(selector.area?.entity && (selector.area?.entity &&
@ -127,10 +112,12 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true; return true;
} }
const deviceIntegrations = const deviceIntegrations = this._entitySources
this._entitySources && this._entities ? this._deviceIntegrationLookup(
? this._deviceIntegrationLookup(this._entitySources, this._entities) this._entitySources,
: undefined; Object.values(this.hass.entities)
)
: undefined;
return ensureArray(this.selector.area.device).some((filter) => return ensureArray(this.selector.area.device).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations) filterSelectorDevices(filter, device, deviceIntegrations)

View File

@ -2,12 +2,11 @@ import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { AttributeSelector } from "../../data/selector"; import { AttributeSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "../entity/ha-entity-attribute-picker"; import "../entity/ha-entity-attribute-picker";
@customElement("ha-selector-attribute") @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 hass!: HomeAssistant;
@property({ attribute: false }) public selector!: AttributeSelector; @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 { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array"; import { ensureArray } from "../../common/array/ensure-array";
import type { DeviceRegistryEntry } from "../../data/device_registry"; import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry"; import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { import {
EntitySources, EntitySources,
fetchEntitySourcesWithCache, fetchEntitySourcesWithCache,
@ -18,21 +14,18 @@ import {
filterSelectorDevices, filterSelectorDevices,
filterSelectorEntities, filterSelectorEntities,
} from "../../data/selector"; } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker"; import "../device/ha-device-picker";
import "../device/ha-devices-picker"; import "../device/ha-devices-picker";
@customElement("ha-selector-device") @customElement("ha-selector-device")
export class HaDeviceSelector extends SubscribeMixin(LitElement) { export class HaDeviceSelector extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public selector!: DeviceSelector; @property() public selector!: DeviceSelector;
@state() private _entitySources?: EntitySources; @state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property() public value?: any; @property() public value?: any;
@property() public label?: string; @property() public label?: string;
@ -45,14 +38,6 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); 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) { private _hasIntegration(selector: DeviceSelector) {
return ( return (
(selector.device?.filter && (selector.device?.filter &&
@ -118,10 +103,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
if (!this.selector.device?.filter) { if (!this.selector.device?.filter) {
return true; return true;
} }
const deviceIntegrations = const deviceIntegrations = this._entitySources
this._entitySources && this._entities ? this._deviceIntegrationLookup(
? this._deviceIntegrationLookup(this._entitySources, this._entities) this._entitySources,
: undefined; Object.values(this.hass.entities)
)
: undefined;
return ensureArray(this.selector.device.filter).some((filter) => return ensureArray(this.selector.device.filter).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations) filterSelectorDevices(filter, device, deviceIntegrations)

View File

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

View File

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

View File

@ -4,7 +4,10 @@ import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare"; import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce"; import { debounce } from "../common/util/debounce";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import type { EntityRegistryEntry } from "./entity_registry"; import type {
EntityRegistryDisplayEntry,
EntityRegistryEntry,
} from "./entity_registry";
import type { EntitySources } from "./entity_sources"; import type { EntitySources } from "./entity_sources";
export interface DeviceRegistryEntry { export interface DeviceRegistryEntry {
@ -25,6 +28,10 @@ export interface DeviceRegistryEntry {
configuration_url: string | null; configuration_url: string | null;
} }
export interface DeviceEntityDisplayLookup {
[deviceId: string]: EntityRegistryDisplayEntry[];
}
export interface DeviceEntityLookup { export interface DeviceEntityLookup {
[deviceId: string]: EntityRegistryEntry[]; [deviceId: string]: EntityRegistryEntry[];
} }
@ -147,9 +154,25 @@ export const getDeviceEntityLookup = (
return deviceEntityLookup; 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 = ( export const getDeviceIntegrationLookup = (
entitySources: EntitySources, entitySources: EntitySources,
entities: EntityRegistryEntry[] entities: EntityRegistryDisplayEntry[]
): Record<string, string[]> => { ): Record<string, string[]> => {
const deviceIntegrations: 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 { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types"; 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 { export interface EntityRegistryEntry {
id: string; id: string;
entity_id: string; entity_id: string;
@ -17,7 +46,7 @@ export interface EntityRegistryEntry {
area_id: string | null; area_id: string | null;
disabled_by: "user" | "device" | "integration" | "config_entry" | null; disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">; hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: "config" | "diagnostic" | null; entity_category: entityCategory | null;
has_entity_name: boolean; has_entity_name: boolean;
original_name?: string; original_name?: string;
unique_id: string; unique_id: string;
@ -154,6 +183,11 @@ export const fetchEntityRegistry = (conn: Connection) =>
type: "config/entity_registry/list", type: "config/entity_registry/list",
}); });
export const fetchEntityRegistryDisplay = (conn: Connection) =>
conn.sendMessagePromise<EntityRegistryDisplayEntryResponse>({
type: "config/entity_registry/list_for_display",
});
const subscribeEntityRegistryUpdates = ( const subscribeEntityRegistryUpdates = (
conn: Connection, conn: Connection,
store: Store<EntityRegistryEntry[]> store: Store<EntityRegistryEntry[]>
@ -182,6 +216,34 @@ export const subscribeEntityRegistry = (
onChange 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 = ( export const sortEntityRegistryByName = (
entries: EntityRegistryEntry[], entries: EntityRegistryEntry[],
language: string language: string
@ -190,10 +252,20 @@ export const sortEntityRegistryByName = (
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) 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( export const entityRegistryById = memoizeOne(
(entries: HomeAssistant["entities"]) => { (entries: EntityRegistryEntry[]) => {
const entities: HomeAssistant["entities"] = {}; const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of Object.values(entries)) { for (const entity of entries) {
entities[entity.id] = entity; entities[entity.id] = entity;
} }
return entities; return entities;

View File

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

View File

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

View File

@ -757,10 +757,14 @@ export const fetchZwaveNodeFirmwareUpdateCapabilities = (
export const uploadFirmwareAndBeginUpdate = async ( export const uploadFirmwareAndBeginUpdate = async (
hass: HomeAssistant, hass: HomeAssistant,
device_id: string, device_id: string,
file: File file: File,
target?: number
) => { ) => {
const fd = new FormData(); const fd = new FormData();
fd.append("file", file); fd.append("file", file);
if (target !== undefined) {
fd.append("target", target.toString());
}
const resp = await hass.fetchWithAuth( const resp = await hass.fetchWithAuth(
`/api/zwave_js/firmware/upload/${device_id}`, `/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, lightSupportsColor,
lightSupportsColorMode, lightSupportsColorMode,
} from "../../../../data/light"; } from "../../../../data/light";
import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LightColorPickerViewParams } from "./show-view-light-color-picker"; import { LightColorPickerViewParams } from "./show-view-light-color-picker";
@ -78,117 +77,115 @@ class MoreInfoViewLightColorPicker extends LitElement {
lightSupportsColorMode(this.stateObj, LightColorMode.RGBW); lightSupportsColorMode(this.stateObj, LightColorMode.RGBW);
return html` 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` ? html`
<mwc-tab-bar <ha-control-slider
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0} vertical
@MDCTabBar:activated=${this._handleTabChanged} 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( </ha-control-slider>
(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 === "color"
${this._mode === LightColorMode.COLOR_TEMP ? html`
? html` <div class="segmentationContainer">
<ha-control-slider <ha-color-picker
vertical class="color"
class="color_temp" @colorselected=${this._colorPicked}
label=${this.hass.localize("ui.card.light.color_temperature")} .desiredRgbColor=${this._colorPickerColor}
min="1" throttle="500"
max="100" .hueSegments=${this._hueSegments}
mode="cursor" .saturationSegments=${this._saturationSegments}
.value=${this._ctSliderValue}
@value-changed=${this._ctSliderChanged}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
> >
</ha-control-slider> </ha-color-picker>
` <ha-icon-button
: ""} .path=${mdiPalette}
${this._mode === "color" @click=${this._segmentClick}
? html` class="segmentationButton"
<div class="segmentationContainer"> ></ha-icon-button>
<ha-color-picker </div>
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>
${supportsRgbw || supportsRgbww ${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider ? 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( .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" max="100"
.value=${this._colorBrightnessSliderValue} .name=${"wv"}
@change=${this._colorBrightnessSliderChanged} .value=${this._wvSliderValue}
@change=${this._wvSliderChanged}
pin pin
></ha-labeled-slider>` ></ha-labeled-slider>
: ""} `
${supportsRgbw : ""}
? html` ${supportsRgbww
<ha-labeled-slider ? html`
.caption=${this.hass.localize( <ha-labeled-slider
"ui.card.light.white_value" .caption=${this.hass.localize(
)} "ui.card.light.cold_white_value"
icon="hass:file-word-box" )}
max="100" icon="hass:file-word-box-outline"
.name=${"wv"} max="100"
.value=${this._wvSliderValue} .name=${"cw"}
@change=${this._wvSliderChanged} .value=${this._cwSliderValue}
pin @change=${this._wvSliderChanged}
></ha-labeled-slider> pin
` ></ha-labeled-slider>
: ""} <ha-labeled-slider
${supportsRgbww .caption=${this.hass.localize(
? html` "ui.card.light.warm_white_value"
<ha-labeled-slider )}
.caption=${this.hass.localize( icon="hass:file-word-box"
"ui.card.light.cold_white_value" max="100"
)} .name=${"ww"}
icon="hass:file-word-box-outline" .value=${this._wwSliderValue}
max="100" @change=${this._wvSliderChanged}
.name=${"cw"} pin
.value=${this._cwSliderValue} ></ha-labeled-slider>
@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>
</div> </div>
`; `;
} }
@ -482,13 +479,18 @@ class MoreInfoViewLightColorPicker extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyleDialog,
css` css`
:host {
display: flex;
flex-direction: column;
}
.content { .content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 16px; justify-content: center;
padding: 24px;
flex: 1;
} }
.segmentationContainer { .segmentationContainer {

View File

@ -94,12 +94,12 @@ export const computeShowLogBookComponent = (
return true; return true;
}; };
export const computeShowNewMoreInfo = (stateObj: HassEntity) => { export const computeShowNewMoreInfo = (stateObj: HassEntity): boolean => {
const domain = computeDomain(stateObj.entity_id); const domain = computeDomain(stateObj.entity_id);
if (domain === "group") { if (domain === "group") {
const groupDomain = computeGroupDomain(stateObj as GroupEntity); const groupDomain = computeGroupDomain(stateObj as GroupEntity);
return ( return (
groupDomain && groupDomain != null &&
groupDomain !== "group" && groupDomain !== "group" &&
DOMAINS_WITH_NEW_MORE_INFO.includes(groupDomain) 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 { computeGroupDomain, GroupEntity } from "../../../data/group";
import "../../../state-summary/state-card-content"; import "../../../state-summary/state-card-content";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import { import {
domainMoreInfoType, domainMoreInfoType,
importMoreInfoControl, importMoreInfoControl,
@ -94,12 +95,15 @@ class MoreInfoGroup extends LitElement {
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return [
state-card-content { moreInfoControlStyle,
display: block; css`
margin-top: 8px; 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-button-menu";
import "../../../components/ha-select"; import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import { import {
LightColorMode, LightColorMode,
LightEntity, LightEntity,
@ -32,6 +33,7 @@ import {
lightSupportsColorMode, lightSupportsColorMode,
} from "../../../data/light"; } from "../../../data/light";
import type { HomeAssistant } from "../../../types"; 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-state-header";
import "../components/ha-more-info-toggle"; import "../components/ha-more-info-toggle";
import "../components/lights/ha-more-info-light-brightness"; import "../components/lights/ha-more-info-light-brightness";
@ -85,12 +87,12 @@ class MoreInfoLight extends LitElement {
: undefined; : undefined;
return html` return html`
<div class="content"> <ha-more-info-state-header
<ha-more-info-state-header .hass=${this.hass}
.hass=${this.hass} .stateObj=${this.stateObj}
.stateObj=${this.stateObj} .stateOverride=${stateOverride}
.stateOverride=${stateOverride} ></ha-more-info-state-header>
></ha-more-info-state-header> <div class="controls">
${supportsBrightness ${supportsBrightness
? html` ? html`
<ha-more-info-light-brightness <ha-more-info-light-brightness
@ -184,17 +186,19 @@ class MoreInfoLight extends LitElement {
</div> </div>
` `
: null} : 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> </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 = () => { private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on"; const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
this.hass.callService("light", service, { this.hass.callService("light", service, {
entity_id: this.stateObj!.entity_id, entity_id: this.stateObj!.entity_id,
}); });
@ -230,41 +234,29 @@ class MoreInfoLight extends LitElement {
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return [
.content { moreInfoControlStyle,
display: flex; css`
flex-direction: column; .buttons {
align-items: center; display: flex;
} align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.buttons > * {
margin: 4px;
}
.buttons { md-outlined-icon-button-toggle,
display: flex; md-outlined-icon-button {
align-items: center; --ha-icon-display: block;
justify-content: center; --md-sys-color-on-surface: var(--secondary-text-color);
margin-bottom: 12px; --md-sys-color-on-surface-variant: var(--secondary-text-color);
} --md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
.buttons > * { --md-sys-color-outline: var(--secondary-text-color);
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);
}
`;
} }
} }

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import {
} from "./const"; } from "./const";
import "./ha-more-info-history"; import "./ha-more-info-history";
import "./ha-more-info-logbook"; import "./ha-more-info-logbook";
import "./more-info-content";
@customElement("ha-more-info-info") @customElement("ha-more-info-info")
export class MoreInfoInfo extends LitElement { export class MoreInfoInfo extends LitElement {
@ -29,52 +30,59 @@ export class MoreInfoInfo extends LitElement {
const entityId = this.entityId; const entityId = this.entityId;
const stateObj = this.hass.states[entityId]; const stateObj = this.hass.states[entityId];
const domain = computeDomain(entityId); const domain = computeDomain(entityId);
const newMoreInfo = computeShowNewMoreInfo(stateObj);
return html` return html`
${!stateObj <div class="container" data-domain=${domain}>
? html`<ha-alert alert-type="warning"> ${!stateObj
${this.hass.localize( ? html`<ha-alert alert-type="warning">
"ui.dialogs.entity_registry.editor.unavailable" ${this.hass.localize(
)} "ui.dialogs.entity_registry.editor.unavailable"
</ha-alert>` )}
: ""} </ha-alert>`
${stateObj?.attributes.restored && this._entityEntry : ""}
? html`<ha-alert alert-type="warning"> ${stateObj?.attributes.restored && this._entityEntry
${this.hass.localize( ? html`<ha-alert alert-type="warning">
"ui.dialogs.more_info_control.restored.no_longer_provided", ${this.hass.localize(
{ "ui.dialogs.more_info_control.restored.no_longer_provided",
integration: this._entityEntry.platform, {
} integration: this._entityEntry.platform,
)} }
</ha-alert>` )}
: ""} </ha-alert>`
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj) : ""}
? "" <div class="content">
: html` ${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
<state-card-content ? ""
in-dialog : html`
.stateObj=${stateObj} <state-card-content
.hass=${this.hass} in-dialog
></state-card-content> .stateObj=${stateObj}
`} .hass=${this.hass}
${DOMAINS_WITH_MORE_INFO.includes(domain) || ></state-card-content>
!computeShowHistoryComponent(this.hass, entityId) `}
? "" ${DOMAINS_WITH_MORE_INFO.includes(domain) ||
: html`<ha-more-info-history !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} .hass=${this.hass}
.entityId=${this.entityId} ></more-info-content>
></ha-more-info-history>`} <div class="toto"></div>
${DOMAINS_WITH_MORE_INFO.includes(domain) || </div>
!computeShowLogBookComponent(this.hass, entityId) </div>
? ""
: 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>
`; `;
} }
@ -91,6 +99,40 @@ export class MoreInfoInfo extends LitElement {
static get styles() { static get styles() {
return css` 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, state-card-content,
ha-more-info-history, ha-more-info-history,
ha-more-info-logbook:not(:last-child) { ha-more-info-logbook:not(:last-child) {
@ -100,9 +142,6 @@ export class MoreInfoInfo extends LitElement {
ha-alert { ha-alert {
display: block; 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 { import {
EntityRegistryEntry, EntityRegistryEntry,
ExtEntityRegistryEntry, ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../data/entity_registry"; } from "../../data/entity_registry";
import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const"; import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const";
import "../../panels/config/entities/entity-registry-settings";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url"; import { documentationUrl } from "../../util/documentation-url";
import "../../panels/config/entities/entity-registry-settings";
@customElement("ha-more-info-settings") @customElement("ha-more-info-settings")
export class HaMoreInfoSettings extends LitElement { export class HaMoreInfoSettings extends LitElement {
@ -19,18 +18,18 @@ export class HaMoreInfoSettings extends LitElement {
@property({ attribute: false }) public entityId!: string; @property({ attribute: false }) public entityId!: string;
@state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null; @state() private entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private _settingsElementTag?: string; @state() private _settingsElementTag?: string;
protected render() { protected render() {
// loading. // loading.
if (this._entry === undefined) { if (this.entry === undefined) {
return html``; return html``;
} }
// No unique ID // No unique ID
if (this._entry === null) { if (this.entry === null) {
return html` return html`
<div class="content"> <div class="content">
${this.hass.localize( ${this.hass.localize(
@ -54,53 +53,31 @@ export class HaMoreInfoSettings extends LitElement {
} }
return html` return html`
<div @entity-entry-updated=${this._entryUpdated}> ${dynamicElement(this._settingsElementTag, {
${dynamicElement(this._settingsElementTag, { hass: this.hass,
hass: this.hass, entry: this.entry,
entry: this._entry, entityId: this.entityId,
entityId: this.entityId, })}
})}
</div>
`; `;
} }
protected willUpdate(changedProps: PropertyValues) { public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps); if (changedProps.has("entry")) {
if (changedProps.has("entityId")) {
this._entry = undefined;
if (this.entityId) {
this._getEntityReg();
}
}
}
private async _getEntityReg() {
try {
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this.entityId
);
this._loadPlatformSettingTabs(); this._loadPlatformSettingTabs();
} catch {
this._entry = null;
} }
} }
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
this._entry = ev.detail;
}
private async _loadPlatformSettingTabs(): Promise<void> { private async _loadPlatformSettingTabs(): Promise<void> {
if (!this._entry) { if (!this.entry) {
return; return;
} }
if ( 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"; this._settingsElementTag = "entity-registry-settings";
return; 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}`); await import(`../../panels/config/entities/editor-tabs/settings/${tag}`);
this._settingsElementTag = tag; this._settingsElementTag = tag;
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -36,6 +36,15 @@ import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../../../dialogs/generic/show-dialog-box"; } 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") @customElement("dialog-zwave_js-update-firmware-node")
class DialogZWaveJSUpdateFirmwareNode extends LitElement { class DialogZWaveJSUpdateFirmwareNode extends LitElement {
@ -59,6 +68,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
@state() private _nodeStatus?: ZWaveJSNodeStatus; @state() private _nodeStatus?: ZWaveJSNodeStatus;
@state() private _firmwareTarget?: number;
private _subscribedNodeStatus?: Promise<UnsubscribeFunc>; private _subscribedNodeStatus?: Promise<UnsubscribeFunc>;
private _subscribedNodeFirmwareUpdate?: Promise<UnsubscribeFunc>; private _subscribedNodeFirmwareUpdate?: Promise<UnsubscribeFunc>;
@ -80,6 +91,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._updateFinishedMessage = undefined; this._updateFinishedMessage = undefined;
this._firmwareFile = undefined; this._firmwareFile = undefined;
this._nodeStatus = undefined; this._nodeStatus = undefined;
this._firmwareTarget = undefined;
this._uploading = this._updateInProgress = false; this._uploading = this._updateInProgress = false;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
@ -104,6 +116,19 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
)} )}
@file-picked=${this._uploadFile} @file-picked=${this._uploadFile}
></ha-file-upload> ></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 <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._beginFirmwareUpdate} @click=${this._beginFirmwareUpdate}
@ -283,7 +308,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
await uploadFirmwareAndBeginUpdate( await uploadFirmwareAndBeginUpdate(
this.hass, this.hass,
this.device!.id, this.device!.id,
this._firmwareFile! this._firmwareFile!,
this._firmwareTarget
); );
this._updateInProgress = true; this._updateInProgress = true;
this._uploading = false; this._uploading = false;
@ -388,6 +414,10 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
this._subscribedNodeFirmwareUpdate = undefined; this._subscribedNodeFirmwareUpdate = undefined;
} }
private async _firmwareTargetChanged(ev) {
this._firmwareTarget = ev.detail.value.firmware_target;
}
private async _uploadFile(ev) { private async _uploadFile(ev) {
this._firmwareFile = ev.detail.files[0]; 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 { PropertyValues } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { debounce } from "../../../common/util/debounce"; import { debounce } from "../../../common/util/debounce";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { ScriptEntity } from "../../../data/script"; import { ScriptEntity } from "../../../data/script";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
} from "../../../layouts/hass-router-page"; } from "../../../layouts/hass-router-page";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "./ha-script-editor"; import "./ha-script-editor";
import "./ha-script-picker"; import "./ha-script-picker";
@ -21,7 +26,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => {
}; };
@customElement("ha-config-script") @customElement("ha-config-script")
class HaConfigScript extends HassRouterPage { class HaConfigScript extends SubscribeMixin(HassRouterPage) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@ -32,6 +37,16 @@ class HaConfigScript extends HassRouterPage {
@property() public scripts: ScriptEntity[] = []; @property() public scripts: ScriptEntity[] = [];
@state() private _entityReg: EntityRegistryEntry[] = [];
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entityReg = entities;
}),
];
}
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
defaultPage: "dashboard", defaultPage: "dashboard",
routes: { routes: {
@ -78,6 +93,7 @@ class HaConfigScript extends HassRouterPage {
pageEl.isWide = this.isWide; pageEl.isWide = this.isWide;
pageEl.route = this.routeTail; pageEl.route = this.routeTail;
pageEl.showAdvanced = this.showAdvanced; pageEl.showAdvanced = this.showAdvanced;
pageEl.entityRegistry = this._entityReg;
if (this.hass) { if (this.hass) {
if (!pageEl.scripts || !changedProps) { if (!pageEl.scripts || !changedProps) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import { polyfillsLoaded } from "../common/translations/localize";
import { subscribeAreaRegistry } from "../data/area_registry"; import { subscribeAreaRegistry } from "../data/area_registry";
import { broadcastConnectionStatus } from "../data/connection-status"; import { broadcastConnectionStatus } from "../data/connection-status";
import { subscribeDeviceRegistry } from "../data/device_registry"; import { subscribeDeviceRegistry } from "../data/device_registry";
import { subscribeEntityRegistry } from "../data/entity_registry"; import { subscribeEntityRegistryDisplay } from "../data/entity_registry";
import { subscribeFrontendUserData } from "../data/frontend"; import { subscribeFrontendUserData } from "../data/frontend";
import { forwardHaptic } from "../data/haptics"; import { forwardHaptic } from "../data/haptics";
import { DEFAULT_PANEL } from "../data/panel"; import { DEFAULT_PANEL } from "../data/panel";
@ -188,10 +188,22 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
}); });
subscribeEntities(conn, (states) => this._updateHass({ states })); subscribeEntities(conn, (states) => this._updateHass({ states }));
subscribeEntityRegistry(conn, (entityReg) => { subscribeEntityRegistryDisplay(conn, (entityReg) => {
const entities: HomeAssistant["entities"] = {}; const entities: HomeAssistant["entities"] = {};
for (const entity of entityReg) { for (const entity of entityReg.entities) {
entities[entity.entity_id] = entity; 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 }); 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?", "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": "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.", "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_firmware": "Upload Firmware",
"upload_failed": "Upload Failed", "upload_failed": "Upload Failed",
"begin_update": "Begin Firmware Update", "begin_update": "Begin Firmware Update",

View File

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

View File

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