mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20230223.0 (#15575)
This commit is contained in:
commit
dd6437376d
@ -60,7 +60,6 @@
|
||||
"no-restricted-globals": [2, "event"],
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"no-unsafe-optional-chaining": "warn",
|
||||
"prefer-regex-literals": ["warn"],
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-default-export": "off",
|
||||
"import/no-unresolved": "off",
|
||||
|
@ -252,6 +252,22 @@ export class HcMain extends HassElement {
|
||||
msg.urlPath = null;
|
||||
}
|
||||
this._lovelacePath = msg.viewPath;
|
||||
if (msg.urlPath === "energy") {
|
||||
this._lovelaceConfig = {
|
||||
views: [
|
||||
{
|
||||
strategy: {
|
||||
type: "energy",
|
||||
options: { show_date_selection: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
this._urlPath = "energy";
|
||||
this._lovelacePath = 0;
|
||||
this._sendStatus();
|
||||
return;
|
||||
}
|
||||
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
|
||||
this._urlPath = msg.urlPath;
|
||||
this._lovelaceConfig = undefined;
|
||||
|
@ -66,7 +66,7 @@ const incrementalUnits = ["clients", "queries", "ads"];
|
||||
|
||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
mockHass.mockAPI(
|
||||
new RegExp("history/period/.+"),
|
||||
/history\/period\/.+/,
|
||||
(hass, _method, path, _parameters) => {
|
||||
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
|
||||
const entities = params.filter_entity_id.split(",");
|
||||
|
@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
<div class="action">
|
||||
<span>
|
||||
${this._action
|
||||
? describeAction(this.hass, this._action)
|
||||
? describeAction(this.hass, [], this._action)
|
||||
: "<invalid YAML>"}
|
||||
</span>
|
||||
<ha-yaml-editor
|
||||
@ -149,7 +149,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
${ACTIONS.map(
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, conf as any)}</span>
|
||||
<span>${describeAction(this.hass, [], conf as any)}</span>
|
||||
<pre>${dump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
|
@ -71,7 +71,7 @@
|
||||
"@material/mwc-textfield": "^0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "^0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "=1.0.0-pre.2",
|
||||
"@material/web": "=1.0.0-pre.3",
|
||||
"@mdi/js": "7.1.96",
|
||||
"@mdi/svg": "7.1.96",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
@ -216,7 +216,7 @@
|
||||
"lint-staged": "^13.1.2",
|
||||
"lit-analyzer": "^1.2.1",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.29.0",
|
||||
"magic-string": "^0.30.0",
|
||||
"map-stream": "^0.0.7",
|
||||
"merge-stream": "^2.0.0",
|
||||
"mocha": "^10.2.0",
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230222.0"
|
||||
version = "20230223.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
@ -15,7 +15,7 @@ export const computeAttributeValueDisplay = (
|
||||
const attributeValue =
|
||||
value !== undefined ? value : stateObj.attributes[attribute];
|
||||
const domain = computeDomain(entityId);
|
||||
const entity = entities[entityId] as EntityRegistryEntry | undefined;
|
||||
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
|
||||
const translationKey = entity?.translation_key;
|
||||
|
||||
return (
|
||||
@ -38,7 +38,7 @@ export const computeAttributeNameDisplay = (
|
||||
): string => {
|
||||
const entityId = stateObj.entity_id;
|
||||
const domain = computeDomain(entityId);
|
||||
const entity = entities[entityId] as EntityRegistryEntry | undefined;
|
||||
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
|
||||
const translationKey = entity?.translation_key;
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import {
|
||||
updateIsInstallingFromAttributes,
|
||||
@ -49,7 +49,7 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
return localize(`state.default.${state}`);
|
||||
}
|
||||
|
||||
const entity = entities[entityId] as EntityRegistryEntry | undefined;
|
||||
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
|
||||
|
||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||
if (isNumericFromAttributes(attributes)) {
|
||||
|
@ -2,7 +2,7 @@ import {
|
||||
HassEntity,
|
||||
HassEntityAttributeBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||
import { round } from "./round";
|
||||
|
||||
@ -92,11 +92,9 @@ export const formatNumber = (
|
||||
*/
|
||||
export const getNumberFormatOptions = (
|
||||
entityState: HassEntity,
|
||||
entity?: EntityRegistryEntry
|
||||
entity?: EntityRegistryDisplayEntry
|
||||
): Intl.NumberFormatOptions | undefined => {
|
||||
const precision =
|
||||
entity?.options?.sensor?.display_precision ??
|
||||
entity?.options?.sensor?.suggested_display_precision;
|
||||
const precision = entity?.display_precision;
|
||||
if (precision != null) {
|
||||
return {
|
||||
maximumFractionDigits: precision,
|
||||
|
@ -1,4 +1,4 @@
|
||||
const isTemplateRegex = new RegExp("{%|{{");
|
||||
const isTemplateRegex = /{%|{{/;
|
||||
|
||||
export const isTemplate = (value: string): boolean =>
|
||||
isTemplateRegex.test(value);
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
isNumericState,
|
||||
} from "../../common/number/format_number";
|
||||
import { isUnavailableState, UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { timerTimeRemaining } from "../../data/timer";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-label-badge";
|
||||
@ -160,7 +160,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
private _computeValue(
|
||||
domain: string,
|
||||
entityState: HassEntity,
|
||||
entry?: EntityRegistryEntry
|
||||
entry?: EntityRegistryDisplayEntry
|
||||
) {
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
@ -200,7 +200,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
private _computeShowIcon(
|
||||
domain: string,
|
||||
entityState: HassEntity,
|
||||
entry?: EntityRegistryEntry
|
||||
entry?: EntityRegistryDisplayEntry
|
||||
): boolean {
|
||||
if (entityState.state === UNAVAILABLE) {
|
||||
return false;
|
||||
|
@ -12,10 +12,11 @@ import {
|
||||
createAreaRegistryEntry,
|
||||
} from "../data/area_registry";
|
||||
import {
|
||||
DeviceEntityLookup,
|
||||
DeviceEntityDisplayLookup,
|
||||
DeviceRegistryEntry,
|
||||
getDeviceEntityDisplayLookup,
|
||||
} from "../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
@ -113,7 +114,7 @@ export class HaAreaPicker extends LitElement {
|
||||
(
|
||||
areas: AreaRegistryEntry[],
|
||||
devices: DeviceRegistryEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
entities: EntityRegistryDisplayEntry[],
|
||||
includeDomains: this["includeDomains"],
|
||||
excludeDomains: this["excludeDomains"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"],
|
||||
@ -133,111 +134,107 @@ export class HaAreaPicker extends LitElement {
|
||||
];
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||
let inputEntities: EntityRegistryEntry[] | undefined;
|
||||
let inputEntities: EntityRegistryDisplayEntry[] | undefined;
|
||||
|
||||
if (
|
||||
includeDomains ||
|
||||
excludeDomains ||
|
||||
includeDeviceClasses ||
|
||||
deviceFilter ||
|
||||
entityFilter
|
||||
) {
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
}
|
||||
inputDevices = devices;
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
|
||||
inputDevices = devices;
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
|
||||
if (includeDomains) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) =>
|
||||
if (includeDomains) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) =>
|
||||
includeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) =>
|
||||
includeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) =>
|
||||
includeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (excludeDomains) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return true;
|
||||
}
|
||||
return entities.every(
|
||||
if (excludeDomains) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return true;
|
||||
}
|
||||
return entities.every(
|
||||
(entity) =>
|
||||
!excludeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
});
|
||||
inputEntities = inputEntities!.filter(
|
||||
(entity) =>
|
||||
!excludeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
});
|
||||
inputEntities = inputEntities!.filter(
|
||||
(entity) => !excludeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
if (includeDeviceClasses) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
);
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
);
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
}
|
||||
|
||||
if (deviceFilter) {
|
||||
inputDevices = inputDevices!.filter((device) =>
|
||||
deviceFilter!(device)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceFilter) {
|
||||
inputDevices = inputDevices!.filter((device) => deviceFilter!(device));
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
if (entityFilter) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return entityFilter(stateObj);
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return entityFilter(stateObj);
|
||||
return entityFilter!(stateObj);
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return entityFilter!(stateObj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let outputAreas = areas;
|
||||
|
@ -1,3 +1,10 @@
|
||||
import {
|
||||
DIRECTION_HORIZONTAL,
|
||||
DIRECTION_VERTICAL,
|
||||
Manager,
|
||||
Swipe,
|
||||
Tap,
|
||||
} from "@egjs/hammerjs";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -6,7 +13,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@ -30,8 +37,11 @@ export class HaControlSwitch extends LitElement {
|
||||
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
|
||||
@property({ type: String }) pathOff?: string;
|
||||
|
||||
private _mc?: HammerManager;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.setupListeners();
|
||||
this.setAttribute("role", "switch");
|
||||
if (!this.hasAttribute("tabindex")) {
|
||||
this.setAttribute("tabindex", "0");
|
||||
@ -53,14 +63,70 @@ export class HaControlSwitch extends LitElement {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("keydown", this._keydown);
|
||||
this.addEventListener("click", this._toggle);
|
||||
this.setupListeners();
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.destroyListeners();
|
||||
}
|
||||
|
||||
@query("#switch")
|
||||
private switch!: HTMLDivElement;
|
||||
|
||||
setupListeners() {
|
||||
if (this.switch && !this._mc) {
|
||||
this._mc = new Manager(this.switch, {
|
||||
touchAction: this.vertical ? "pan-x" : "pan-y",
|
||||
});
|
||||
this._mc.add(
|
||||
new Swipe({
|
||||
direction: this.vertical ? DIRECTION_VERTICAL : DIRECTION_HORIZONTAL,
|
||||
})
|
||||
);
|
||||
|
||||
this._mc.add(new Tap({ event: "singletap" }));
|
||||
|
||||
if (this.vertical) {
|
||||
this._mc.on("swipeup", () => {
|
||||
if (this.disabled) return;
|
||||
this.checked = !!this.reversed;
|
||||
fireEvent(this, "change");
|
||||
});
|
||||
|
||||
this._mc.on("swipedown", () => {
|
||||
if (this.disabled) return;
|
||||
this.checked = !this.reversed;
|
||||
fireEvent(this, "change");
|
||||
});
|
||||
} else {
|
||||
this._mc.on("swiperight", () => {
|
||||
if (this.disabled) return;
|
||||
this.checked = !this.reversed;
|
||||
fireEvent(this, "change");
|
||||
});
|
||||
|
||||
this._mc.on("swipeleft", () => {
|
||||
if (this.disabled) return;
|
||||
this.checked = !!this.reversed;
|
||||
fireEvent(this, "change");
|
||||
});
|
||||
}
|
||||
|
||||
this._mc.on("singletap", () => {
|
||||
if (this.disabled) return;
|
||||
this._toggle();
|
||||
});
|
||||
this.addEventListener("keydown", this._keydown);
|
||||
}
|
||||
}
|
||||
|
||||
destroyListeners() {
|
||||
if (this._mc) {
|
||||
this._mc.destroy();
|
||||
this._mc = undefined;
|
||||
}
|
||||
this.removeEventListener("keydown", this._keydown);
|
||||
this.removeEventListener("click", this._toggle);
|
||||
}
|
||||
|
||||
private _keydown(ev: any) {
|
||||
@ -73,7 +139,7 @@ export class HaControlSwitch extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="switch">
|
||||
<div id="switch" class="switch">
|
||||
<div class="background"></div>
|
||||
<div class="button" aria-hidden="true">
|
||||
${this.checked
|
||||
|
@ -45,6 +45,7 @@ export class HaDialog extends DialogBase {
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.contentElement.removeEventListener("scroll", this._onScroll);
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,9 @@ export class Gauge extends LitElement {
|
||||
|
||||
@property({ type: Number }) public value = 0;
|
||||
|
||||
@property({ attribute: false })
|
||||
public formatOptions?: Intl.NumberFormatOptions;
|
||||
|
||||
@property({ type: String }) public valueText?: string;
|
||||
|
||||
@property() public locale!: FrontendLocaleData;
|
||||
@ -132,7 +135,8 @@ export class Gauge extends LitElement {
|
||||
${
|
||||
this._segment_label
|
||||
? this._segment_label
|
||||
: this.valueText || formatNumber(this.value, this.locale)
|
||||
: this.valueText ||
|
||||
formatNumber(this.value, this.locale, this.formatOptions)
|
||||
}${
|
||||
this._segment_label
|
||||
? ""
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import {
|
||||
EntitySources,
|
||||
fetchEntitySourcesWithCache,
|
||||
@ -18,13 +14,12 @@ import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
} from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-area-picker";
|
||||
import "../ha-areas-picker";
|
||||
|
||||
@customElement("ha-selector-area")
|
||||
export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
export class HaAreaSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: AreaSelector;
|
||||
@ -41,18 +36,8 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.device_id !== null);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _hasIntegration(selector: AreaSelector) {
|
||||
return (
|
||||
(selector.area?.entity &&
|
||||
@ -127,10 +112,12 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const deviceIntegrations =
|
||||
this._entitySources && this._entities
|
||||
? this._deviceIntegrationLookup(this._entitySources, this._entities)
|
||||
: undefined;
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return ensureArray(this.selector.area.device).some((filter) =>
|
||||
filterSelectorDevices(filter, device, deviceIntegrations)
|
||||
|
@ -2,12 +2,11 @@ import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { AttributeSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entity-attribute-picker";
|
||||
|
||||
@customElement("ha-selector-attribute")
|
||||
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
||||
export class HaSelectorAttribute extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: AttributeSelector;
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import {
|
||||
EntitySources,
|
||||
fetchEntitySourcesWithCache,
|
||||
@ -18,21 +14,18 @@ import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
} from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../device/ha-device-picker";
|
||||
import "../device/ha-devices-picker";
|
||||
|
||||
@customElement("ha-selector-device")
|
||||
export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
||||
export class HaDeviceSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: DeviceSelector;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
@ -45,14 +38,6 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.device_id !== null);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _hasIntegration(selector: DeviceSelector) {
|
||||
return (
|
||||
(selector.device?.filter &&
|
||||
@ -118,10 +103,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
|
||||
if (!this.selector.device?.filter) {
|
||||
return true;
|
||||
}
|
||||
const deviceIntegrations =
|
||||
this._entitySources && this._entities
|
||||
? this._deviceIntegrationLookup(this._entitySources, this._entities)
|
||||
: undefined;
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return ensureArray(this.selector.device.filter).some((filter) =>
|
||||
filterSelectorDevices(filter, device, deviceIntegrations)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// @ts-ignore
|
||||
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import {
|
||||
mdiClose,
|
||||
mdiDevices,
|
||||
@ -9,13 +10,14 @@ import {
|
||||
mdiUnfoldMoreVertical,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||
@ -23,7 +25,7 @@ import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
} from "../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./device/ha-device-picker";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
@ -33,8 +35,6 @@ import "./ha-area-picker";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-svg-icon";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends LitElement {
|
||||
@ -551,7 +551,7 @@ export class HaTargetPicker extends LitElement {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
|
||||
private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean {
|
||||
if (entity.entity_category) {
|
||||
return false;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
mdiProgressWrench,
|
||||
mdiRecordCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -14,12 +15,16 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import { relativeTime } from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
import {
|
||||
ChooseAction,
|
||||
@ -193,6 +198,7 @@ class ActionRenderer {
|
||||
|
||||
constructor(
|
||||
private hass: HomeAssistant,
|
||||
private entityReg: EntityRegistryEntry[],
|
||||
private entries: TemplateResult[],
|
||||
private trace: AutomationTraceExtended,
|
||||
private logbookRenderer: LogbookRenderer,
|
||||
@ -298,7 +304,7 @@ class ActionRenderer {
|
||||
|
||||
this._renderEntry(
|
||||
path,
|
||||
describeAction(this.hass, data, actionType),
|
||||
describeAction(this.hass, this.entityReg, data, actionType),
|
||||
undefined,
|
||||
data.enabled === false
|
||||
);
|
||||
@ -441,7 +447,9 @@ class ActionRenderer {
|
||||
) as RepeatAction;
|
||||
const disabled = repeatConfig.enabled === false;
|
||||
|
||||
const name = repeatConfig.alias || describeAction(this.hass, repeatConfig);
|
||||
const name =
|
||||
repeatConfig.alias ||
|
||||
describeAction(this.hass, this.entityReg, repeatConfig);
|
||||
|
||||
this._renderEntry(repeatPath, name, undefined, disabled);
|
||||
|
||||
@ -577,6 +585,16 @@ export class HaAutomationTracer extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public allowPick = false;
|
||||
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entityReg = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.trace) {
|
||||
return html``;
|
||||
@ -592,6 +610,7 @@ export class HaAutomationTracer extends LitElement {
|
||||
);
|
||||
const actionRenderer = new ActionRenderer(
|
||||
this.hass,
|
||||
this._entityReg,
|
||||
entries,
|
||||
this.trace,
|
||||
logbookRenderer,
|
||||
|
@ -4,7 +4,10 @@ import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { EntityRegistryEntry } from "./entity_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity_registry";
|
||||
import type { EntitySources } from "./entity_sources";
|
||||
|
||||
export interface DeviceRegistryEntry {
|
||||
@ -25,6 +28,10 @@ export interface DeviceRegistryEntry {
|
||||
configuration_url: string | null;
|
||||
}
|
||||
|
||||
export interface DeviceEntityDisplayLookup {
|
||||
[deviceId: string]: EntityRegistryDisplayEntry[];
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
@ -147,9 +154,25 @@ export const getDeviceEntityLookup = (
|
||||
return deviceEntityLookup;
|
||||
};
|
||||
|
||||
export const getDeviceEntityDisplayLookup = (
|
||||
entities: EntityRegistryDisplayEntry[]
|
||||
): DeviceEntityDisplayLookup => {
|
||||
const deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
return deviceEntityLookup;
|
||||
};
|
||||
|
||||
export const getDeviceIntegrationLookup = (
|
||||
entitySources: EntitySources,
|
||||
entities: EntityRegistryEntry[]
|
||||
entities: EntityRegistryDisplayEntry[]
|
||||
): Record<string, string[]> => {
|
||||
const deviceIntegrations: Record<string, string[]> = {};
|
||||
|
||||
|
@ -6,6 +6,35 @@ import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
type entityCategory = "config" | "diagnostic";
|
||||
|
||||
export interface EntityRegistryDisplayEntry {
|
||||
entity_id: string;
|
||||
name?: string;
|
||||
device_id?: string;
|
||||
area_id?: string;
|
||||
hidden?: boolean;
|
||||
entity_category?: entityCategory;
|
||||
translation_key?: string;
|
||||
platform?: string;
|
||||
display_precision?: number;
|
||||
}
|
||||
|
||||
interface EntityRegistryDisplayEntryResponse {
|
||||
entities: {
|
||||
ei: string;
|
||||
di?: string;
|
||||
ai?: string;
|
||||
ec?: number;
|
||||
en?: string;
|
||||
pl?: string;
|
||||
tk?: string;
|
||||
hb?: boolean;
|
||||
dp?: number;
|
||||
}[];
|
||||
entity_categories: Record<number, entityCategory>;
|
||||
}
|
||||
|
||||
export interface EntityRegistryEntry {
|
||||
id: string;
|
||||
entity_id: string;
|
||||
@ -17,7 +46,7 @@ export interface EntityRegistryEntry {
|
||||
area_id: string | null;
|
||||
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
|
||||
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
|
||||
entity_category: "config" | "diagnostic" | null;
|
||||
entity_category: entityCategory | null;
|
||||
has_entity_name: boolean;
|
||||
original_name?: string;
|
||||
unique_id: string;
|
||||
@ -154,6 +183,11 @@ export const fetchEntityRegistry = (conn: Connection) =>
|
||||
type: "config/entity_registry/list",
|
||||
});
|
||||
|
||||
export const fetchEntityRegistryDisplay = (conn: Connection) =>
|
||||
conn.sendMessagePromise<EntityRegistryDisplayEntryResponse>({
|
||||
type: "config/entity_registry/list_for_display",
|
||||
});
|
||||
|
||||
const subscribeEntityRegistryUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<EntityRegistryEntry[]>
|
||||
@ -182,6 +216,34 @@ export const subscribeEntityRegistry = (
|
||||
onChange
|
||||
);
|
||||
|
||||
const subscribeEntityRegistryDisplayUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<EntityRegistryDisplayEntryResponse>
|
||||
) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchEntityRegistryDisplay(conn).then((entities) =>
|
||||
store.setState(entities, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"entity_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeEntityRegistryDisplay = (
|
||||
conn: Connection,
|
||||
onChange: (entities: EntityRegistryDisplayEntryResponse) => void
|
||||
) =>
|
||||
createCollection<EntityRegistryDisplayEntryResponse>(
|
||||
"_entityRegistryDisplay",
|
||||
fetchEntityRegistryDisplay,
|
||||
subscribeEntityRegistryDisplayUpdates,
|
||||
conn,
|
||||
onChange
|
||||
);
|
||||
|
||||
export const sortEntityRegistryByName = (
|
||||
entries: EntityRegistryEntry[],
|
||||
language: string
|
||||
@ -190,10 +252,20 @@ export const sortEntityRegistryByName = (
|
||||
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
|
||||
);
|
||||
|
||||
export const entityRegistryByEntityId = memoizeOne(
|
||||
(entries: EntityRegistryEntry[]) => {
|
||||
const entities: Record<string, EntityRegistryEntry> = {};
|
||||
for (const entity of entries) {
|
||||
entities[entity.entity_id] = entity;
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
);
|
||||
|
||||
export const entityRegistryById = memoizeOne(
|
||||
(entries: HomeAssistant["entities"]) => {
|
||||
const entities: HomeAssistant["entities"] = {};
|
||||
for (const entity of Object.values(entries)) {
|
||||
(entries: EntityRegistryEntry[]) => {
|
||||
const entities: Record<string, EntityRegistryEntry> = {};
|
||||
for (const entity of entries) {
|
||||
entities[entity.id] = entity;
|
||||
}
|
||||
return entities;
|
||||
|
@ -11,6 +11,7 @@ import { computeDeviceName } from "./device_registry";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
entityRegistryById,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity_registry";
|
||||
import { domainToName } from "./integration";
|
||||
import {
|
||||
@ -33,6 +34,7 @@ import {
|
||||
|
||||
export const describeAction = <T extends ActionType>(
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
action: ActionTypes[T],
|
||||
actionType?: T,
|
||||
ignoreAlias = false
|
||||
@ -91,7 +93,7 @@ export const describeAction = <T extends ActionType>(
|
||||
targets.push(targetThing);
|
||||
}
|
||||
} else {
|
||||
const entityReg = entityRegistryById(hass.entities)[targetThing];
|
||||
const entityReg = entityRegistryById(entityRegistry)[targetThing];
|
||||
if (entityReg) {
|
||||
targets.push(
|
||||
computeEntityRegistryName(hass, entityReg) || targetThing
|
||||
|
@ -26,12 +26,9 @@ export interface ThreadRouterDiscoveryEvent {
|
||||
}
|
||||
|
||||
class DiscoveryStream {
|
||||
hass: HomeAssistant;
|
||||
|
||||
routers: { [key: string]: ThreadRouter };
|
||||
|
||||
constructor(hass: HomeAssistant) {
|
||||
this.hass = hass;
|
||||
constructor() {
|
||||
this.routers = {};
|
||||
}
|
||||
|
||||
@ -49,7 +46,7 @@ export const subscribeDiscoverThreadRouters = (
|
||||
hass: HomeAssistant,
|
||||
callbackFunction: (routers: ThreadRouter[]) => void
|
||||
) => {
|
||||
const stream = new DiscoveryStream(hass);
|
||||
const stream = new DiscoveryStream();
|
||||
return hass.connection.subscribeMessage<ThreadRouterDiscoveryEvent>(
|
||||
(message) => callbackFunction(stream.processEvent(message)),
|
||||
{
|
||||
|
@ -757,10 +757,14 @@ export const fetchZwaveNodeFirmwareUpdateCapabilities = (
|
||||
export const uploadFirmwareAndBeginUpdate = async (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
file: File
|
||||
file: File,
|
||||
target?: number
|
||||
) => {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
if (target !== undefined) {
|
||||
fd.append("target", target.toString());
|
||||
}
|
||||
const resp = await hass.fetchWithAuth(
|
||||
`/api/zwave_js/firmware/upload/${device_id}`,
|
||||
{
|
||||
|
@ -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%;
|
||||
}
|
||||
`;
|
@ -23,7 +23,6 @@ import {
|
||||
lightSupportsColor,
|
||||
lightSupportsColorMode,
|
||||
} from "../../../../data/light";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LightColorPickerViewParams } from "./show-view-light-color-picker";
|
||||
|
||||
@ -78,117 +77,115 @@ class MoreInfoViewLightColorPicker extends LitElement {
|
||||
lightSupportsColorMode(this.stateObj, LightColorMode.RGBW);
|
||||
|
||||
return html`
|
||||
<div>
|
||||
${this._modes.length > 1
|
||||
${this._modes.length > 1
|
||||
? html`
|
||||
<mwc-tab-bar
|
||||
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
|
||||
@MDCTabBar:activated=${this._handleTabChanged}
|
||||
>
|
||||
${this._modes.map(
|
||||
(value) =>
|
||||
html`<mwc-tab
|
||||
.label=${this.hass.localize(
|
||||
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
|
||||
)}
|
||||
></mwc-tab>`
|
||||
)}
|
||||
</mwc-tab-bar>
|
||||
`
|
||||
: ""}
|
||||
<div class="content">
|
||||
${this._mode === LightColorMode.COLOR_TEMP
|
||||
? html`
|
||||
<mwc-tab-bar
|
||||
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
|
||||
@MDCTabBar:activated=${this._handleTabChanged}
|
||||
<ha-control-slider
|
||||
vertical
|
||||
class="color_temp"
|
||||
label=${this.hass.localize("ui.card.light.color_temperature")}
|
||||
min="1"
|
||||
max="100"
|
||||
mode="cursor"
|
||||
.value=${this._ctSliderValue}
|
||||
@value-changed=${this._ctSliderChanged}
|
||||
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
|
||||
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
|
||||
>
|
||||
${this._modes.map(
|
||||
(value) =>
|
||||
html`<mwc-tab
|
||||
.label=${this.hass.localize(
|
||||
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
|
||||
)}
|
||||
></mwc-tab>`
|
||||
)}
|
||||
</mwc-tab-bar>
|
||||
</ha-control-slider>
|
||||
`
|
||||
: ""}
|
||||
<div class="content">
|
||||
${this._mode === LightColorMode.COLOR_TEMP
|
||||
? html`
|
||||
<ha-control-slider
|
||||
vertical
|
||||
class="color_temp"
|
||||
label=${this.hass.localize("ui.card.light.color_temperature")}
|
||||
min="1"
|
||||
max="100"
|
||||
mode="cursor"
|
||||
.value=${this._ctSliderValue}
|
||||
@value-changed=${this._ctSliderChanged}
|
||||
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
|
||||
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
|
||||
${this._mode === "color"
|
||||
? html`
|
||||
<div class="segmentationContainer">
|
||||
<ha-color-picker
|
||||
class="color"
|
||||
@colorselected=${this._colorPicked}
|
||||
.desiredRgbColor=${this._colorPickerColor}
|
||||
throttle="500"
|
||||
.hueSegments=${this._hueSegments}
|
||||
.saturationSegments=${this._saturationSegments}
|
||||
>
|
||||
</ha-control-slider>
|
||||
`
|
||||
: ""}
|
||||
${this._mode === "color"
|
||||
? html`
|
||||
<div class="segmentationContainer">
|
||||
<ha-color-picker
|
||||
class="color"
|
||||
@colorselected=${this._colorPicked}
|
||||
.desiredRgbColor=${this._colorPickerColor}
|
||||
throttle="500"
|
||||
.hueSegments=${this._hueSegments}
|
||||
.saturationSegments=${this._saturationSegments}
|
||||
>
|
||||
</ha-color-picker>
|
||||
<ha-icon-button
|
||||
.path=${mdiPalette}
|
||||
@click=${this._segmentClick}
|
||||
class="segmentationButton"
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</ha-color-picker>
|
||||
<ha-icon-button
|
||||
.path=${mdiPalette}
|
||||
@click=${this._segmentClick}
|
||||
class="segmentationButton"
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
|
||||
${supportsRgbw || supportsRgbww
|
||||
? html`<ha-labeled-slider
|
||||
${supportsRgbw || supportsRgbww
|
||||
? html`<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.color_brightness"
|
||||
)}
|
||||
icon="hass:brightness-7"
|
||||
max="100"
|
||||
.value=${this._colorBrightnessSliderValue}
|
||||
@change=${this._colorBrightnessSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>`
|
||||
: ""}
|
||||
${supportsRgbw
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.color_brightness"
|
||||
"ui.card.light.white_value"
|
||||
)}
|
||||
icon="hass:brightness-7"
|
||||
icon="hass:file-word-box"
|
||||
max="100"
|
||||
.value=${this._colorBrightnessSliderValue}
|
||||
@change=${this._colorBrightnessSliderChanged}
|
||||
.name=${"wv"}
|
||||
.value=${this._wvSliderValue}
|
||||
@change=${this._wvSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>`
|
||||
: ""}
|
||||
${supportsRgbw
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.white_value"
|
||||
)}
|
||||
icon="hass:file-word-box"
|
||||
max="100"
|
||||
.name=${"wv"}
|
||||
.value=${this._wvSliderValue}
|
||||
@change=${this._wvSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
${supportsRgbww
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.cold_white_value"
|
||||
)}
|
||||
icon="hass:file-word-box-outline"
|
||||
max="100"
|
||||
.name=${"cw"}
|
||||
.value=${this._cwSliderValue}
|
||||
@change=${this._wvSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>
|
||||
<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.warm_white_value"
|
||||
)}
|
||||
icon="hass:file-word-box"
|
||||
max="100"
|
||||
.name=${"ww"}
|
||||
.value=${this._wwSliderValue}
|
||||
@change=${this._wvSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
${supportsRgbww
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.cold_white_value"
|
||||
)}
|
||||
icon="hass:file-word-box-outline"
|
||||
max="100"
|
||||
.name=${"cw"}
|
||||
.value=${this._cwSliderValue}
|
||||
@change=${this._wvSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>
|
||||
<ha-labeled-slider
|
||||
.caption=${this.hass.localize(
|
||||
"ui.card.light.warm_white_value"
|
||||
)}
|
||||
icon="hass:file-word-box"
|
||||
max="100"
|
||||
.name=${"ww"}
|
||||
.value=${this._wwSliderValue}
|
||||
@change=${this._wvSliderChanged}
|
||||
pin
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -482,13 +479,18 @@ class MoreInfoViewLightColorPicker extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.segmentationContainer {
|
||||
|
@ -94,12 +94,12 @@ export const computeShowLogBookComponent = (
|
||||
return true;
|
||||
};
|
||||
|
||||
export const computeShowNewMoreInfo = (stateObj: HassEntity) => {
|
||||
export const computeShowNewMoreInfo = (stateObj: HassEntity): boolean => {
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
if (domain === "group") {
|
||||
const groupDomain = computeGroupDomain(stateObj as GroupEntity);
|
||||
return (
|
||||
groupDomain &&
|
||||
groupDomain != null &&
|
||||
groupDomain !== "group" &&
|
||||
DOMAINS_WITH_NEW_MORE_INFO.includes(groupDomain)
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import { computeGroupDomain, GroupEntity } from "../../../data/group";
|
||||
import "../../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||
import {
|
||||
domainMoreInfoType,
|
||||
importMoreInfoControl,
|
||||
@ -94,12 +95,15 @@ class MoreInfoGroup extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
state-card-content {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
return [
|
||||
moreInfoControlStyle,
|
||||
css`
|
||||
state-card-content {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-select";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import {
|
||||
LightColorMode,
|
||||
LightEntity,
|
||||
@ -32,6 +33,7 @@ import {
|
||||
lightSupportsColorMode,
|
||||
} from "../../../data/light";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||
import "../components/ha-more-info-state-header";
|
||||
import "../components/ha-more-info-toggle";
|
||||
import "../components/lights/ha-more-info-light-brightness";
|
||||
@ -85,12 +87,12 @@ class MoreInfoLight extends LitElement {
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
.stateOverride=${stateOverride}
|
||||
></ha-more-info-state-header>
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
.stateOverride=${stateOverride}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls">
|
||||
${supportsBrightness
|
||||
? html`
|
||||
<ha-more-info-light-brightness
|
||||
@ -184,17 +186,19 @@ class MoreInfoLight extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="brightness,color_temp,color_temp_kelvin,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,min_color_temp_kelvin,max_color_temp_kelvin,entity_id,supported_color_modes,color_mode"
|
||||
></ha-attributes>
|
||||
</div>
|
||||
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="brightness,color_temp,color_temp_kelvin,white_value,effect_list,effect,hs_color,rgb_color,rgbw_color,rgbww_color,xy_color,min_mireds,max_mireds,min_color_temp_kelvin,max_color_temp_kelvin,entity_id,supported_color_modes,color_mode"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggle = () => {
|
||||
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
|
||||
forwardHaptic("light");
|
||||
this.hass.callService("light", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
@ -230,41 +234,29 @@ class MoreInfoLight extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
return [
|
||||
moreInfoControlStyle,
|
||||
css`
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.buttons > * {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.buttons > * {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
ha-more-info-light-brightness,
|
||||
ha-more-info-toggle {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
ha-attributes {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
md-outlined-icon-button-toggle,
|
||||
md-outlined-icon-button {
|
||||
--ha-icon-display: block;
|
||||
--md-sys-color-on-surface: var(--secondary-text-color);
|
||||
--md-sys-color-on-surface-variant: var(--secondary-text-color);
|
||||
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
|
||||
--md-sys-color-outline: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
md-outlined-icon-button-toggle,
|
||||
md-outlined-icon-button {
|
||||
--ha-icon-display: block;
|
||||
--md-sys-color-on-surface: var(--secondary-text-color);
|
||||
--md-sys-color-on-surface-variant: var(--secondary-text-color);
|
||||
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
|
||||
--md-sys-color-outline: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-attributes";
|
||||
import { LightEntity } from "../../../data/light";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||
import "../components/ha-more-info-state-header";
|
||||
import "../components/ha-more-info-toggle";
|
||||
|
||||
@ -19,41 +20,27 @@ class MoreInfoSiren extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-more-info-state-header>
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls">
|
||||
<ha-more-info-toggle
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
.iconPathOn=${mdiVolumeHigh}
|
||||
.iconPathOff=${mdiVolumeOff}
|
||||
></ha-more-info-toggle>
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-attributes>
|
||||
</div>
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ha-more-info-toggle {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
ha-attributes {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
return moreInfoControlStyle;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { mdiPower, mdiPowerOff } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-attributes";
|
||||
import { LightEntity } from "../../../data/light";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||
import "../components/ha-more-info-state-header";
|
||||
import "../components/ha-more-info-toggle";
|
||||
|
||||
@ -19,41 +20,27 @@ class MoreInfoSwitch extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-more-info-state-header>
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls">
|
||||
<ha-more-info-toggle
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
.iconPathOn=${mdiPower}
|
||||
.iconPathOff=${mdiPowerOff}
|
||||
></ha-more-info-toggle>
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-attributes>
|
||||
</div>
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ha-more-info-toggle {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
ha-attributes {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
return moreInfoControlStyle;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,11 @@ import "../../components/ha-icon-button";
|
||||
import "../../components/ha-icon-button-prev";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-related-items";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import "../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@ -77,6 +81,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@state() private _childView?: ChildView;
|
||||
|
||||
@state() private _entry?: ExtEntityRegistryEntry;
|
||||
|
||||
public showDialog(params: MoreInfoDialogParams) {
|
||||
this._entityId = params.entityId;
|
||||
if (!this._entityId) {
|
||||
@ -86,10 +92,22 @@ export class MoreInfoDialog extends LitElement {
|
||||
this._currView = params.view || "info";
|
||||
this._childView = undefined;
|
||||
this.large = false;
|
||||
this._loadEntityRegistryEntry();
|
||||
}
|
||||
|
||||
private async _loadEntityRegistryEntry() {
|
||||
if (!this._entityId) {
|
||||
return;
|
||||
}
|
||||
this._entry = await getExtendedEntityRegistryEntry(
|
||||
this.hass,
|
||||
this._entityId
|
||||
);
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._entityId = undefined;
|
||||
this._entry = undefined;
|
||||
this._childView = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
@ -172,7 +190,10 @@ export class MoreInfoDialog extends LitElement {
|
||||
idToPassThroughUrl = stateObj.attributes.id;
|
||||
}
|
||||
if (EDITABLE_DOMAINS_WITH_UNIQUE_ID.includes(domain)) {
|
||||
idToPassThroughUrl = this.hass.entities[this._entityId!].unique_id;
|
||||
if (!this._entry) {
|
||||
return;
|
||||
}
|
||||
idToPassThroughUrl = this._entry.unique_id;
|
||||
}
|
||||
|
||||
navigate(`/config/${domain}/edit/${idToPassThroughUrl}`);
|
||||
@ -203,13 +224,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
const isInfoView = this._currView === "info" && !this._childView;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${title}
|
||||
hideActions
|
||||
data-domain=${domain}
|
||||
>
|
||||
<ha-dialog open @closed=${this.closeDialog} .heading=${title} hideActions>
|
||||
<div slot="heading" class="heading">
|
||||
<ha-header-bar>
|
||||
${isInfoView
|
||||
@ -335,10 +350,14 @@ export class MoreInfoDialog extends LitElement {
|
||||
@show-child-view=${this._showChildView}
|
||||
>
|
||||
${this._childView
|
||||
? dynamicElement(this._childView.viewTag, {
|
||||
hass: this.hass,
|
||||
params: this._childView.viewParams,
|
||||
})
|
||||
? html`
|
||||
<div class="child-view">
|
||||
${dynamicElement(this._childView.viewTag, {
|
||||
hass: this.hass,
|
||||
params: this._childView.viewParams,
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: cache(
|
||||
this._currView === "info"
|
||||
? html`
|
||||
@ -360,6 +379,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
<ha-more-info-settings
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
@entity-entry-updated=${this._entryUpdated}
|
||||
></ha-more-info-settings>
|
||||
`
|
||||
: this._currView === "related"
|
||||
@ -385,12 +406,12 @@ export class MoreInfoDialog extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("_currView")) {
|
||||
this.setAttribute("view", this._currView);
|
||||
this._childView = undefined;
|
||||
}
|
||||
if (changedProps.has("_childView")) {
|
||||
this.toggleAttribute("has-child-view", !!this._childView);
|
||||
}
|
||||
}
|
||||
|
||||
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
|
||||
this._entry = ev.detail;
|
||||
}
|
||||
|
||||
private _enlarge() {
|
||||
@ -407,7 +428,6 @@ export class MoreInfoDialog extends LitElement {
|
||||
--dialog-content-position: static;
|
||||
--vertical-align-dialog: flex-start;
|
||||
--dialog-content-padding: 0;
|
||||
--content-padding: 24px;
|
||||
}
|
||||
|
||||
ha-header-bar {
|
||||
@ -417,6 +437,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
outline: none;
|
||||
}
|
||||
@ -426,22 +447,16 @@ export class MoreInfoDialog extends LitElement {
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
:host([view="settings"]) ha-dialog {
|
||||
--content-padding: 0;
|
||||
ha-related-items,
|
||||
ha-more-info-history-and-logbook {
|
||||
padding: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host([view="info"]) ha-dialog[data-domain="camera"] {
|
||||
--content-padding: 0;
|
||||
/* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */
|
||||
--video-max-height: calc(100vh - 65px - 72px);
|
||||
}
|
||||
|
||||
:host([has-child-view]) ha-dialog {
|
||||
--content-padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--content-padding);
|
||||
@media all and (max-width: 450px) {
|
||||
.child-view > * {
|
||||
min-height: calc(100vh - 56px);
|
||||
}
|
||||
}
|
||||
|
||||
.main-title {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import {
|
||||
@ -34,6 +34,18 @@ export class MoreInfoHistoryAndLogbook extends LitElement {
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-more-info-history,
|
||||
ha-more-info-logbook {
|
||||
display: block;
|
||||
}
|
||||
ha-more-info-history + ha-more-info-logbook {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from "./const";
|
||||
import "./ha-more-info-history";
|
||||
import "./ha-more-info-logbook";
|
||||
import "./more-info-content";
|
||||
|
||||
@customElement("ha-more-info-info")
|
||||
export class MoreInfoInfo extends LitElement {
|
||||
@ -29,52 +30,59 @@ export class MoreInfoInfo extends LitElement {
|
||||
const entityId = this.entityId;
|
||||
const stateObj = this.hass.states[entityId];
|
||||
const domain = computeDomain(entityId);
|
||||
const newMoreInfo = computeShowNewMoreInfo(stateObj);
|
||||
|
||||
return html`
|
||||
${!stateObj
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.unavailable"
|
||||
)}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${stateObj?.attributes.restored && this._entityEntry
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.restored.no_longer_provided",
|
||||
{
|
||||
integration: this._entityEntry.platform,
|
||||
}
|
||||
)}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
|
||||
? ""
|
||||
: html`
|
||||
<state-card-content
|
||||
in-dialog
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></state-card-content>
|
||||
`}
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
|
||||
!computeShowHistoryComponent(this.hass, entityId)
|
||||
? ""
|
||||
: html`<ha-more-info-history
|
||||
<div class="container" data-domain=${domain}>
|
||||
${!stateObj
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.unavailable"
|
||||
)}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${stateObj?.attributes.restored && this._entityEntry
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.restored.no_longer_provided",
|
||||
{
|
||||
integration: this._entityEntry.platform,
|
||||
}
|
||||
)}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
<div class="content">
|
||||
${DOMAINS_NO_INFO.includes(domain) || computeShowNewMoreInfo(stateObj)
|
||||
? ""
|
||||
: html`
|
||||
<state-card-content
|
||||
in-dialog
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></state-card-content>
|
||||
`}
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
|
||||
!computeShowHistoryComponent(this.hass, entityId)
|
||||
? ""
|
||||
: html`<ha-more-info-history
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
></ha-more-info-history>`}
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
|
||||
!computeShowLogBookComponent(this.hass, entityId)
|
||||
? ""
|
||||
: html`<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
></ha-more-info-logbook>`}
|
||||
<more-info-content
|
||||
?full-height=${newMoreInfo}
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
></ha-more-info-history>`}
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
|
||||
!computeShowLogBookComponent(this.hass, entityId)
|
||||
? ""
|
||||
: html`<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
></ha-more-info-logbook>`}
|
||||
<more-info-content
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></more-info-content>
|
||||
></more-info-content>
|
||||
<div class="toto"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -91,6 +99,40 @@ export class MoreInfoInfo extends LitElement {
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
.container {
|
||||
min-height: calc(100vh - 56px);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
padding-bottom: max(env(safe-area-inset-bottom), 24px);
|
||||
}
|
||||
|
||||
[data-domain="camera"] .content {
|
||||
padding: 0;
|
||||
/* max height of the video is full screen, minus the height of the header of the dialog and the padding of the dialog (mdc-dialog-max-height: calc(100% - 72px)) */
|
||||
--video-max-height: calc(100vh - 65px - 72px);
|
||||
}
|
||||
|
||||
more-info-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
more-info-content[full-height] {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
state-card-content,
|
||||
ha-more-info-history,
|
||||
ha-more-info-logbook:not(:last-child) {
|
||||
@ -100,9 +142,6 @@ export class MoreInfoInfo extends LitElement {
|
||||
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: calc(-1 * var(--content-padding, 24px))
|
||||
calc(-1 * var(--content-padding, 24px)) 16px
|
||||
calc(-1 * var(--content-padding, 24px));
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -6,12 +6,11 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const";
|
||||
import "../../panels/config/entities/entity-registry-settings";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../../panels/config/entities/entity-registry-settings";
|
||||
|
||||
@customElement("ha-more-info-settings")
|
||||
export class HaMoreInfoSettings extends LitElement {
|
||||
@ -19,18 +18,18 @@ export class HaMoreInfoSettings extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
|
||||
@state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
|
||||
@state() private entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
|
||||
|
||||
@state() private _settingsElementTag?: string;
|
||||
|
||||
protected render() {
|
||||
// loading.
|
||||
if (this._entry === undefined) {
|
||||
if (this.entry === undefined) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
// No unique ID
|
||||
if (this._entry === null) {
|
||||
if (this.entry === null) {
|
||||
return html`
|
||||
<div class="content">
|
||||
${this.hass.localize(
|
||||
@ -54,53 +53,31 @@ export class HaMoreInfoSettings extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div @entity-entry-updated=${this._entryUpdated}>
|
||||
${dynamicElement(this._settingsElementTag, {
|
||||
hass: this.hass,
|
||||
entry: this._entry,
|
||||
entityId: this.entityId,
|
||||
})}
|
||||
</div>
|
||||
${dynamicElement(this._settingsElementTag, {
|
||||
hass: this.hass,
|
||||
entry: this.entry,
|
||||
entityId: this.entityId,
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("entityId")) {
|
||||
this._entry = undefined;
|
||||
if (this.entityId) {
|
||||
this._getEntityReg();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _getEntityReg() {
|
||||
try {
|
||||
this._entry = await getExtendedEntityRegistryEntry(
|
||||
this.hass,
|
||||
this.entityId
|
||||
);
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (changedProps.has("entry")) {
|
||||
this._loadPlatformSettingTabs();
|
||||
} catch {
|
||||
this._entry = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _entryUpdated(ev: CustomEvent<ExtEntityRegistryEntry>) {
|
||||
this._entry = ev.detail;
|
||||
}
|
||||
|
||||
private async _loadPlatformSettingTabs(): Promise<void> {
|
||||
if (!this._entry) {
|
||||
if (!this.entry) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
|
||||
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this.entry.platform)
|
||||
) {
|
||||
this._settingsElementTag = "entity-registry-settings";
|
||||
return;
|
||||
}
|
||||
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
|
||||
const tag = PLATFORMS_WITH_SETTINGS_TAB[this.entry.platform];
|
||||
await import(`../../panels/config/entities/editor-tabs/settings/${tag}`);
|
||||
this._settingsElementTag = tag;
|
||||
}
|
||||
|
@ -13,10 +13,8 @@ import {
|
||||
StaleWhileRevalidate,
|
||||
} from "workbox-strategies";
|
||||
|
||||
const noFallBackRegEx = new RegExp(
|
||||
"/(api|static|auth|frontend_latest|frontend_es5|local)/.*"
|
||||
);
|
||||
|
||||
const noFallBackRegEx =
|
||||
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;
|
||||
// Clean up caches from older workboxes and old service workers.
|
||||
// Will help with cleaning up Workbox v4 stuff
|
||||
cleanupOutdatedCaches();
|
||||
@ -33,22 +31,22 @@ function initRouting() {
|
||||
|
||||
// Cache static content (including translations) on first access.
|
||||
registerRoute(
|
||||
new RegExp("/(static|frontend_latest|frontend_es5)/.+"),
|
||||
/\/(static|frontend_latest|frontend_es5)\/.+/,
|
||||
new CacheFirst({ matchOptions: { ignoreSearch: true } })
|
||||
);
|
||||
|
||||
// Get api from network.
|
||||
registerRoute(new RegExp("/(api|auth)/.*"), new NetworkOnly());
|
||||
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());
|
||||
|
||||
// Get manifest, service worker, onboarding from network.
|
||||
registerRoute(
|
||||
new RegExp("/(service_worker.js|manifest.json|onboarding.html)"),
|
||||
/\/(service_worker.js|manifest.json|onboarding.html)/,
|
||||
new NetworkOnly()
|
||||
);
|
||||
|
||||
// For the root "/" we ignore search
|
||||
registerRoute(
|
||||
new RegExp(/\/(\?.*)?$/),
|
||||
/\/(\?.*)?$/,
|
||||
new StaleWhileRevalidate({ matchOptions: { ignoreSearch: true } })
|
||||
);
|
||||
|
||||
@ -57,7 +55,7 @@ function initRouting() {
|
||||
// First access might bring stale data from cache, but a single refresh will bring updated
|
||||
// file.
|
||||
registerRoute(
|
||||
new RegExp(/\/.*/),
|
||||
/\/.*/,
|
||||
new StaleWhileRevalidate({
|
||||
cacheName: "file-cache",
|
||||
plugins: [
|
||||
|
@ -116,7 +116,7 @@ export const provideHass = (
|
||||
}
|
||||
|
||||
mockAPI(
|
||||
new RegExp("states/.+"),
|
||||
/states\/.+/,
|
||||
(
|
||||
// @ts-ignore
|
||||
method,
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
mdiStopCircleOutline,
|
||||
mdiSort,
|
||||
} from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@ -26,6 +27,10 @@ import "../../../../components/ha-icon-button";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import { ACTION_TYPES } from "../../../../data/action";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../../data/entity_registry";
|
||||
import { Action, getActionType } from "../../../../data/script";
|
||||
import { describeAction } from "../../../../data/script_i18n";
|
||||
import { callExecuteScript } from "../../../../data/service";
|
||||
@ -107,6 +112,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@state() private _uiModeAvailable = true;
|
||||
@ -115,6 +122,14 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entityReg = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
@ -156,7 +171,9 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
class="action-icon"
|
||||
.path=${ACTION_TYPES[type!]}
|
||||
></ha-svg-icon>
|
||||
${capitalizeFirstLetter(describeAction(this.hass, this.action))}
|
||||
${capitalizeFirstLetter(
|
||||
describeAction(this.hass, this._entityReg, this.action)
|
||||
)}
|
||||
</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
@ -465,7 +482,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
),
|
||||
inputType: "string",
|
||||
placeholder: capitalizeFirstLetter(
|
||||
describeAction(this.hass, this.action, undefined, true)
|
||||
describeAction(this.hass, this._entityReg, this.action, undefined, true)
|
||||
),
|
||||
defaultValue: this.action.alias,
|
||||
confirmText: this.hass.localize("ui.common.submit"),
|
||||
|
@ -49,6 +49,7 @@ import {
|
||||
showAutomationEditor,
|
||||
triggerAutomationActions,
|
||||
} from "../../../data/automation";
|
||||
import { fetchEntityRegistry } from "../../../data/entity_registry";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@ -479,7 +480,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
this._readOnly = false;
|
||||
this._config = this._normalizeConfig(config);
|
||||
} catch (err: any) {
|
||||
const entity = Object.values(this.hass.entities).find(
|
||||
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
|
||||
const entity = entityRegistry.find(
|
||||
(ent) =>
|
||||
ent.platform === "automation" && ent.unique_id === this.automationId
|
||||
);
|
||||
|
@ -36,6 +36,15 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
|
||||
const firmwareTargetSchema: HaFormSchema[] = [
|
||||
{
|
||||
name: "firmware_target",
|
||||
type: "integer",
|
||||
valueMin: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("dialog-zwave_js-update-firmware-node")
|
||||
class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
@ -59,6 +68,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
|
||||
@state() private _nodeStatus?: ZWaveJSNodeStatus;
|
||||
|
||||
@state() private _firmwareTarget?: number;
|
||||
|
||||
private _subscribedNodeStatus?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _subscribedNodeFirmwareUpdate?: Promise<UnsubscribeFunc>;
|
||||
@ -80,6 +91,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
this._updateFinishedMessage = undefined;
|
||||
this._firmwareFile = undefined;
|
||||
this._nodeStatus = undefined;
|
||||
this._firmwareTarget = undefined;
|
||||
this._uploading = this._updateInProgress = false;
|
||||
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
@ -104,6 +116,19 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
)}
|
||||
@file-picked=${this._uploadFile}
|
||||
></ha-file-upload>
|
||||
${this._nodeStatus.is_controller_node
|
||||
? html``
|
||||
: html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.firmware_target_intro"
|
||||
)}
|
||||
</p>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${{ firmware_target: this._firmwareTarget }}
|
||||
.schema=${firmwareTargetSchema}
|
||||
@value-changed=${this._firmwareTargetChanged}
|
||||
></ha-form>`}
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._beginFirmwareUpdate}
|
||||
@ -283,7 +308,8 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
await uploadFirmwareAndBeginUpdate(
|
||||
this.hass,
|
||||
this.device!.id,
|
||||
this._firmwareFile!
|
||||
this._firmwareFile!,
|
||||
this._firmwareTarget
|
||||
);
|
||||
this._updateInProgress = true;
|
||||
this._uploading = false;
|
||||
@ -388,6 +414,10 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
this._subscribedNodeFirmwareUpdate = undefined;
|
||||
}
|
||||
|
||||
private async _firmwareTargetChanged(ev) {
|
||||
this._firmwareTarget = ev.detail.value.firmware_target;
|
||||
}
|
||||
|
||||
private async _uploadFile(ev) {
|
||||
this._firmwareFile = ev.detail.files[0];
|
||||
}
|
||||
|
@ -1,14 +1,19 @@
|
||||
import { HassEntities } from "home-assistant-js-websocket";
|
||||
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../layouts/hass-router-page";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./ha-script-editor";
|
||||
import "./ha-script-picker";
|
||||
@ -21,7 +26,7 @@ const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => {
|
||||
};
|
||||
|
||||
@customElement("ha-config-script")
|
||||
class HaConfigScript extends HassRouterPage {
|
||||
class HaConfigScript extends SubscribeMixin(HassRouterPage) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
@ -32,6 +37,16 @@ class HaConfigScript extends HassRouterPage {
|
||||
|
||||
@property() public scripts: ScriptEntity[] = [];
|
||||
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entityReg = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "dashboard",
|
||||
routes: {
|
||||
@ -78,6 +93,7 @@ class HaConfigScript extends HassRouterPage {
|
||||
pageEl.isWide = this.isWide;
|
||||
pageEl.route = this.routeTail;
|
||||
pageEl.showAdvanced = this.showAdvanced;
|
||||
pageEl.entityRegistry = this._entityReg;
|
||||
|
||||
if (this.hass) {
|
||||
if (!pageEl.scripts || !changedProps) {
|
||||
|
@ -39,6 +39,7 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import {
|
||||
deleteScript,
|
||||
getScriptStateConfig,
|
||||
@ -75,6 +76,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _config?: ScriptConfig;
|
||||
|
||||
@state() private _entityId?: string;
|
||||
@ -431,7 +434,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
this._config = this._normalizeConfig(config);
|
||||
},
|
||||
(resp) => {
|
||||
const entity = Object.values(this.hass.entities).find(
|
||||
const entity = this.entityRegistry.find(
|
||||
(ent) =>
|
||||
ent.platform === "script" && ent.unique_id === this.scriptId
|
||||
);
|
||||
@ -477,7 +480,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
getScriptStateConfig(this.hass, this.entityId).then((c) => {
|
||||
this._config = this._normalizeConfig(c.config);
|
||||
});
|
||||
const regEntry = this.hass.entities[this.entityId];
|
||||
const regEntry = this.entityRegistry.find(
|
||||
(ent) => ent.entity_id === this.entityId
|
||||
);
|
||||
if (regEntry?.unique_id) {
|
||||
this.scriptId = regEntry.unique_id;
|
||||
}
|
||||
@ -544,7 +549,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
if (!this.scriptId) {
|
||||
return;
|
||||
}
|
||||
const entity = Object.values(this.hass.entities).find(
|
||||
const entity = this.entityRegistry.find(
|
||||
(entry) => entry.unique_id === this.scriptId
|
||||
);
|
||||
if (!entity) {
|
||||
|
@ -44,6 +44,7 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
|
||||
@customElement("ha-script-picker")
|
||||
class HaScriptPicker extends LitElement {
|
||||
@ -57,7 +58,9 @@ class HaScriptPicker extends LitElement {
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() private _activeFilters?: string[];
|
||||
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _activeFilters?: string[];
|
||||
|
||||
@state() private _filteredScripts?: string[] | null;
|
||||
|
||||
@ -266,7 +269,7 @@ class HaScriptPicker extends LitElement {
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const entry = this.hass.entities[ev.detail.id];
|
||||
const entry = this.entityRegistry.find((e) => e.entity_id === ev.detail.id);
|
||||
if (entry) {
|
||||
navigate(`/config/script/edit/${entry.unique_id}`);
|
||||
} else {
|
||||
@ -275,7 +278,12 @@ class HaScriptPicker extends LitElement {
|
||||
}
|
||||
|
||||
private _runScript = async (script: any) => {
|
||||
const entry = this.hass.entities[script.entity_id];
|
||||
const entry = this.entityRegistry.find(
|
||||
(e) => e.entity_id === script.entity_id
|
||||
);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
await triggerScript(this.hass, entry.unique_id);
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
@ -291,7 +299,9 @@ class HaScriptPicker extends LitElement {
|
||||
}
|
||||
|
||||
private _showTrace(script: any) {
|
||||
const entry = this.hass.entities[script.entity_id];
|
||||
const entry = this.entityRegistry.find(
|
||||
(e) => e.entity_id === script.entity_id
|
||||
);
|
||||
if (entry) {
|
||||
navigate(`/config/script/trace/${entry.unique_id}`);
|
||||
}
|
||||
@ -317,7 +327,12 @@ class HaScriptPicker extends LitElement {
|
||||
|
||||
private async _duplicate(script: any) {
|
||||
try {
|
||||
const entry = this.hass.entities[script.entity_id];
|
||||
const entry = this.entityRegistry.find(
|
||||
(e) => e.entity_id === script.entity_id
|
||||
);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
const config = await fetchScriptFileConfig(this.hass, entry.unique_id);
|
||||
showScriptEditor({
|
||||
...config,
|
||||
@ -362,8 +377,12 @@ class HaScriptPicker extends LitElement {
|
||||
|
||||
private async _delete(script: any) {
|
||||
try {
|
||||
const entry = this.hass.entities[script.entity_id];
|
||||
await deleteScript(this.hass, entry.unique_id);
|
||||
const entry = this.entityRegistry.find(
|
||||
(e) => e.entity_id === script.entity_id
|
||||
);
|
||||
if (entry) {
|
||||
await deleteScript(this.hass, entry.unique_id);
|
||||
}
|
||||
} catch (err: any) {
|
||||
await showAlertDialog(this, {
|
||||
text:
|
||||
|
@ -39,6 +39,7 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../components/ha-button-menu";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
|
||||
@customElement("ha-script-trace")
|
||||
export class HaScriptTrace extends LitElement {
|
||||
@ -54,6 +55,8 @@ export class HaScriptTrace extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _entityId?: string;
|
||||
|
||||
@state() private _traces?: ScriptTrace[];
|
||||
@ -318,7 +321,7 @@ export class HaScriptTrace extends LitElement {
|
||||
const params = new URLSearchParams(location.search);
|
||||
this._loadTraces(params.get("run_id") || undefined);
|
||||
|
||||
this._entityId = Object.values(this.hass.entities).find(
|
||||
this._entityId = this.entityRegistry.find(
|
||||
(entry) => entry.unique_id === this.scriptId
|
||||
)?.entity_id;
|
||||
}
|
||||
@ -335,7 +338,7 @@ export class HaScriptTrace extends LitElement {
|
||||
if (this.scriptId) {
|
||||
this._loadTraces();
|
||||
|
||||
this._entityId = Object.values(this.hass.entities).find(
|
||||
this._entityId = this.entityRegistry.find(
|
||||
(entry) => entry.unique_id === this.scriptId
|
||||
)?.entity_id;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import "../lovelace/components/hui-energy-period-selector";
|
||||
import { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
|
||||
const LOVELACE_CONFIG: LovelaceConfig = {
|
||||
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
||||
views: [
|
||||
{
|
||||
strategy: {
|
||||
@ -93,8 +93,8 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
private _setLovelace() {
|
||||
this._lovelace = {
|
||||
config: LOVELACE_CONFIG,
|
||||
rawConfig: LOVELACE_CONFIG,
|
||||
config: ENERGY_LOVELACE_CONFIG,
|
||||
rawConfig: ENERGY_LOVELACE_CONFIG,
|
||||
editMode: false,
|
||||
urlPath: "energy",
|
||||
mode: "generated",
|
||||
|
@ -56,7 +56,7 @@ export class EnergyStrategy {
|
||||
(source) => source.type === "water"
|
||||
);
|
||||
|
||||
if (info.narrow) {
|
||||
if (info.narrow || info.view.strategy?.options?.show_date_selection) {
|
||||
view.cards!.push({
|
||||
type: "energy-date-selection",
|
||||
collection_key: "energy_dashboard",
|
||||
|
@ -884,6 +884,11 @@ class HuiEnergyDistrubutionCard
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
opacity: 1;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 80px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
line,
|
||||
path {
|
||||
|
@ -13,6 +13,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { getNumberFormatOptions } from "../../../common/number/format_number";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-gauge";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
@ -129,6 +130,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
.min=${this._config.min!}
|
||||
.max=${this._config.max!}
|
||||
.value=${stateObj.state}
|
||||
.formatOptions=${getNumberFormatOptions(
|
||||
stateObj,
|
||||
this.hass.entities[stateObj.entity_id]
|
||||
)}
|
||||
.locale=${this.hass!.locale}
|
||||
.label=${this._config!.unit ||
|
||||
this.hass?.states[this._config!.entity].attributes
|
||||
|
@ -278,8 +278,8 @@ const computeDefaultViewStates = (
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.entity_category ||
|
||||
HIDE_PLATFORM.has(entry.platform) ||
|
||||
entry.hidden_by
|
||||
(entry.platform && HIDE_PLATFORM.has(entry.platform)) ||
|
||||
entry.hidden
|
||||
)
|
||||
.map((entry) => entry.entity_id)
|
||||
);
|
||||
|
@ -62,7 +62,7 @@ const buttonEntitiesRowConfigStruct = object({
|
||||
|
||||
const castEntitiesRowConfigStruct = object({
|
||||
type: literal("cast"),
|
||||
view: union([string(), number()]),
|
||||
view: optional(union([string(), number()])),
|
||||
dashboard: optional(string()),
|
||||
name: optional(string()),
|
||||
icon: optional(string()),
|
||||
|
@ -55,7 +55,7 @@ export interface CastConfig {
|
||||
type: "cast";
|
||||
icon?: string;
|
||||
name?: string;
|
||||
view: string | number;
|
||||
view?: string | number;
|
||||
dashboard?: string;
|
||||
// Hide the row if either unsupported browser or no API available.
|
||||
hide_if_unavailable?: boolean;
|
||||
|
@ -29,13 +29,10 @@ class HuiCastRow extends LitElement implements LovelaceRow {
|
||||
@state() private _noHTTPS = false;
|
||||
|
||||
public setConfig(config: CastConfig): void {
|
||||
if (!config || config.view === undefined || config.view === null) {
|
||||
throw new Error("View required");
|
||||
}
|
||||
|
||||
this._config = {
|
||||
icon: "hass:television",
|
||||
icon: "mdi:television",
|
||||
name: "Home Assistant Cast",
|
||||
view: 0,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
@ -123,7 +120,7 @@ class HuiCastRow extends LitElement implements LovelaceRow {
|
||||
castSendShowLovelaceView(
|
||||
this._castManager!,
|
||||
this.hass.auth.data.hassUrl,
|
||||
this._config!.view,
|
||||
this._config!.view!,
|
||||
this._config!.dashboard
|
||||
);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import { polyfillsLoaded } from "../common/translations/localize";
|
||||
import { subscribeAreaRegistry } from "../data/area_registry";
|
||||
import { broadcastConnectionStatus } from "../data/connection-status";
|
||||
import { subscribeDeviceRegistry } from "../data/device_registry";
|
||||
import { subscribeEntityRegistry } from "../data/entity_registry";
|
||||
import { subscribeEntityRegistryDisplay } from "../data/entity_registry";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
import { forwardHaptic } from "../data/haptics";
|
||||
import { DEFAULT_PANEL } from "../data/panel";
|
||||
@ -188,10 +188,22 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
});
|
||||
|
||||
subscribeEntities(conn, (states) => this._updateHass({ states }));
|
||||
subscribeEntityRegistry(conn, (entityReg) => {
|
||||
subscribeEntityRegistryDisplay(conn, (entityReg) => {
|
||||
const entities: HomeAssistant["entities"] = {};
|
||||
for (const entity of entityReg) {
|
||||
entities[entity.entity_id] = entity;
|
||||
for (const entity of entityReg.entities) {
|
||||
entities[entity.ei] = {
|
||||
entity_id: entity.ei,
|
||||
device_id: entity.di,
|
||||
area_id: entity.ai,
|
||||
translation_key: entity.tk,
|
||||
platform: entity.pl,
|
||||
entity_category: entity.ec
|
||||
? entityReg.entity_categories[entity.ec]
|
||||
: undefined,
|
||||
name: entity.en,
|
||||
hidden: entity.hb,
|
||||
display_precision: entity.dp,
|
||||
};
|
||||
}
|
||||
this._updateHass({ entities });
|
||||
});
|
||||
|
@ -3691,6 +3691,8 @@
|
||||
"warning_controller": "WARNING: Firmware updates can brick your controller if you do not use the right firmware files, or if you attempt to stop the firmware update before it completes. The Home Assistant and Z-Wave JS teams do not take any responsibility for any damages to your controller as a result of the firmware update and will not be able to help you if you brick your controller. Would you still like to continue?",
|
||||
"introduction": "Select the firmware file you would like to use to update {device}.",
|
||||
"introduction_controller": "Select the firmware file you would like to use to update {device}. Note that once you start a firmware update, you MUST wait for the update to complete.",
|
||||
"firmware_target_intro": "Select the firmware target (0 for the Z-Wave chip, ≥1 for other chips if they exist) for this update.",
|
||||
"firmware_target": "Firmware Target (chip)",
|
||||
"upload_firmware": "Upload Firmware",
|
||||
"upload_failed": "Upload Failed",
|
||||
"begin_update": "Begin Firmware Update",
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { LocalizeFunc } from "./common/translations/localize";
|
||||
import { AreaRegistryEntry } from "./data/area_registry";
|
||||
import { DeviceRegistryEntry } from "./data/device_registry";
|
||||
import { EntityRegistryEntry } from "./data/entity_registry";
|
||||
import { EntityRegistryDisplayEntry } from "./data/entity_registry";
|
||||
import { CoreFrontendUserData } from "./data/frontend";
|
||||
import { FrontendLocaleData, getHassTranslations } from "./data/translation";
|
||||
import { Themes } from "./data/ws-themes";
|
||||
@ -189,7 +189,7 @@ export interface HomeAssistant {
|
||||
connection: Connection;
|
||||
connected: boolean;
|
||||
states: HassEntities;
|
||||
entities: { [id: string]: EntityRegistryEntry };
|
||||
entities: { [id: string]: EntityRegistryDisplayEntry };
|
||||
devices: { [id: string]: DeviceRegistryEntry };
|
||||
areas: { [id: string]: AreaRegistryEntry };
|
||||
services: HassServices;
|
||||
|
20
yarn.lock
20
yarn.lock
@ -3095,13 +3095,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@material/web@npm:=1.0.0-pre.2":
|
||||
version: 1.0.0-pre.2
|
||||
resolution: "@material/web@npm:1.0.0-pre.2"
|
||||
"@material/web@npm:=1.0.0-pre.3":
|
||||
version: 1.0.0-pre.3
|
||||
resolution: "@material/web@npm:1.0.0-pre.3"
|
||||
dependencies:
|
||||
lit: ^2.3.0
|
||||
tslib: ^2.4.0
|
||||
checksum: 7c6733fae5fb67c43d7c49fab70f7893defd95e4fcbe996d06057882e47c0121760546cc5d1c407a9dbd11c5f02f3f278016c52922e6a9e97db0c0b52d7133f2
|
||||
checksum: d6286992cb0d63b094e638a3db484398195608b422bb2cb209102eaf87d220ed24bbe85d29933730054fd3cf99d318dbb3645ae9a2e271fe1a3c2833d829bf4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -9562,7 +9562,7 @@ fsevents@~2.3.2:
|
||||
"@material/mwc-textfield": ^0.27.0
|
||||
"@material/mwc-top-app-bar-fixed": ^0.27.0
|
||||
"@material/top-app-bar": =14.0.0-canary.53b3cad2f.0
|
||||
"@material/web": =1.0.0-pre.2
|
||||
"@material/web": =1.0.0-pre.3
|
||||
"@mdi/js": 7.1.96
|
||||
"@mdi/svg": 7.1.96
|
||||
"@octokit/auth-oauth-device": ^4.0.4
|
||||
@ -9663,7 +9663,7 @@ fsevents@~2.3.2:
|
||||
lit: ^2.6.1
|
||||
lit-analyzer: ^1.2.1
|
||||
lodash.template: ^4.5.0
|
||||
magic-string: ^0.29.0
|
||||
magic-string: ^0.30.0
|
||||
map-stream: ^0.0.7
|
||||
marked: ^4.2.12
|
||||
memoize-one: ^6.0.0
|
||||
@ -11590,12 +11590,12 @@ fsevents@~2.3.2:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"magic-string@npm:^0.29.0":
|
||||
version: 0.29.0
|
||||
resolution: "magic-string@npm:0.29.0"
|
||||
"magic-string@npm:^0.30.0":
|
||||
version: 0.30.0
|
||||
resolution: "magic-string@npm:0.30.0"
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec": ^1.4.13
|
||||
checksum: 19e5398fcfc44804917127c72ad622c68a19a0a10cbdb8d4f9f9417584a087fe9e117140bfb2463d86743cf1ed9cf4182ae0b0ad1a7536f7fdda257ee4449ffb
|
||||
checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user