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:
Paulus Schoutsen 2019-06-04 08:47:02 -07:00 committed by GitHub
parent 4ccf450ad4
commit 2d056bad81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 195 additions and 91 deletions

View File

@ -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();

View File

@ -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"

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

View File

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

View File

@ -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", {

View File

@ -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,