diff --git a/src/components/ha-selector/ha-selector-location.ts b/src/components/ha-selector/ha-selector-location.ts index 96d9d66298..01fc052cb2 100644 --- a/src/components/ha-selector/ha-selector-location.ts +++ b/src/components/ha-selector/ha-selector-location.ts @@ -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`

${this.label ? this.label : ""}

@@ -35,6 +80,17 @@ export class HaLocationSelector extends LitElement { @location-updated=${this._locationChanged} @radius-updated=${this._radiusChanged} > + `; } @@ -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> + ): 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; diff --git a/src/data/selector.ts b/src/data/selector.ts index 557650635a..f49eae11a1 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -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 { diff --git a/src/data/zone.ts b/src/data/zone.ts index 52ea733317..d59e33e87f 100644 --- a/src/data/zone.ts +++ b/src/data/zone.ts @@ -11,6 +11,11 @@ export interface Zone { radius?: number; } +export interface HomeZoneMutableParams { + latitude: number; + longitude: number; +} + export interface ZoneMutableParams { name: string; icon?: string; diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts index 1a8b4a7263..438ff479b2 100644 --- a/src/panels/config/core/ha-config-section-general.ts +++ b/src/panels/config/core/ha-config-section-general.ts @@ -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 { ${this.narrow ? html` - + @value-changed=${this._locationChanged} + > ` : html` @@ -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; - } `, ]; } diff --git a/src/panels/config/zone/dialog-home-zone-detail.ts b/src/panels/config/zone/dialog-home-zone-detail.ts new file mode 100644 index 0000000000..2786cca3cb --- /dev/null +++ b/src/panels/config/zone/dialog-home-zone-detail.ts @@ -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; + + @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` + +
+ +

+ ${this.hass!.localize( + "ui.panel.config.zone.detail.no_edit_home_zone_radius" + )} +

+
+ + ${this.hass!.localize("ui.panel.config.zone.detail.update")} + +
+ `; + } + + 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); diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts index 37432c6b8f..064bf61fa7 100644 --- a/src/panels/config/zone/dialog-zone-detail.ts +++ b/src/panels/config/zone/dialog-zone-detail.ts @@ -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; diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 2455bf2b48..46c9ea5c71 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -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} > ${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, diff --git a/src/panels/config/zone/show-dialog-home-zone-detail.ts b/src/panels/config/zone/show-dialog-home-zone-detail.ts new file mode 100644 index 0000000000..a4dc6c12c6 --- /dev/null +++ b/src/panels/config/zone/show-dialog-home-zone-detail.ts @@ -0,0 +1,20 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { HomeZoneMutableParams } from "../../../data/zone"; + +export interface HomeZoneDetailDialogParams { + updateEntry?: (updates: HomeZoneMutableParams) => Promise; +} + +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, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index 3e5142ff56..6478c5a339 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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" },