Allow editing of device name (#3209)

* Allow editing of device name

* Patches

* Update dialog-device-registry-detail.ts
This commit is contained in:
Penny Wood 2019-05-26 22:27:33 +08:00 committed by Paulus Schoutsen
parent 7691e3f2c2
commit c24f8a2115
5 changed files with 318 additions and 57 deletions

View File

@ -16,8 +16,8 @@ export interface DeviceRegistryEntry {
}
export interface DeviceRegistryEntryMutableParams {
area_id?: string;
name_by_user?: string;
area_id?: string | null;
name_by_user?: string | null;
}
export const updateDeviceRegistryEntry = (

View 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;
}
}

View File

@ -14,7 +14,16 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
import computeStateName from "../../../common/entity/compute_state_name";
import "../../../components/entity/state-badge";
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) {
if (entity.name) return entity.name;
@ -38,6 +47,13 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
padding-bottom: 10px;
min-width: 0;
}
.card-header {
display: flex;
justify-content: space-between;
}
.card-header .name {
width: 90%;
}
.device {
width: 30%;
}
@ -45,9 +61,13 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
font-weight: bold;
}
.device .model,
.device .manuf {
.device .manuf,
.device .area {
color: var(--secondary-text-color);
}
.area .extra-info .name {
color: var(--primary-text-color);
}
.extra-info {
margin-top: 8px;
}
@ -57,39 +77,34 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
padding-bottom: 4px;
}
.manuf,
.entity-id {
.entity-id,
.area {
color: var(--secondary-text-color);
}
</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">
<!--
<h1>[[configEntry.title]] ([[_computeIntegrationTitle(localize, configEntry.domain)]])</h1>
-->
<div class="info">
<div class="model">[[device.model]]</div>
<div class="manuf">
[[localize('ui.panel.config.integrations.config_entry.manuf',
'manufacturer', device.manufacturer)]]
</div>
<div class="area">
<paper-dropdown-menu
selected-item-label="{{selectedArea}}"
label="Area"
>
<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>
<template is="dom-if" if="[[device.area_id]]">
<div class="area">
<div class="extra-info">
[[localize('ui.panel.config.integrations.device_registry.area')]]
<span class="name">{{_computeArea(areas, device)}}</span>
</div>
</div>
</template>
</div>
<template is="dom-if" if="[[device.hub_device_id]]">
<div class="extra-info">
@ -144,41 +159,41 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
type: Array,
computed: "_computeChildDevices(device, devices)",
},
selectedArea: {
type: String,
observer: "_selectedAreaChanged",
},
};
}
_computeSelectedArea(areas, device) {
if (!areas || !device || !device.area_id) {
return 0;
}
// +1 because of "No Area" entry
return areas.findIndex((area) => area.area_id === device.area_id) + 1;
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadDeviceRegistryDetailDialog();
}
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,
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) {
return "No Area";
}
// +1 because of "No Area" entry
return areas.find((area) => area.area_id === device.area_id).name;
}
_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) {
const device = devices.find((dev) => dev.id === deviceId);
return device
? device.name
? this._deviceName(device)
: `(${this.localize(
"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) {
this.fire("hass-more-info", { entityId: ev.model.entity.entity_id });
}

View File

@ -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,
});
};

View File

@ -626,6 +626,9 @@
"description": "This step requires you to visit an external website to be completed.",
"open_site": "Open website"
}
},
"device_registry": {
"area": "area"
}
},
"zha": {