mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-17 14:26:35 +00:00
Allow editing of device name (#3209)
* Allow editing of device name * Patches * Update dialog-device-registry-detail.ts
This commit is contained in:
parent
7691e3f2c2
commit
c24f8a2115
@ -16,8 +16,8 @@ export interface DeviceRegistryEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceRegistryEntryMutableParams {
|
export interface DeviceRegistryEntryMutableParams {
|
||||||
area_id?: string;
|
area_id?: string | null;
|
||||||
name_by_user?: string;
|
name_by_user?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateDeviceRegistryEntry = (
|
export const updateDeviceRegistryEntry = (
|
||||||
|
203
src/panels/config/integrations/dialog-device-registry-detail.ts
Normal file
203
src/panels/config/integrations/dialog-device-registry-detail.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
|
||||||
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
|
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
|
||||||
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import {
|
||||||
|
subscribeAreaRegistry,
|
||||||
|
AreaRegistryEntry,
|
||||||
|
} from "../../../data/area_registry";
|
||||||
|
|
||||||
|
@customElement("dialog-device-registry-detail")
|
||||||
|
class DialogDeviceRegistryDetail extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private _nameByUser!: string;
|
||||||
|
@property() private _error?: string;
|
||||||
|
@property() private _params?: DeviceRegistryDetailDialogParams;
|
||||||
|
@property() private _areas?: AreaRegistryEntry[];
|
||||||
|
@property() private _areaId?: string;
|
||||||
|
|
||||||
|
private _submitting?: boolean;
|
||||||
|
private _unsubAreas?: any;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: DeviceRegistryDetailDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._error = undefined;
|
||||||
|
this._nameByUser = this._params.device.name_by_user || "";
|
||||||
|
this._areaId = this._params.device.area_id;
|
||||||
|
await this.updateComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||||
|
this._areas = areas;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsubAreas) {
|
||||||
|
this._unsubAreas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const device = this._params.device;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-paper-dialog
|
||||||
|
with-backdrop
|
||||||
|
opened
|
||||||
|
@opened-changed="${this._openedChanged}"
|
||||||
|
>
|
||||||
|
<h2>${device.name}</h2>
|
||||||
|
<paper-dialog-scrollable>
|
||||||
|
${this._error
|
||||||
|
? html`
|
||||||
|
<div class="error">${this._error}</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div class="form">
|
||||||
|
<paper-input
|
||||||
|
.value=${this._nameByUser}
|
||||||
|
@value-changed=${this._nameChanged}
|
||||||
|
.label=${this.hass.localize("ui.dialogs.more_info_settings.name")}
|
||||||
|
.placeholder=${device.name || ""}
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
></paper-input>
|
||||||
|
<div class="area">
|
||||||
|
<paper-dropdown-menu label="Area" class="flex">
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected="${this._computeSelectedArea()}"
|
||||||
|
@iron-select="${this._areaIndexChanged}"
|
||||||
|
>
|
||||||
|
<paper-item>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.no_area"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
${this._renderAreas()}
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</paper-dialog-scrollable>
|
||||||
|
<div class="paper-dialog-buttons">
|
||||||
|
<mwc-button @click="${this._updateEntry}">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entity_registry.editor.update"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-paper-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _nameChanged(ev: PolymerChangedEvent<string>): void {
|
||||||
|
this._error = undefined;
|
||||||
|
this._nameByUser = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderAreas() {
|
||||||
|
if (!this._areas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this._areas!.map(
|
||||||
|
(area) => html`
|
||||||
|
<paper-item>${area.name}</paper-item>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeSelectedArea() {
|
||||||
|
if (!this._params || !this._areas) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const device = this._params!.device;
|
||||||
|
if (!device.area_id) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// +1 because of "No Area" entry
|
||||||
|
return this._areas.findIndex((area) => area.area_id === device.area_id) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _areaIndexChanged(event): void {
|
||||||
|
const selectedAreaIdx = event.target!.selected;
|
||||||
|
this._areaId =
|
||||||
|
selectedAreaIdx < 1
|
||||||
|
? undefined
|
||||||
|
: this._areas![selectedAreaIdx - 1].area_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateEntry(): Promise<void> {
|
||||||
|
this._submitting = true;
|
||||||
|
try {
|
||||||
|
await this._params!.updateEntry({
|
||||||
|
name_by_user: this._nameByUser.trim() || null,
|
||||||
|
area_id: this._areaId || null,
|
||||||
|
});
|
||||||
|
this._params = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err.message || "Unknown error";
|
||||||
|
} finally {
|
||||||
|
this._submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||||
|
if (!(ev.detail as any).value) {
|
||||||
|
this._params = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-paper-dialog {
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
.form {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
mwc-button.warning {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-device-registry-detail": DialogDeviceRegistryDetail;
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,16 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
|
|||||||
import computeStateName from "../../../common/entity/compute_state_name";
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
import "../../../components/entity/state-badge";
|
import "../../../components/entity/state-badge";
|
||||||
import { compare } from "../../../common/string/compare";
|
import { compare } from "../../../common/string/compare";
|
||||||
import { updateDeviceRegistryEntry } from "../../../data/device_registry";
|
import {
|
||||||
|
subscribeDeviceRegistry,
|
||||||
|
updateDeviceRegistryEntry,
|
||||||
|
} from "../../../data/device_registry";
|
||||||
|
import { subscribeAreaRegistry } from "../../../data/area_registry";
|
||||||
|
|
||||||
|
import {
|
||||||
|
showDeviceRegistryDetailDialog,
|
||||||
|
loadDeviceRegistryDetailDialog,
|
||||||
|
} from "./show-dialog-device-registry-detail";
|
||||||
|
|
||||||
function computeEntityName(hass, entity) {
|
function computeEntityName(hass, entity) {
|
||||||
if (entity.name) return entity.name;
|
if (entity.name) return entity.name;
|
||||||
@ -38,6 +47,13 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.card-header .name {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
.device {
|
.device {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
@ -45,9 +61,13 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.device .model,
|
.device .model,
|
||||||
.device .manuf {
|
.device .manuf,
|
||||||
|
.device .area {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
.area .extra-info .name {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
.extra-info {
|
.extra-info {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
@ -57,40 +77,35 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
.manuf,
|
.manuf,
|
||||||
.entity-id {
|
.entity-id,
|
||||||
|
.area {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<ha-card header="[[device.name]]">
|
<ha-card>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="name">[[_deviceName(device)]]</div>
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:settings"
|
||||||
|
on-click="_gotoSettings"
|
||||||
|
></paper-icon-button>
|
||||||
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<!--
|
|
||||||
<h1>[[configEntry.title]] ([[_computeIntegrationTitle(localize, configEntry.domain)]])</h1>
|
|
||||||
-->
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="model">[[device.model]]</div>
|
<div class="model">[[device.model]]</div>
|
||||||
<div class="manuf">
|
<div class="manuf">
|
||||||
[[localize('ui.panel.config.integrations.config_entry.manuf',
|
[[localize('ui.panel.config.integrations.config_entry.manuf',
|
||||||
'manufacturer', device.manufacturer)]]
|
'manufacturer', device.manufacturer)]]
|
||||||
</div>
|
</div>
|
||||||
|
<template is="dom-if" if="[[device.area_id]]">
|
||||||
<div class="area">
|
<div class="area">
|
||||||
<paper-dropdown-menu
|
<div class="extra-info">
|
||||||
selected-item-label="{{selectedArea}}"
|
[[localize('ui.panel.config.integrations.device_registry.area')]]
|
||||||
label="Area"
|
<span class="name">{{_computeArea(areas, device)}}</span>
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
selected="[[_computeSelectedArea(areas, device)]]"
|
|
||||||
>
|
|
||||||
<paper-item>
|
|
||||||
[[localize('ui.panel.config.integrations.config_entry.no_area')]]
|
|
||||||
</paper-item>
|
|
||||||
<template is="dom-repeat" items="[[areas]]">
|
|
||||||
<paper-item area="[[item]]">[[item.name]]</paper-item>
|
|
||||||
</template>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
<template is="dom-if" if="[[device.hub_device_id]]">
|
<template is="dom-if" if="[[device.hub_device_id]]">
|
||||||
<div class="extra-info">
|
<div class="extra-info">
|
||||||
[[localize('ui.panel.config.integrations.config_entry.hub')]]
|
[[localize('ui.panel.config.integrations.config_entry.hub')]]
|
||||||
@ -144,41 +159,41 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
type: Array,
|
type: Array,
|
||||||
computed: "_computeChildDevices(device, devices)",
|
computed: "_computeChildDevices(device, devices)",
|
||||||
},
|
},
|
||||||
selectedArea: {
|
|
||||||
type: String,
|
|
||||||
observer: "_selectedAreaChanged",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeSelectedArea(areas, device) {
|
firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
loadDeviceRegistryDetailDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
|
||||||
|
this._areas = areas;
|
||||||
|
});
|
||||||
|
this._unsubDevices = subscribeDeviceRegistry(this.hass, (devices) => {
|
||||||
|
this.devices = devices;
|
||||||
|
this.device = devices.find((device) => device.id === this.device.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsubAreas) {
|
||||||
|
this._unsubAreas();
|
||||||
|
}
|
||||||
|
if (this._unsubDevices) {
|
||||||
|
this._unsubDevices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeArea(areas, device) {
|
||||||
if (!areas || !device || !device.area_id) {
|
if (!areas || !device || !device.area_id) {
|
||||||
return 0;
|
return "No Area";
|
||||||
}
|
}
|
||||||
// +1 because of "No Area" entry
|
// +1 because of "No Area" entry
|
||||||
return areas.findIndex((area) => area.area_id === device.area_id) + 1;
|
return areas.find((area) => area.area_id === device.area_id).name;
|
||||||
}
|
|
||||||
|
|
||||||
async _selectedAreaChanged(option) {
|
|
||||||
// Selected Option will transition to '' before transitioning to new value
|
|
||||||
if (option === "" || !this.device || !this.areas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const area =
|
|
||||||
option === "No Area"
|
|
||||||
? undefined
|
|
||||||
: this.areas.find((ar) => ar.name === option);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(!area && !this.device.area_id) ||
|
|
||||||
(area && area.area_id === this.device.area_id)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateDeviceRegistryEntry(this.hass, this.device.id, {
|
|
||||||
area_id: area ? area.area_id : null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeChildDevices(device, devices) {
|
_computeChildDevices(device, devices) {
|
||||||
@ -211,15 +226,29 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_deviceName(device) {
|
||||||
|
return device.name_by_user || device.name;
|
||||||
|
}
|
||||||
|
|
||||||
_computeDeviceName(devices, deviceId) {
|
_computeDeviceName(devices, deviceId) {
|
||||||
const device = devices.find((dev) => dev.id === deviceId);
|
const device = devices.find((dev) => dev.id === deviceId);
|
||||||
return device
|
return device
|
||||||
? device.name
|
? this._deviceName(device)
|
||||||
: `(${this.localize(
|
: `(${this.localize(
|
||||||
"ui.panel.config.integrations.config_entry.device_unavailable"
|
"ui.panel.config.integrations.config_entry.device_unavailable"
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_gotoSettings() {
|
||||||
|
const device = this.device;
|
||||||
|
showDeviceRegistryDetailDialog(this, {
|
||||||
|
device,
|
||||||
|
updateEntry: async (updates) => {
|
||||||
|
await updateDeviceRegistryEntry(this.hass, device.id, updates);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_openMoreInfo(ev) {
|
_openMoreInfo(ev) {
|
||||||
this.fire("hass-more-info", { entityId: ev.model.entity.entity_id });
|
this.fire("hass-more-info", { entityId: ev.model.entity.entity_id });
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import {
|
||||||
|
DeviceRegistryEntry,
|
||||||
|
DeviceRegistryEntryMutableParams,
|
||||||
|
} from "../../../data/device_registry";
|
||||||
|
|
||||||
|
export interface DeviceRegistryDetailDialogParams {
|
||||||
|
device: DeviceRegistryEntry;
|
||||||
|
updateEntry: (
|
||||||
|
updates: Partial<DeviceRegistryEntryMutableParams>
|
||||||
|
) => Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadDeviceRegistryDetailDialog = () =>
|
||||||
|
import(/* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail");
|
||||||
|
|
||||||
|
export const showDeviceRegistryDetailDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
deviceRegistryDetailParams: DeviceRegistryDetailDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-device-registry-detail",
|
||||||
|
dialogImport: loadDeviceRegistryDetailDialog,
|
||||||
|
dialogParams: deviceRegistryDetailParams,
|
||||||
|
});
|
||||||
|
};
|
@ -626,6 +626,9 @@
|
|||||||
"description": "This step requires you to visit an external website to be completed.",
|
"description": "This step requires you to visit an external website to be completed.",
|
||||||
"open_site": "Open website"
|
"open_site": "Open website"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"device_registry": {
|
||||||
|
"area": "area"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zha": {
|
"zha": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user