Add area picker (#4608)

* Add area picker

* Make config flow dialog wider

* Comment
This commit is contained in:
Bram Kragten 2020-01-26 21:41:11 +01:00 committed by GitHub
parent 523dc881bb
commit c7b3a517e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 284 additions and 165 deletions

View File

@ -0,0 +1,229 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import "@polymer/paper-listbox/paper-listbox";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
customElement,
property,
} from "lit-element";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import { HomeAssistant } from "../types";
import { fireEvent } from "../common/dom/fire_event";
import { PolymerChangedEvent } from "../polymer-types";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
createAreaRegistryEntry,
} from "../data/area_registry";
import {
showPromptDialog,
showAlertDialog,
} from "../dialogs/generic/show-dialog-box";
const rowRenderer = (
root: HTMLElement,
_owner,
model: { item: AreaRegistryEntry }
) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-item {
margin: -10px 0;
padding: 0;
}
paper-item.add-new {
font-weight: 500;
}
</style>
<paper-item>
<paper-item-body two-line>
<div class='name'>[[item.name]]</div>
</paper-item-body>
</paper-item>
`;
}
root.querySelector(".name")!.textContent = model.item.name!;
if (model.item.area_id === "add_new") {
root.querySelector("paper-item")!.className = "add-new";
} else {
root.querySelector("paper-item")!.classList.remove("add-new");
}
};
@customElement("ha-area-picker")
export class HaAreaPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property() public _areas?: AreaRegistryEntry[];
@property({ type: Boolean, attribute: "no-add" })
public noAdd?: boolean;
@property() private _opened?: boolean;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = this.noAdd
? areas
: [
...areas,
{
area_id: "add_new",
name: this.hass.localize("ui.components.area-picker.add_new"),
},
];
}),
];
}
protected render(): TemplateResult | void {
if (!this._areas) {
return;
}
return html`
<vaadin-combo-box-light
item-value-path="area_id"
item-id-path="area_id"
item-label-path="name"
.items=${this._areas}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._areaChanged}
>
<paper-input
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.area-picker.area")
: this.label}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.area-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
${this.hass.localize("ui.components.area-picker.clear")}
</paper-icon-button>
`
: ""}
${this._areas.length > 0
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.area-picker.show_areas"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
${this.hass.localize("ui.components.area-picker.toggle")}
</paper-icon-button>
`
: ""}
</paper-input>
</vaadin-combo-box-light>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue("");
}
private get _value() {
return this.value || "";
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _areaChanged(ev: PolymerChangedEvent<string>) {
const newValue = ev.detail.value;
if (newValue !== "add_new") {
if (newValue !== this._value) {
this._setValue(newValue);
}
return;
}
(ev.target as any).value = this._value;
showPromptDialog(this, {
title: this.hass.localize("ui.components.area-picker.add_dialog.title"),
text: this.hass.localize("ui.components.area-picker.add_dialog.text"),
confirmText: this.hass.localize(
"ui.components.area-picker.add_dialog.add"
),
inputLabel: this.hass.localize(
"ui.components.area-picker.add_dialog.name"
),
confirm: async (name) => {
if (!name) {
return;
}
try {
const area = await createAreaRegistryEntry(this.hass, {
name,
});
this._areas = [...this._areas!, area];
this._setValue(area.area_id);
} catch (err) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.components.area-picker.add_dialog.failed_create_area"
),
});
}
},
});
}
private _setValue(value: string) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResult {
return css`
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-area-picker": HaAreaPicker;
}
}

View File

@ -322,7 +322,7 @@ class DataEntryFlowDialog extends LitElement {
haStyleDialog,
css`
ha-paper-dialog {
max-width: 500px;
max-width: 600px;
}
ha-paper-dialog > * {
margin: 0;

View File

@ -11,7 +11,7 @@ import "@material/mwc-button";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "../../components/ha-area-picker";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import { configFlowContentStyles } from "./styles";
@ -19,13 +19,9 @@ import {
DeviceRegistryEntry,
updateDeviceRegistryEntry,
} from "../../data/device_registry";
import {
AreaRegistryEntry,
createAreaRegistryEntry,
} from "../../data/area_registry";
import { DataEntryFlowStepCreateEntry } from "../../data/data_entry_flow";
import { FlowConfig } from "./show-dialog-data-entry-flow";
import { showPromptDialog } from "../generic/show-dialog-box";
import { showAlertDialog } from "../generic/show-dialog-box";
@customElement("step-flow-create-entry")
class StepFlowCreateEntry extends LitElement {
@ -40,9 +36,6 @@ class StepFlowCreateEntry extends LitElement {
@property()
public devices!: DeviceRegistryEntry[];
@property()
public areas!: AreaRegistryEntry[];
protected render(): TemplateResult | void {
const localize = this.hass.localize;
@ -63,28 +56,11 @@ class StepFlowCreateEntry extends LitElement {
<b>${device.name}</b><br />
${device.model} (${device.manufacturer})
</div>
<paper-dropdown-menu-light
label="${localize(
"ui.panel.config.integrations.config_flow.area_picker_label"
)}"
<ha-area-picker
.hass=${this.hass}
.device=${device.id}
@selected-item-changed=${this._handleAreaChanged}
>
<paper-listbox slot="dropdown-content" selected="0">
<paper-item>
${localize(
"ui.panel.config.integrations.config_entry.no_area"
)}
</paper-item>
${this.areas.map(
(area) => html`
<paper-item .area=${area.area_id}>
${area.name}
</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
@value-changed=${this._areaPicked}
></ha-area-picker>
</div>
`
)}
@ -92,16 +68,6 @@ class StepFlowCreateEntry extends LitElement {
`}
</div>
<div class="buttons">
${this.devices.length > 0
? html`
<mwc-button @click="${this._promptAddArea}"
>${localize(
"ui.panel.config.integrations.config_flow.add_area"
)}</mwc-button
>
`
: ""}
<mwc-button @click="${this._flowDone}"
>${localize(
"ui.panel.config.integrations.config_flow.finish"
@ -115,59 +81,24 @@ class StepFlowCreateEntry extends LitElement {
fireEvent(this, "flow-update", { step: undefined });
}
private async _addArea(name?: string) {
if (!name) {
return;
}
try {
const area = await createAreaRegistryEntry(this.hass, {
name,
});
this.areas = [...this.areas, area];
} catch (err) {
alert(
this.hass.localize(
"ui.panel.config.integrations.config_flow.failed_create_area"
)
);
}
}
private async _areaPicked(ev: CustomEvent) {
const picker = ev.currentTarget as any;
const device = picker.device;
private async _promptAddArea() {
showPromptDialog(this, {
title: this.hass.localize(
"ui.panel.config.integrations.config_flow.name_new_area"
),
inputLabel: this.hass.localize(
"ui.panel.config.integrations.config_flow.area_picker_label"
),
confirm: (text) => this._addArea(text),
});
}
private async _handleAreaChanged(ev: Event) {
const dropdown = ev.currentTarget as any;
const device = dropdown.device;
// Item first becomes null, then new item.
if (!dropdown.selectedItem) {
return;
}
const area = dropdown.selectedItem.area;
const area = ev.detail.value;
try {
await updateDeviceRegistryEntry(this.hass, device, {
area_id: area,
});
} catch (err) {
alert(
this.hass.localize(
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.error_saving_area",
"error",
"err.message"
)
);
dropdown.value = null;
err.message
),
});
picker.value = null;
}
}
@ -188,7 +119,7 @@ class StepFlowCreateEntry extends LitElement {
border-radius: 4px;
margin: 4px;
display: inline-block;
width: 200px;
width: 250px;
}
.buttons > *:last-child {
margin-left: auto;

View File

@ -15,15 +15,12 @@ import "@polymer/paper-item/paper-item";
import "@material/mwc-button/mwc-button";
import "../../components/dialog/ha-paper-dialog";
import "../../components/ha-area-picker";
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";
import { computeDeviceName } from "../../data/device_registry";
@customElement("dialog-device-registry-detail")
@ -33,11 +30,9 @@ class DialogDeviceRegistryDetail extends LitElement {
@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
@ -49,20 +44,6 @@ class DialogDeviceRegistryDetail extends LitElement {
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``;
@ -88,36 +69,20 @@ class DialogDeviceRegistryDetail extends LitElement {
<paper-input
.value=${this._nameByUser}
@value-changed=${this._nameChanged}
.label=${this.hass.localize("ui.dialogs.more_info_settings.name")}
.label=${this.hass.localize("ui.dialogs.devices.name")}
.placeholder=${device.name || ""}
.disabled=${this._submitting}
></paper-input>
<div class="area">
<paper-dropdown-menu
label="${this.hass.localize(
"ui.panel.config.devices.area_picker_label"
)}"
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>
<ha-area-picker
.hass=${this.hass}
.value=${this._areaId}
@value-changed=${this._areaPicked}
></ha-area-picker>
</div>
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button @click="${this._updateEntry}">
${this.hass.localize("ui.panel.config.entities.editor.update")}
${this.hass.localize("ui.panel.config.devices.update")}
</mwc-button>
</div>
</ha-paper-dialog>
@ -129,35 +94,8 @@ class DialogDeviceRegistryDetail extends LitElement {
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 _areaPicked(event: CustomEvent): void {
this._areaId = event.detail.value;
}
private async _updateEntry(): Promise<void> {

View File

@ -17,6 +17,7 @@ import { HomeAssistant } from "../../types";
import { DialogParams } from "./show-dialog-box";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { classMap } from "lit-html/directives/class-map";
@customElement("dialog-box")
class DialogBox extends LitElement {
@ -56,7 +57,13 @@ class DialogBox extends LitElement {
<paper-dialog-scrollable>
${this._params.text
? html`
<p>${this._params.text}</p>
<p
class=${classMap({
"no-bottom-padding": Boolean(this._params.prompt),
})}
>
${this._params.text}
</p>
`
: ""}
${this._params.prompt
@ -137,6 +144,9 @@ class DialogBox extends LitElement {
padding-bottom: 24px;
color: var(--primary-text-color);
}
.no-bottom-padding {
padding-bottom: 0;
}
.secondary {
color: var(--secondary-text-color);
}

View File

@ -535,9 +535,23 @@
},
"device-picker": {
"clear": "Clear",
"toggle": "Toggle",
"show_devices": "Show devices",
"device": "Device"
},
"area-picker": {
"clear": "Clear",
"show_areas": "Show areas",
"area": "Area",
"add_new": "Add new area…",
"add_dialog": {
"title": "Add new area",
"text": "Enter the name of the new area.",
"name": "Name",
"add": "Add",
"failed_create_area": "Failed to create area."
}
},
"relative_time": {
"past": "{time} ago",
"future": "In {time}",
@ -1254,7 +1268,8 @@
"description": "Manage connected devices",
"unnamed_device": "Unnamed device",
"unknown_error": "Unknown error",
"area_picker_label": "Area",
"name": "Name",
"update": "Update",
"automation": {
"triggers": {
"caption": "Do something when..."
@ -1425,11 +1440,7 @@
"finish": "Finish",
"submit": "Submit",
"not_all_required_fields": "Not all required fields are filled in.",
"add_area": "Add Area",
"area_picker_label": "Area",
"failed_create_area": "Failed to create area.",
"error_saving_area": "Error saving area: {error}",
"name_new_area": "Name of the new area?",
"created_config": "Created config for {name}.",
"external_step": {
"description": "This step requires you to visit an external website to be completed.",