mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Location/zone editor updates (#19994)
This commit is contained in:
parent
178feb7330
commit
b82f1128fe
@ -7,8 +7,10 @@ import type {
|
||||
LocationSelectorValue,
|
||||
} from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { SchemaUnion } from "../ha-form/types";
|
||||
import type { MarkerLocation } from "../map/ha-locations-editor";
|
||||
import "../map/ha-locations-editor";
|
||||
import "../ha-form/ha-form";
|
||||
|
||||
@customElement("ha-selector-location")
|
||||
export class HaLocationSelector extends LitElement {
|
||||
@ -24,6 +26,49 @@ export class HaLocationSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(radius?: boolean, radius_readonly?: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "latitude",
|
||||
required: true,
|
||||
selector: { number: { step: "any" } },
|
||||
},
|
||||
{
|
||||
name: "longitude",
|
||||
required: true,
|
||||
selector: { number: { step: "any" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
...(radius
|
||||
? [
|
||||
{
|
||||
name: "radius",
|
||||
required: true,
|
||||
default: 1000,
|
||||
disabled: !!radius_readonly,
|
||||
selector: { number: { min: 0, step: 1, mode: "box" } as const },
|
||||
} as const,
|
||||
]
|
||||
: []),
|
||||
] as const
|
||||
);
|
||||
|
||||
protected willUpdate() {
|
||||
if (!this.value) {
|
||||
this.value = {
|
||||
latitude: this.hass.config.latitude,
|
||||
longitude: this.hass.config.longitude,
|
||||
radius: this.selector.location?.radius ? 1000 : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<p>${this.label ? this.label : ""}</p>
|
||||
@ -35,6 +80,17 @@ export class HaLocationSelector extends LitElement {
|
||||
@location-updated=${this._locationChanged}
|
||||
@radius-updated=${this._radiusChanged}
|
||||
></ha-locations-editor>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.schema=${this._schema(
|
||||
this.selector.location?.radius,
|
||||
this.selector.location?.radius_readonly
|
||||
)}
|
||||
.data=${this.value}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -66,7 +122,8 @@ export class HaLocationSelector extends LitElement {
|
||||
? "mdi:map-marker-radius"
|
||||
: "mdi:map-marker",
|
||||
location_editable: true,
|
||||
radius_editable: true,
|
||||
radius_editable:
|
||||
!!selector.location?.radius && !selector.location?.radius_readonly,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -80,14 +137,39 @@ export class HaLocationSelector extends LitElement {
|
||||
}
|
||||
|
||||
private _radiusChanged(ev: CustomEvent) {
|
||||
const radius = ev.detail.radius;
|
||||
const radius = Math.round(ev.detail.radius);
|
||||
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
const radius = Math.round(ev.detail.value.radius);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
latitude: value.latitude,
|
||||
longitude: value.longitude,
|
||||
...(this.selector.location?.radius &&
|
||||
!this.selector.location?.radius_readonly
|
||||
? {
|
||||
radius,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
this.hass.localize(`ui.components.selectors.location.${entry.name}`);
|
||||
|
||||
static styles = css`
|
||||
ha-locations-editor {
|
||||
display: block;
|
||||
height: 400px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
|
@ -270,7 +270,11 @@ export interface LanguageSelector {
|
||||
}
|
||||
|
||||
export interface LocationSelector {
|
||||
location: { radius?: boolean; icon?: string } | null;
|
||||
location: {
|
||||
radius?: boolean;
|
||||
radius_readonly?: boolean;
|
||||
icon?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface LocationSelectorValue {
|
||||
|
@ -11,6 +11,11 @@ export interface Zone {
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export interface HomeZoneMutableParams {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
export interface ZoneMutableParams {
|
||||
name: string;
|
||||
icon?: string;
|
||||
|
@ -18,18 +18,20 @@ import "../../../components/ha-language-picker";
|
||||
import "../../../components/ha-radio";
|
||||
import type { HaRadio } from "../../../components/ha-radio";
|
||||
import "../../../components/ha-select";
|
||||
import "../../../components/ha-selector/ha-selector-location";
|
||||
import type { LocationSelectorValue } from "../../../data/selector";
|
||||
import "../../../components/ha-settings-row";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
import "../../../components/ha-timezone-picker";
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
|
||||
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
|
||||
const LOCATION_SELECTOR = { location: {} };
|
||||
|
||||
@customElement("ha-config-section-general")
|
||||
class HaConfigSectionGeneral extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -244,15 +246,16 @@ class HaConfigSectionGeneral extends LitElement {
|
||||
</div>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-locations-editor
|
||||
<ha-selector-location
|
||||
.hass=${this.hass}
|
||||
.locations=${this._markerLocation(
|
||||
.selector=${LOCATION_SELECTOR}
|
||||
.value=${this._selectorLocation(
|
||||
this.hass.config.latitude,
|
||||
this.hass.config.longitude,
|
||||
this._location
|
||||
)}
|
||||
@location-updated=${this._locationChanged}
|
||||
></ha-locations-editor>
|
||||
@value-changed=${this._locationChanged}
|
||||
></ha-selector-location>
|
||||
`
|
||||
: html`
|
||||
<ha-settings-row>
|
||||
@ -320,7 +323,7 @@ class HaConfigSectionGeneral extends LitElement {
|
||||
}
|
||||
|
||||
private _locationChanged(ev: CustomEvent) {
|
||||
this._location = ev.detail.location;
|
||||
this._location = [ev.detail.value.latitude, ev.detail.value.longitude];
|
||||
}
|
||||
|
||||
private async _updateEntry(ev: CustomEvent) {
|
||||
@ -381,19 +384,15 @@ class HaConfigSectionGeneral extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _markerLocation = memoizeOne(
|
||||
private _selectorLocation = memoizeOne(
|
||||
(
|
||||
lat: number,
|
||||
lng: number,
|
||||
latDefault: number,
|
||||
lngDefault: number,
|
||||
location?: [number, number]
|
||||
): MarkerLocation[] => [
|
||||
{
|
||||
id: "location",
|
||||
latitude: location ? location[0] : lat,
|
||||
longitude: location ? location[1] : lng,
|
||||
location_editable: true,
|
||||
},
|
||||
]
|
||||
): LocationSelectorValue => ({
|
||||
latitude: location != null ? location[0] : latDefault,
|
||||
longitude: location != null ? location[1] : lngDefault,
|
||||
})
|
||||
);
|
||||
|
||||
private _editLocation() {
|
||||
@ -441,11 +440,6 @@ class HaConfigSectionGeneral extends LitElement {
|
||||
margin-top: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
ha-locations-editor {
|
||||
display: block;
|
||||
height: 400px;
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
150
src/panels/config/zone/dialog-home-zone-detail.ts
Normal file
150
src/panels/config/zone/dialog-home-zone-detail.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import { HomeZoneMutableParams } from "../../../data/zone";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { HomeZoneDetailDialogParams } from "./show-dialog-home-zone-detail";
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
name: "location",
|
||||
required: true,
|
||||
selector: { location: { radius: true, radius_readonly: true } },
|
||||
},
|
||||
];
|
||||
|
||||
class DialogHomeZoneDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _error?: Record<string, string>;
|
||||
|
||||
@state() private _data?: HomeZoneMutableParams;
|
||||
|
||||
@state() private _params?: HomeZoneDetailDialogParams;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public showDialog(params: HomeZoneDetailDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._data = {
|
||||
latitude: this.hass.config.latitude,
|
||||
longitude: this.hass.config.longitude,
|
||||
};
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._data = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
const latInvalid = String(this._data.latitude) === "";
|
||||
const lngInvalid = String(this._data.longitude) === "";
|
||||
|
||||
const valid = !latInvalid && !lngInvalid;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass!.localize("ui.panel.config.zone.edit_home")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.schema=${SCHEMA}
|
||||
.data=${this._formData(this._data)}
|
||||
.error=${this._error}
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.no_edit_home_zone_radius"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${!valid || this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.zone.detail.update")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _formData = memoizeOne((data: HomeZoneMutableParams) => ({
|
||||
...data,
|
||||
location: {
|
||||
latitude: data.latitude,
|
||||
longitude: data.longitude,
|
||||
radius: this.hass.states["zone.home"]?.attributes?.radius || 100,
|
||||
},
|
||||
}));
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
const value = { ...ev.detail.value };
|
||||
value.latitude = value.location.latitude;
|
||||
value.longitude = value.location.longitude;
|
||||
delete value.location;
|
||||
this._data = value;
|
||||
}
|
||||
|
||||
private _computeLabel = (): string => "";
|
||||
|
||||
private async _updateEntry() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
await this._params!.updateEntry!(this._data!);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = { base: err ? err.message : "Unknown error" };
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: min(600px, 95vw);
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: calc(
|
||||
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-home-zone-detail": DialogHomeZoneDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-home-zone-detail", DialogHomeZoneDetail);
|
@ -145,30 +145,8 @@ class DialogZoneDetail extends LitElement {
|
||||
required: true,
|
||||
selector: { location: { radius: true, icon } },
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
name: "latitude",
|
||||
required: true,
|
||||
selector: { number: {} },
|
||||
},
|
||||
{
|
||||
name: "longitude",
|
||||
required: true,
|
||||
|
||||
selector: { number: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "passive_note", type: "constant" },
|
||||
{ name: "passive", selector: { boolean: {} } },
|
||||
{
|
||||
name: "radius",
|
||||
required: false,
|
||||
selector: { number: { min: 0, max: 999999, mode: "box" } },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
@ -184,15 +162,9 @@ class DialogZoneDetail extends LitElement {
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
const value = { ...ev.detail.value };
|
||||
if (
|
||||
value.location.latitude !== this._data!.latitude ||
|
||||
value.location.longitude !== this._data!.longitude ||
|
||||
value.location.radius !== this._data!.radius
|
||||
) {
|
||||
value.latitude = value.location.latitude;
|
||||
value.longitude = value.location.longitude;
|
||||
value.radius = Math.round(value.location.radius);
|
||||
}
|
||||
value.latitude = value.location.latitude;
|
||||
value.longitude = value.location.longitude;
|
||||
value.radius = value.location.radius;
|
||||
delete value.location;
|
||||
if (!value.icon) {
|
||||
delete value.icon;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mdiCog, mdiPencil, mdiPencilOff, mdiPlus } from "@mdi/js";
|
||||
import { mdiPencil, mdiPencilOff, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@ -35,6 +35,7 @@ import {
|
||||
updateZone,
|
||||
Zone,
|
||||
ZoneMutableParams,
|
||||
HomeZoneMutableParams,
|
||||
} from "../../../data/zone";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@ -47,6 +48,7 @@ import type { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
|
||||
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
|
||||
|
||||
@customElement("ha-config-zone")
|
||||
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
@ -193,12 +195,12 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
!this._canEditCore}
|
||||
.path=${stateObject.entity_id === "zone.home" &&
|
||||
this._canEditCore
|
||||
? mdiCog
|
||||
? mdiPencil
|
||||
: mdiPencilOff}
|
||||
.label=${stateObject.entity_id === "zone.home"
|
||||
? hass.localize("ui.panel.config.zone.edit_home")
|
||||
: hass.localize("ui.panel.config.zone.edit_zone")}
|
||||
@click=${this._openCoreConfig}
|
||||
@click=${this._editHomeZone}
|
||||
></ha-icon-button>
|
||||
${stateObject.entity_id !== "zone.home"
|
||||
? html`
|
||||
@ -400,7 +402,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
this._openDialog(entry);
|
||||
}
|
||||
|
||||
private async _openCoreConfig(ev) {
|
||||
private async _editHomeZone(ev) {
|
||||
if (ev.currentTarget.noEdit) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.zone.can_not_edit"),
|
||||
@ -409,7 +411,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
navigate("/config/general");
|
||||
showHomeZoneDetailDialog(this, {
|
||||
updateEntry: (values) => this._updateHomeZoneEntry(values),
|
||||
});
|
||||
}
|
||||
|
||||
private async _createEntry(values: ZoneMutableParams) {
|
||||
@ -427,6 +431,14 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
this._map?.fitMarker(created.id);
|
||||
}
|
||||
|
||||
private async _updateHomeZoneEntry(values: HomeZoneMutableParams) {
|
||||
await saveCoreConfig(this.hass, {
|
||||
latitude: values.latitude,
|
||||
longitude: values.longitude,
|
||||
});
|
||||
this._zoomZone("zone.home");
|
||||
}
|
||||
|
||||
private async _updateEntry(
|
||||
entry: Zone,
|
||||
values: Partial<ZoneMutableParams>,
|
||||
|
20
src/panels/config/zone/show-dialog-home-zone-detail.ts
Normal file
20
src/panels/config/zone/show-dialog-home-zone-detail.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeZoneMutableParams } from "../../../data/zone";
|
||||
|
||||
export interface HomeZoneDetailDialogParams {
|
||||
updateEntry?: (updates: HomeZoneMutableParams) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export const loadHomeZoneDetailDialog = () =>
|
||||
import("./dialog-home-zone-detail");
|
||||
|
||||
export const showHomeZoneDetailDialog = (
|
||||
element: HTMLElement,
|
||||
params: HomeZoneDetailDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-home-zone-detail",
|
||||
dialogImport: loadHomeZoneDetailDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
@ -377,6 +377,11 @@
|
||||
"upload_failed": "Upload failed",
|
||||
"unknown_file": "Unknown file"
|
||||
},
|
||||
"location": {
|
||||
"latitude": "[%key:ui::panel::config::zone::detail::latitude%]",
|
||||
"longitude": "[%key:ui::panel::config::zone::detail::longitude%]",
|
||||
"radius": "[%key:ui::panel::config::zone::detail::radius%]"
|
||||
},
|
||||
"selector": {
|
||||
"options": "Selector Options",
|
||||
"types": {
|
||||
@ -4122,10 +4127,6 @@
|
||||
"confirm_delete": "Are you sure you want to delete this zone?",
|
||||
"can_not_edit": "Unable to edit zone",
|
||||
"configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.",
|
||||
"edit_home_zone": "The radius of the Home zone can't be edited from the frontend yet. Drag the marker on the map to move the home zone.",
|
||||
"edit_home_zone_narrow": "The radius of the Home zone can't be edited from the frontend yet. The location can be changed from the general configuration.",
|
||||
"go_to_core_config": "Go to general configuration?",
|
||||
"home_zone_core_config": "The location of your home zone is editable from the general configuration page. The radius of the Home zone can't be edited from the frontend yet. Do you want to go to the general configuration?",
|
||||
"detail": {
|
||||
"new_zone": "New zone",
|
||||
"name": "Name",
|
||||
@ -4140,7 +4141,8 @@
|
||||
"required_error_msg": "This field is required",
|
||||
"delete": "Delete",
|
||||
"create": "Add",
|
||||
"update": "Update"
|
||||
"update": "Update",
|
||||
"no_edit_home_zone_radius": "The radius of the home zone is not editable in the UI."
|
||||
},
|
||||
"core_location_dialog": "Home Assistant location"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user