mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-27 03:27:21 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dc4004323 | |||
| fb0a54231a |
+16
-5
@@ -2,7 +2,10 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { subscribeDeviceRegistry } from "./device/device_registry";
|
||||
import {
|
||||
subscribeDeviceRegistry,
|
||||
type DeviceRegistryEntry,
|
||||
} from "./device/device_registry";
|
||||
import { getThreadDataSetTLV, listThreadDataSets } from "./thread";
|
||||
|
||||
export enum NetworkType {
|
||||
@@ -77,9 +80,9 @@ export const startExternalCommissioning = async (hass: HomeAssistant) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const redirectOnNewMatterDevice = (
|
||||
export const watchForNewMatterDevice = (
|
||||
hass: HomeAssistant,
|
||||
callback?: () => void
|
||||
callback: (device: DeviceRegistryEntry) => void
|
||||
): UnsubscribeFunc => {
|
||||
let curMatterDevices: Set<string> | undefined;
|
||||
const unsubDeviceReg = subscribeDeviceRegistry(hass.connection, (entries) => {
|
||||
@@ -101,8 +104,7 @@ export const redirectOnNewMatterDevice = (
|
||||
if (newMatterDevices.length) {
|
||||
unsubDeviceReg();
|
||||
curMatterDevices = undefined;
|
||||
callback?.();
|
||||
navigate(`/config/devices/device/${newMatterDevices[0].id}`);
|
||||
callback(newMatterDevices[0]);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
@@ -111,6 +113,15 @@ export const redirectOnNewMatterDevice = (
|
||||
};
|
||||
};
|
||||
|
||||
export const redirectOnNewMatterDevice = (
|
||||
hass: HomeAssistant,
|
||||
callback?: () => void
|
||||
): UnsubscribeFunc =>
|
||||
watchForNewMatterDevice(hass, (device) => {
|
||||
callback?.();
|
||||
navigate(`/config/devices/device/${device.id}`);
|
||||
});
|
||||
|
||||
export const addMatterDevice = (hass: HomeAssistant) => {
|
||||
startExternalCommissioning(hass);
|
||||
};
|
||||
|
||||
@@ -39,11 +39,15 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _dirty = false;
|
||||
|
||||
@state() private _componentLoaded?: boolean;
|
||||
|
||||
@query("entity-registry-settings-editor")
|
||||
private _registryEditor?: EntityRegistrySettingsEditor;
|
||||
|
||||
private _originalItemJson?: string;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._componentLoaded = isComponentLoaded(
|
||||
@@ -120,7 +124,9 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
</ha-button>
|
||||
<ha-button
|
||||
@click=${this._updateItem}
|
||||
.disabled=${!!this._submitting || !!(this._item && !this._item.name)}
|
||||
.disabled=${!this._dirty ||
|
||||
!!this._submitting ||
|
||||
!!(this._item && !this._item.name)}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.update")}
|
||||
</ha-button>
|
||||
@@ -128,8 +134,18 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private get _isHelperDirty(): boolean {
|
||||
if (!this._item || !this._originalItemJson) return false;
|
||||
return JSON.stringify(this._item) !== this._originalItemJson;
|
||||
}
|
||||
|
||||
private _updateDirty() {
|
||||
this._dirty = (this._registryEditor?.dirty ?? false) || this._isHelperDirty;
|
||||
}
|
||||
|
||||
private _entityRegistryChanged() {
|
||||
this._error = undefined;
|
||||
this._updateDirty();
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
@@ -138,11 +154,15 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
}
|
||||
this._error = undefined;
|
||||
this._item = ev.detail.value;
|
||||
this._updateDirty();
|
||||
}
|
||||
|
||||
private async _getItem() {
|
||||
const items = await HELPERS_CRUD[this.entry.platform].fetch(this.hass!);
|
||||
this._item = items.find((item) => item.id === this.entry.unique_id) || null;
|
||||
this._originalItemJson = this._item
|
||||
? JSON.stringify(this._item)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private async _updateItem(): Promise<void> {
|
||||
|
||||
@@ -97,7 +97,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
||||
|
||||
const OVERRIDE_DEVICE_CLASSES = {
|
||||
export const OVERRIDE_DEVICE_CLASSES = {
|
||||
cover: [
|
||||
[
|
||||
"awning",
|
||||
@@ -208,6 +208,34 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
private _deviceClassOptions?: string[][];
|
||||
|
||||
private _initialStateJson!: string;
|
||||
|
||||
private _lastDirty = false;
|
||||
|
||||
private _currentState() {
|
||||
return {
|
||||
name: this._name.trim() || null,
|
||||
icon: this._icon.trim() || null,
|
||||
entityId: this._entityId.trim(),
|
||||
areaId: this._areaId ?? null,
|
||||
labels: this._labels ?? [],
|
||||
deviceClass: this._deviceClass,
|
||||
disabledBy: this._disabledBy,
|
||||
hiddenBy: this._hiddenBy,
|
||||
unitOfMeasurement: this._unit_of_measurement,
|
||||
precision: this._precision,
|
||||
defaultCode: this._defaultCode,
|
||||
calendarColor: this._calendarColor ?? null,
|
||||
precipitationUnit: this._precipitation_unit,
|
||||
pressureUnit: this._pressure_unit,
|
||||
temperatureUnit: this._temperature_unit,
|
||||
visibilityUnit: this._visibility_unit,
|
||||
windSpeedUnit: this._wind_speed_unit,
|
||||
switchAsDomain: this._switchAsDomain,
|
||||
switchAsInvert: this._switchAsInvert,
|
||||
};
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (
|
||||
@@ -274,6 +302,9 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
this._wind_speed_unit = stateObj?.attributes?.wind_speed_unit;
|
||||
}
|
||||
|
||||
this._initialStateJson = JSON.stringify(this._currentState());
|
||||
this._lastDirty = false;
|
||||
|
||||
const deviceClasses: string[][] = OVERRIDE_DEVICE_CLASSES[domain];
|
||||
|
||||
if (!deviceClasses || this._hideDeviceClassOverride(domain)) {
|
||||
@@ -372,6 +403,16 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
this._switchAsDomain = "switch";
|
||||
this._switchAsInvert = false;
|
||||
}
|
||||
this._initialStateJson = JSON.stringify(this._currentState());
|
||||
this._lastDirty = false;
|
||||
}
|
||||
|
||||
if (this._initialStateJson) {
|
||||
const dirty = this.dirty;
|
||||
if (dirty !== this._lastDirty) {
|
||||
this._lastDirty = dirty;
|
||||
fireEvent(this, "change");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +448,23 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@input=${this._nameChanged}
|
||||
>
|
||||
${this._device
|
||||
? html`<span slot="hint"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.device_name_tip",
|
||||
{
|
||||
link: html`<button
|
||||
class="link"
|
||||
@click=${this._resetNameAndOpenDeviceSettings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.open_device_settings"
|
||||
)}
|
||||
</button>`,
|
||||
}
|
||||
)}</span
|
||||
>`
|
||||
: nothing}
|
||||
</ha-input>`}
|
||||
${this.hideIcon
|
||||
? nothing
|
||||
@@ -1060,6 +1118,10 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public get dirty(): boolean {
|
||||
return JSON.stringify(this._currentState()) !== this._initialStateJson;
|
||||
}
|
||||
|
||||
public async updateEntry(): Promise<{
|
||||
close: boolean;
|
||||
entry: ExtEntityRegistryEntry;
|
||||
@@ -1518,6 +1580,13 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _resetNameAndOpenDeviceSettings() {
|
||||
this._name = this.entry.name || "";
|
||||
fireEvent(this, "change");
|
||||
|
||||
this._openDeviceSettings();
|
||||
}
|
||||
|
||||
private _openDeviceSettings() {
|
||||
showDeviceRegistryDetailDialog(this, {
|
||||
device: this._device!,
|
||||
|
||||
@@ -44,6 +44,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _submitting?: boolean;
|
||||
|
||||
@state() private _dirty = false;
|
||||
|
||||
@query("entity-registry-settings-editor")
|
||||
private _registryEditor?: EntityRegistrySettingsEditor;
|
||||
|
||||
@@ -144,7 +146,11 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.delete")}
|
||||
</ha-button>
|
||||
<ha-button @click=${this._updateEntry} .loading=${!!this._submitting}>
|
||||
<ha-button
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${!this._dirty || !!this._submitting}
|
||||
.loading=${!!this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.update")}
|
||||
</ha-button>
|
||||
</div>
|
||||
@@ -153,6 +159,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _entityRegistryChanged() {
|
||||
this._error = undefined;
|
||||
this._dirty = this._registryEditor?.dirty ?? false;
|
||||
}
|
||||
|
||||
private _openDeviceSettings() {
|
||||
|
||||
+193
-6
@@ -3,16 +3,28 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import { computeDeviceName } from "../../../../../common/entity/compute_device_name";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import {
|
||||
commissionMatterDevice,
|
||||
redirectOnNewMatterDevice,
|
||||
watchForNewMatterDevice,
|
||||
} from "../../../../../data/matter";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import { updateDeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import {
|
||||
getAutomaticEntityIds,
|
||||
getExtendedEntityRegistryEntries,
|
||||
updateEntityRegistryEntry,
|
||||
type ExtEntityRegistryEntry,
|
||||
} from "../../../../../data/entity/entity_registry";
|
||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import "./matter-add-device/matter-add-device-apple-home";
|
||||
import "./matter-add-device/matter-add-device-existing";
|
||||
import "./matter-add-device/matter-add-device-generic";
|
||||
@@ -21,6 +33,7 @@ import "./matter-add-device/matter-add-device-google-home-fallback";
|
||||
import "./matter-add-device/matter-add-device-main";
|
||||
import "./matter-add-device/matter-add-device-new";
|
||||
import "./matter-add-device/matter-add-device-commissioning";
|
||||
import "./matter-add-device/matter-add-device-device-added";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
|
||||
export type MatterAddDeviceStep =
|
||||
@@ -31,7 +44,8 @@ export type MatterAddDeviceStep =
|
||||
| "google_home_fallback"
|
||||
| "apple_home"
|
||||
| "generic"
|
||||
| "commissioning";
|
||||
| "commissioning"
|
||||
| "device_added";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -50,6 +64,7 @@ const BACK_STEP: Record<MatterAddDeviceStep, MatterAddDeviceStep | undefined> =
|
||||
apple_home: "existing",
|
||||
generic: "existing",
|
||||
commissioning: undefined,
|
||||
device_added: undefined,
|
||||
};
|
||||
|
||||
@customElement("dialog-matter-add-device")
|
||||
@@ -62,23 +77,90 @@ class DialogMatterAddDevice extends LitElement {
|
||||
|
||||
@state() _step: MatterAddDeviceStep = "main";
|
||||
|
||||
@state() private _newDevice?: DeviceRegistryEntry;
|
||||
|
||||
@state() private _mainEntity?: ExtEntityRegistryEntry;
|
||||
|
||||
@state() private _deviceAddedState: {
|
||||
name: string;
|
||||
area: string | undefined;
|
||||
deviceClass: string | undefined;
|
||||
hasPendingUpdates: boolean;
|
||||
} = {
|
||||
name: "",
|
||||
area: undefined,
|
||||
deviceClass: undefined,
|
||||
hasPendingUpdates: false,
|
||||
};
|
||||
|
||||
private _mainEntityFetched = false;
|
||||
|
||||
private _unsub?: UnsubscribeFunc;
|
||||
|
||||
public showDialog(): void {
|
||||
this._open = true;
|
||||
this._unsub = redirectOnNewMatterDevice(this.hass, () =>
|
||||
this.closeDialog()
|
||||
);
|
||||
this._unsub = watchForNewMatterDevice(this.hass, (device) => {
|
||||
this._newDevice = device;
|
||||
this._step = "device_added";
|
||||
this._fetchMainEntity();
|
||||
});
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
protected updated(changedProps: Map<string, unknown>): void {
|
||||
// Retry fetching main entity when hass updates (entities may not be available immediately)
|
||||
if (
|
||||
changedProps.has("hass") &&
|
||||
this._newDevice &&
|
||||
!this._mainEntityFetched
|
||||
) {
|
||||
this._fetchMainEntity();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchMainEntity(): Promise<void> {
|
||||
if (!this._newDevice || this._mainEntityFetched) return;
|
||||
|
||||
const entityIds = Object.values(this.hass.entities)
|
||||
.filter((e) => e.device_id === this._newDevice!.id)
|
||||
.map((e) => e.entity_id);
|
||||
|
||||
if (!entityIds.length) return;
|
||||
|
||||
this._mainEntityFetched = true;
|
||||
|
||||
const entries = await getExtendedEntityRegistryEntries(
|
||||
this.hass,
|
||||
entityIds
|
||||
);
|
||||
|
||||
const mainEntry = Object.values(entries).find(
|
||||
(e) => e.original_name === null
|
||||
);
|
||||
if (!mainEntry) return;
|
||||
|
||||
const domain = computeDomain(mainEntry.entity_id);
|
||||
if (domain === "cover" || domain === "binary_sensor") {
|
||||
this._mainEntity = mainEntry;
|
||||
}
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._open = false;
|
||||
this._step = "main";
|
||||
this._pairingCode = "";
|
||||
this._newDevice = undefined;
|
||||
this._mainEntity = undefined;
|
||||
this._mainEntityFetched = false;
|
||||
this._deviceAddedState = {
|
||||
name: "",
|
||||
area: undefined,
|
||||
deviceClass: undefined,
|
||||
hasPendingUpdates: false,
|
||||
};
|
||||
this._unsub?.();
|
||||
this._unsub = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
@@ -93,6 +175,17 @@ class DialogMatterAddDevice extends LitElement {
|
||||
this._pairingCode = ev.detail.code;
|
||||
}
|
||||
|
||||
private _handleDeviceAddedChanged(
|
||||
ev: CustomEvent<{
|
||||
name: string;
|
||||
area: string | undefined;
|
||||
deviceClass: string | undefined;
|
||||
hasPendingUpdates: boolean;
|
||||
}>
|
||||
) {
|
||||
this._deviceAddedState = ev.detail;
|
||||
}
|
||||
|
||||
private _back() {
|
||||
const backStep = BACK_STEP[this._step];
|
||||
if (!backStep) return;
|
||||
@@ -104,12 +197,15 @@ class DialogMatterAddDevice extends LitElement {
|
||||
<div
|
||||
@pairing-code-changed=${this._handlePairingCodeChanged}
|
||||
@step-selected=${this._handleStepSelected}
|
||||
@device-added-changed=${this._handleDeviceAddedChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${dynamicElement(
|
||||
`matter-add-device-${this._step.replaceAll("_", "-")}`,
|
||||
{
|
||||
hass: this.hass,
|
||||
device: this._newDevice,
|
||||
mainEntity: this._mainEntity,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
@@ -129,8 +225,86 @@ class DialogMatterAddDevice extends LitElement {
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
this._step = savedStep;
|
||||
}
|
||||
this._step = savedStep;
|
||||
// On success, keep showing commissioning spinner until watchForNewMatterDevice fires
|
||||
}
|
||||
|
||||
private async _finishDeviceAdded(): Promise<void> {
|
||||
const device = this._newDevice!;
|
||||
const { name, area, deviceClass, hasPendingUpdates } =
|
||||
this._deviceAddedState;
|
||||
|
||||
if (hasPendingUpdates) {
|
||||
const origName = computeDeviceName(device) ?? "";
|
||||
const nameChanged = name !== origName;
|
||||
const origArea = device.area_id ?? undefined;
|
||||
const areaChanged = area !== origArea;
|
||||
|
||||
if (nameChanged || areaChanged) {
|
||||
await updateDeviceRegistryEntry(this.hass, device.id, {
|
||||
...(nameChanged && { name_by_user: name || null }),
|
||||
...(areaChanged && { area_id: area || null }),
|
||||
}).catch((err: Error) =>
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error_saving_device",
|
||||
{ error: err.message }
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (nameChanged && name) {
|
||||
const entityIds = Object.values(this.hass.entities)
|
||||
.filter((e) => e.device_id === device.id)
|
||||
.map((e) => e.entity_id);
|
||||
|
||||
if (entityIds.length) {
|
||||
const mapping = await getAutomaticEntityIds(this.hass, entityIds);
|
||||
await Promise.allSettled(
|
||||
Object.entries(mapping)
|
||||
.filter((entry): entry is [string, string] => !!entry[1])
|
||||
.map(([oldId, newId]) =>
|
||||
updateEntityRegistryEntry(this.hass, oldId, {
|
||||
new_entity_id: newId,
|
||||
}).catch((err: Error) =>
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error_saving_entity",
|
||||
{ error: err.message }
|
||||
),
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._mainEntity) {
|
||||
const origClass =
|
||||
this._mainEntity.device_class ??
|
||||
this._mainEntity.original_device_class ??
|
||||
undefined;
|
||||
if (deviceClass !== origClass) {
|
||||
await updateEntityRegistryEntry(
|
||||
this.hass,
|
||||
this._mainEntity.entity_id,
|
||||
{ device_class: deviceClass || null }
|
||||
).catch((err: Error) =>
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error_saving_entity",
|
||||
{ error: err.message }
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.closeDialog();
|
||||
navigate(`/config/devices/device/${device.id}`);
|
||||
}
|
||||
|
||||
private _renderActions() {
|
||||
@@ -156,6 +330,19 @@ class DialogMatterAddDevice extends LitElement {
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
if (this._step === "device_added") {
|
||||
return html`
|
||||
<ha-button slot="primaryAction" @click=${this._finishDeviceAdded}>
|
||||
${this._deviceAddedState.hasPendingUpdates
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.matter-add-device.device_added.finish"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.dialogs.matter-add-device.device_added.skip"
|
||||
)}
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../../../../../common/entity/compute_domain";
|
||||
import { computeDeviceName } from "../../../../../../common/entity/compute_device_name";
|
||||
import { caseInsensitiveStringCompare } from "../../../../../../common/string/compare";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-area-picker";
|
||||
import "../../../../../../components/input/ha-input";
|
||||
import "../../../../../../components/ha-select";
|
||||
import "../../../../../../components/ha-dropdown-item";
|
||||
import type { HaSelectSelectEvent } from "../../../../../../components/ha-select";
|
||||
import type { ExtEntityRegistryEntry } from "../../../../../../data/entity/entity_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device/device_registry";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { brandsUrl } from "../../../../../../util/brands-url";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
import { OVERRIDE_DEVICE_CLASSES } from "../../../../entities/entity-registry-settings-editor";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"device-added-changed": {
|
||||
name: string;
|
||||
area: string | undefined;
|
||||
deviceClass: string | undefined;
|
||||
hasPendingUpdates: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("matter-add-device-device-added")
|
||||
class MatterAddDeviceDeviceAdded extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public device!: DeviceRegistryEntry;
|
||||
|
||||
@property({ attribute: false }) public mainEntity?: ExtEntityRegistryEntry;
|
||||
|
||||
@state() private _deviceName = "";
|
||||
|
||||
@state() private _area: string | undefined;
|
||||
|
||||
@state() private _deviceClass: string | undefined;
|
||||
|
||||
private _initialized = false;
|
||||
|
||||
protected willUpdate() {
|
||||
if (!this._initialized && this.device) {
|
||||
this._initialized = true;
|
||||
this._deviceName = computeDeviceName(this.device) ?? "";
|
||||
this._area = this.device.area_id ?? undefined;
|
||||
this._deviceClass =
|
||||
this.mainEntity?.device_class ??
|
||||
this.mainEntity?.original_device_class ??
|
||||
undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private get _deviceClassOptions(): string[][] | undefined {
|
||||
if (!this.mainEntity) return undefined;
|
||||
const domain = computeDomain(this.mainEntity.entity_id);
|
||||
const deviceClasses = OVERRIDE_DEVICE_CLASSES[domain];
|
||||
if (!deviceClasses) return undefined;
|
||||
|
||||
const options: string[][] = [[], []];
|
||||
for (const deviceClass of deviceClasses) {
|
||||
if (
|
||||
this.mainEntity.original_device_class &&
|
||||
deviceClass.includes(this.mainEntity.original_device_class)
|
||||
) {
|
||||
options[0] = deviceClass;
|
||||
} else {
|
||||
options[1].push(...deviceClass);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private get _hasPendingUpdates(): boolean {
|
||||
const origName = computeDeviceName(this.device) ?? "";
|
||||
const origArea = this.device.area_id ?? undefined;
|
||||
const origDeviceClass =
|
||||
this.mainEntity?.device_class ??
|
||||
this.mainEntity?.original_device_class ??
|
||||
undefined;
|
||||
return (
|
||||
this._deviceName !== origName ||
|
||||
this._area !== origArea ||
|
||||
(this.mainEntity !== undefined && this._deviceClass !== origDeviceClass)
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProps: Map<string, unknown>) {
|
||||
if (
|
||||
changedProps.has("_deviceName") ||
|
||||
changedProps.has("_area") ||
|
||||
changedProps.has("_deviceClass")
|
||||
) {
|
||||
fireEvent(this, "device-added-changed", {
|
||||
name: this._deviceName,
|
||||
area: this._area,
|
||||
deviceClass: this._deviceClass,
|
||||
hasPendingUpdates: this._hasPendingUpdates,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _deviceClassesSorted = memoizeOne(
|
||||
(domain: string, deviceClasses: string[]) =>
|
||||
deviceClasses
|
||||
.map((deviceClass) => ({
|
||||
deviceClass,
|
||||
label: this.hass.localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}`
|
||||
),
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.label,
|
||||
b.label,
|
||||
this.hass.locale.language
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.device) return nothing;
|
||||
|
||||
const domain = this.mainEntity
|
||||
? computeDomain(this.mainEntity.entity_id)
|
||||
: undefined;
|
||||
const deviceClassOptions = this._deviceClassOptions;
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<div class="device">
|
||||
<div class="device-info">
|
||||
<img
|
||||
alt="Matter"
|
||||
src=${brandsUrl(
|
||||
{
|
||||
domain: "matter",
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
},
|
||||
this.hass.auth.data.hassUrl
|
||||
)}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="device-name">
|
||||
<span>${computeDeviceName(this.device)}</span>
|
||||
<span class="secondary">Matter</span>
|
||||
</div>
|
||||
</div>
|
||||
<ha-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.device_name"
|
||||
)}
|
||||
.value=${this._deviceName}
|
||||
@change=${this._deviceNameChanged}
|
||||
></ha-input>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._area}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
${deviceClassOptions && domain
|
||||
? html`
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.device_class"
|
||||
)}
|
||||
.value=${this._deviceClass
|
||||
? this.hass.localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${this._deviceClass}`
|
||||
)
|
||||
: undefined}
|
||||
clearable
|
||||
@selected=${this._deviceClassChanged}
|
||||
>
|
||||
${this._deviceClassesSorted(
|
||||
domain,
|
||||
deviceClassOptions[0]
|
||||
).map(
|
||||
(entry) => html`
|
||||
<ha-dropdown-item
|
||||
.value=${entry.deviceClass}
|
||||
.selected=${entry.deviceClass === this._deviceClass}
|
||||
>
|
||||
${entry.label}
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
)}
|
||||
${deviceClassOptions[0].length && deviceClassOptions[1].length
|
||||
? html`<wa-divider></wa-divider>`
|
||||
: nothing}
|
||||
${this._deviceClassesSorted(
|
||||
domain,
|
||||
deviceClassOptions[1]
|
||||
).map(
|
||||
(entry) => html`
|
||||
<ha-dropdown-item
|
||||
.value=${entry.deviceClass}
|
||||
.selected=${entry.deviceClass === this._deviceClass}
|
||||
>
|
||||
${entry.label}
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _deviceNameChanged(ev: InputEvent) {
|
||||
this._deviceName = (ev.currentTarget as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
private _areaPicked(ev: CustomEvent<{ value: string }>) {
|
||||
this._area = ev.detail.value || undefined;
|
||||
}
|
||||
|
||||
private _deviceClassChanged(ev: HaSelectSelectEvent<string, true>) {
|
||||
this._deviceClass = ev.detail.value;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
sharedStyles,
|
||||
css`
|
||||
.device {
|
||||
border: 1px solid var(--divider-color);
|
||||
padding: var(--ha-space-2);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
.device-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
margin-bottom: var(--ha-space-1);
|
||||
}
|
||||
.device-info img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.device-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
ha-input {
|
||||
margin: var(--ha-space-2) 0;
|
||||
}
|
||||
ha-area-picker,
|
||||
ha-select {
|
||||
display: block;
|
||||
margin-top: var(--ha-space-2);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"matter-add-device-device-added": MatterAddDeviceDeviceAdded;
|
||||
}
|
||||
}
|
||||
+6
@@ -85,6 +85,12 @@ class MatterAddDeviceNew extends LitElement {
|
||||
static styles = [
|
||||
sharedStyles,
|
||||
css`
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
.app-qr {
|
||||
margin: 24px auto 0 auto;
|
||||
display: flex;
|
||||
|
||||
@@ -1969,6 +1969,7 @@
|
||||
"entity_disabled": "This entity is disabled.",
|
||||
"enable_entity": "Enable",
|
||||
"open_device_settings": "Open device settings",
|
||||
"device_name_tip": "Consider renaming the device instead to update all its entities at once. {link}",
|
||||
"switch_as_x_confirm": "This switch will be hidden and a new {domain} will be added. Your existing configurations using the switch will continue to work.",
|
||||
"switch_as_x_remove_confirm": "This {domain} will be removed and the original switch will be visible again. Your existing configurations using the {domain} will no longer work!",
|
||||
"switch_as_x_change_confirm": "This {domain_1} will be removed and will be replaced by a new {domain_2}. Your existing configurations using the {domain_1} will no longer work!",
|
||||
@@ -2421,6 +2422,11 @@
|
||||
"header": "Enter setup code",
|
||||
"code_instructions": "Search for the sharing mode in the app of your controller, and activate it. You will get a setup code, enter that below.",
|
||||
"setup_code": "Setup code"
|
||||
},
|
||||
"device_added": {
|
||||
"header": "Device added",
|
||||
"finish": "Finish",
|
||||
"skip": "Skip and finish"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
|
||||
Reference in New Issue
Block a user