mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Allow picking location on a map (#3244)
* Allow picking location on a map * Add some better defaults * Close connection before navigation
This commit is contained in:
parent
4ccf450ad4
commit
2d056bad81
@ -15,6 +15,10 @@ gulp.task("gen-service-worker-dev", (done) => {
|
||||
writeSW(
|
||||
`
|
||||
console.debug('Service worker disabled in development');
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
`
|
||||
);
|
||||
done();
|
||||
|
@ -11,14 +11,14 @@ export const setupLeafletMap = async (
|
||||
}
|
||||
// tslint:disable-next-line
|
||||
const Leaflet = (await import(/* webpackChunkName: "leaflet" */ "leaflet")) as LeafletModuleType;
|
||||
Leaflet.Icon.Default.imagePath = "/static/images/leaflet";
|
||||
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
|
||||
|
||||
const map = Leaflet.map(mapElement);
|
||||
const style = document.createElement("link");
|
||||
style.setAttribute("href", "/static/images/leaflet/leaflet.css");
|
||||
style.setAttribute("rel", "stylesheet");
|
||||
mapElement.parentNode.appendChild(style);
|
||||
map.setView([51.505, -0.09], 13);
|
||||
map.setView([52.3731339, 4.8903147], 13);
|
||||
Leaflet.tileLayer(
|
||||
`https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}${
|
||||
Leaflet.Browser.retina ? "@2x.png" : ".png"
|
||||
|
125
src/components/map/ha-location-editor.ts
Normal file
125
src/components/map/ha-location-editor.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
html,
|
||||
CSSResult,
|
||||
css,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { Marker, Map, LeafletMouseEvent, DragEndEvent, LatLng } from "leaflet";
|
||||
import {
|
||||
setupLeafletMap,
|
||||
LeafletModuleType,
|
||||
} from "../../common/dom/setup-leaflet-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-location-editor")
|
||||
class LocationEditor extends LitElement {
|
||||
@property() public location?: [number, number];
|
||||
public fitZoom = 16;
|
||||
|
||||
private _ignoreFitToMap?: [number, number];
|
||||
|
||||
// tslint:disable-next-line
|
||||
private Leaflet?: LeafletModuleType;
|
||||
private _leafletMap?: Map;
|
||||
private _locationMarker?: Marker;
|
||||
|
||||
public fitMap(): void {
|
||||
if (!this._leafletMap || !this.location) {
|
||||
return;
|
||||
}
|
||||
this._leafletMap.setView(this.location, this.fitZoom);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<div id="map"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initMap();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
// Still loading.
|
||||
if (!this.Leaflet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateMarker();
|
||||
if (!this._ignoreFitToMap || this._ignoreFitToMap !== this.location) {
|
||||
this.fitMap();
|
||||
}
|
||||
this._ignoreFitToMap = undefined;
|
||||
}
|
||||
|
||||
private get _mapEl(): HTMLDivElement {
|
||||
return this.shadowRoot!.querySelector("div")!;
|
||||
}
|
||||
|
||||
private async _initMap(): Promise<void> {
|
||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(this._mapEl);
|
||||
this._leafletMap.addEventListener(
|
||||
"click",
|
||||
// @ts-ignore
|
||||
(ev: LeafletMouseEvent) => this._updateLocation(ev.latlng)
|
||||
);
|
||||
this._updateMarker();
|
||||
this.fitMap();
|
||||
this._leafletMap.invalidateSize();
|
||||
}
|
||||
|
||||
private _updateLocation(latlng: LatLng) {
|
||||
this.location = this._ignoreFitToMap = [latlng.lat, latlng.lng];
|
||||
fireEvent(this, "change", undefined, { bubbles: false });
|
||||
}
|
||||
|
||||
private _updateMarker(): void {
|
||||
if (!this.location) {
|
||||
if (this._locationMarker) {
|
||||
this._locationMarker.remove();
|
||||
this._locationMarker = undefined;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._locationMarker) {
|
||||
this._locationMarker.setLatLng(this.location);
|
||||
return;
|
||||
}
|
||||
this._locationMarker = this.Leaflet!.marker(this.location, {
|
||||
draggable: true,
|
||||
});
|
||||
this._locationMarker.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateLocation(ev.target.getLatLng())
|
||||
);
|
||||
this._leafletMap!.addLayer(this._locationMarker);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 300px;
|
||||
}
|
||||
#map {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-location-editor": LocationEditor;
|
||||
}
|
||||
}
|
@ -84,8 +84,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchOnboardingSteps();
|
||||
import("./onboarding-integrations");
|
||||
import("./onboarding-core-config");
|
||||
import(/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations");
|
||||
import(/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config");
|
||||
registerServiceWorker(false);
|
||||
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
||||
}
|
||||
@ -156,6 +156,15 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
const result = stepResult.result as OnboardingResponses["integration"];
|
||||
this._loading = true;
|
||||
|
||||
// If we don't close the connection manually, the connection will be
|
||||
// closed when we navigate away from the page. Firefox allows JS to
|
||||
// continue to execute, and so HAWS will automatically reconnect once
|
||||
// the connection is closed. However, since we revoke our token below,
|
||||
// HAWS will reload the page, since that will trigger the auth flow.
|
||||
// In Firefox, triggering a reload will overrule the navigation that
|
||||
// was in progress.
|
||||
this.hass!.connection.close();
|
||||
|
||||
// Revoke current auth token.
|
||||
await this.hass!.auth.revoke();
|
||||
|
||||
@ -184,6 +193,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
this.initializeHass(auth, conn);
|
||||
// Load config strings for integrations
|
||||
(this as any)._loadFragmentTranslations(this.hass!.language, "config");
|
||||
// Make sure hass is initialized + the config/user callbacks have called.
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,14 @@ import {
|
||||
detectCoreConfig,
|
||||
saveCoreConfig,
|
||||
} from "../data/core";
|
||||
import { UNIT_C } from "../common/const";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { onboardCoreConfigStep } from "../data/onboarding";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { createTimezoneListEl } from "../components/timezone-datalist";
|
||||
import "../components/map/ha-location-editor";
|
||||
|
||||
const amsterdam = [52.3731339, 4.8903147];
|
||||
|
||||
@customElement("onboarding-core-config")
|
||||
class OnboardingCoreConfig extends LitElement {
|
||||
@ -34,8 +36,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
@property() private _working = false;
|
||||
|
||||
@property() private _name!: ConfigUpdateValues["location_name"];
|
||||
@property() private _latitude!: string;
|
||||
@property() private _longitude!: string;
|
||||
@property() private _location!: [number, number];
|
||||
@property() private _elevation!: string;
|
||||
@property() private _unitSystem!: ConfigUpdateValues["unit_system"];
|
||||
@property() private _timeZone!: string;
|
||||
@ -82,26 +83,12 @@ class OnboardingCoreConfig extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<paper-input
|
||||
<ha-location-editor
|
||||
class="flex"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.latitude"
|
||||
)}
|
||||
name="latitude"
|
||||
.disabled=${this._working}
|
||||
.value=${this._latitudeValue}
|
||||
@value-changed=${this._handleChange}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
class="flex"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.longitude"
|
||||
)}
|
||||
name="longitude"
|
||||
.disabled=${this._working}
|
||||
.value=${this._longitudeValue}
|
||||
@value-changed=${this._handleChange}
|
||||
></paper-input>
|
||||
.location=${this._locationValue}
|
||||
.fitZoom=${14}
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -205,36 +192,20 @@ class OnboardingCoreConfig extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private get _latitudeValue() {
|
||||
return this._latitude !== undefined
|
||||
? this._latitude
|
||||
: this.hass.config.latitude;
|
||||
}
|
||||
|
||||
private get _longitudeValue() {
|
||||
return this._longitude !== undefined
|
||||
? this._longitude
|
||||
: this.hass.config.longitude;
|
||||
private get _locationValue() {
|
||||
return this._location || amsterdam;
|
||||
}
|
||||
|
||||
private get _elevationValue() {
|
||||
return this._elevation !== undefined
|
||||
? this._elevation
|
||||
: this.hass.config.elevation;
|
||||
return this._elevation !== undefined ? this._elevation : 0;
|
||||
}
|
||||
|
||||
private get _timeZoneValue() {
|
||||
return this._timeZone !== undefined
|
||||
? this._timeZone
|
||||
: this.hass.config.time_zone;
|
||||
return this._timeZone;
|
||||
}
|
||||
|
||||
private get _unitSystemValue() {
|
||||
return this._unitSystem !== undefined
|
||||
? this._unitSystem
|
||||
: this.hass.config.unit_system.temperature === UNIT_C
|
||||
? "metric"
|
||||
: "imperial";
|
||||
return this._unitSystem !== undefined ? this._unitSystem : "metric";
|
||||
}
|
||||
|
||||
private _handleChange(ev: PolymerChangedEvent<string>) {
|
||||
@ -242,6 +213,10 @@ class OnboardingCoreConfig extends LitElement {
|
||||
this[`_${target.name}`] = target.value;
|
||||
}
|
||||
|
||||
private _locationChanged(ev) {
|
||||
this._location = ev.currentTarget.location;
|
||||
}
|
||||
|
||||
private _unitSystemChanged(
|
||||
ev: PolymerChangedEvent<ConfigUpdateValues["unit_system"]>
|
||||
) {
|
||||
@ -252,14 +227,17 @@ class OnboardingCoreConfig extends LitElement {
|
||||
this._working = true;
|
||||
try {
|
||||
const values = await detectCoreConfig(this.hass);
|
||||
for (const key in values) {
|
||||
if (key === "unit_system") {
|
||||
this._unitSystem = values[key]!;
|
||||
} else if (key === "time_zone") {
|
||||
this._timeZone = values[key]!;
|
||||
} else {
|
||||
this[`_${key}`] = values[key];
|
||||
}
|
||||
if (values.latitude && values.longitude) {
|
||||
this._location = [Number(values.latitude), Number(values.longitude)];
|
||||
}
|
||||
if (values.elevation) {
|
||||
this._elevation = String(values.elevation);
|
||||
}
|
||||
if (values.unit_system) {
|
||||
this._unitSystem = values.unit_system;
|
||||
}
|
||||
if (values.time_zone) {
|
||||
this._timeZone = values.time_zone;
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Failed to detect location information: ${err.message}`);
|
||||
@ -272,13 +250,14 @@ class OnboardingCoreConfig extends LitElement {
|
||||
ev.preventDefault();
|
||||
this._working = true;
|
||||
try {
|
||||
const location = this._locationValue;
|
||||
await saveCoreConfig(this.hass, {
|
||||
location_name: this._nameValue,
|
||||
latitude: Number(this._latitudeValue),
|
||||
longitude: Number(this._longitudeValue),
|
||||
latitude: location[0],
|
||||
longitude: location[1],
|
||||
elevation: Number(this._elevationValue),
|
||||
unit_system: this._unitSystemValue,
|
||||
time_zone: this._timeZoneValue,
|
||||
time_zone: this._timeZoneValue || "UTC",
|
||||
});
|
||||
const result = await onboardCoreConfigStep(this.hass);
|
||||
fireEvent(this, "onboarding-step", {
|
||||
|
@ -19,6 +19,7 @@ import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { UNIT_C } from "../../../common/const";
|
||||
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
|
||||
import { createTimezoneListEl } from "../../../components/timezone-datalist";
|
||||
import "../../../components/map/ha-location-editor";
|
||||
|
||||
@customElement("ha-config-core-form")
|
||||
class ConfigCoreForm extends LitElement {
|
||||
@ -26,8 +27,8 @@ class ConfigCoreForm extends LitElement {
|
||||
|
||||
@property() private _working = false;
|
||||
|
||||
@property() private _latitude!: string;
|
||||
@property() private _longitude!: string;
|
||||
@property() private _location!: [number, number];
|
||||
|
||||
@property() private _elevation!: string;
|
||||
@property() private _unitSystem!: ConfigUpdateValues["unit_system"];
|
||||
@property() private _timeZone!: string;
|
||||
@ -56,26 +57,11 @@ class ConfigCoreForm extends LitElement {
|
||||
: ""}
|
||||
|
||||
<div class="row">
|
||||
<paper-input
|
||||
<ha-location-editor
|
||||
class="flex"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.latitude"
|
||||
)}
|
||||
name="latitude"
|
||||
.disabled=${disabled}
|
||||
.value=${this._latitudeValue}
|
||||
@value-changed=${this._handleChange}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
class="flex"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.longitude"
|
||||
)}
|
||||
name="longitude"
|
||||
.disabled=${disabled}
|
||||
.value=${this._longitudeValue}
|
||||
@value-changed=${this._handleChange}
|
||||
></paper-input>
|
||||
.location=${this._locationValue}
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -176,16 +162,10 @@ class ConfigCoreForm extends LitElement {
|
||||
input.inputElement.appendChild(createTimezoneListEl());
|
||||
}
|
||||
|
||||
private get _latitudeValue() {
|
||||
return this._latitude !== undefined
|
||||
? this._latitude
|
||||
: this.hass.config.latitude;
|
||||
}
|
||||
|
||||
private get _longitudeValue() {
|
||||
return this._longitude !== undefined
|
||||
? this._longitude
|
||||
: this.hass.config.longitude;
|
||||
private get _locationValue() {
|
||||
return this._location !== undefined
|
||||
? this._location
|
||||
: [Number(this.hass.config.latitude), Number(this.hass.config.longitude)];
|
||||
}
|
||||
|
||||
private get _elevationValue() {
|
||||
@ -213,6 +193,10 @@ class ConfigCoreForm extends LitElement {
|
||||
this[`_${target.name}`] = target.value;
|
||||
}
|
||||
|
||||
private _locationChanged(ev) {
|
||||
this._location = ev.currentTarget.location;
|
||||
}
|
||||
|
||||
private _unitSystemChanged(
|
||||
ev: PolymerChangedEvent<ConfigUpdateValues["unit_system"]>
|
||||
) {
|
||||
@ -222,9 +206,10 @@ class ConfigCoreForm extends LitElement {
|
||||
private async _save() {
|
||||
this._working = true;
|
||||
try {
|
||||
const location = this._locationValue;
|
||||
await saveCoreConfig(this.hass, {
|
||||
latitude: Number(this._latitudeValue),
|
||||
longitude: Number(this._longitudeValue),
|
||||
latitude: location[0],
|
||||
longitude: location[1],
|
||||
elevation: Number(this._elevationValue),
|
||||
unit_system: this._unitSystemValue,
|
||||
time_zone: this._timeZoneValue,
|
||||
|
Loading…
x
Reference in New Issue
Block a user