diff --git a/src/common/location/add_distance_to_coord.ts b/src/common/location/add_distance_to_coord.ts new file mode 100644 index 0000000000..807a9334a4 --- /dev/null +++ b/src/common/location/add_distance_to_coord.ts @@ -0,0 +1,12 @@ +export const addDistanceToCoord = ( + location: [number, number], + dx: number, + dy: number +): [number, number] => { + const rEarth = 6378000; + const newLatitude = location[0] + (dy / rEarth) * (180 / Math.PI); + const newLongitude = + location[1] + + ((dx / rEarth) * (180 / Math.PI)) / Math.cos((location[0] * Math.PI) / 180); + return [newLatitude, newLongitude]; +}; diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index c3a2396fa6..4cb86f84c7 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -239,6 +239,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { } protected updated(changedProps: PropertyValues) { + super.updated(changedProps); if (changedProps.has("area") && this.area) { this._areaPicker = true; this.value = this.area; diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts new file mode 100644 index 0000000000..600ea6c20b --- /dev/null +++ b/src/components/ha-dialog.ts @@ -0,0 +1,28 @@ +import { customElement, CSSResult, css } from "lit-element"; +import "@material/mwc-dialog"; +import { style } from "@material/mwc-dialog/mwc-dialog-css"; +// tslint:disable-next-line +import { Dialog } from "@material/mwc-dialog"; +import { Constructor } from "../types"; +// tslint:disable-next-line +const MwcDialog = customElements.get("mwc-dialog") as Constructor; + +@customElement("ha-dialog") +export class HaDialog extends MwcDialog { + protected static get styles(): CSSResult[] { + return [ + style, + css` + .mdc-dialog__actions { + justify-content: var(--justify-action-buttons, flex-end); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog": HaDialog; + } +} diff --git a/src/components/ha-switch.ts b/src/components/ha-switch.ts index 5a684d2be7..9426082439 100644 --- a/src/components/ha-switch.ts +++ b/src/components/ha-switch.ts @@ -21,6 +21,7 @@ export class HaSwitch extends MwcSwitch { "slotted", Boolean(this._slot.assignedNodes().length) ); + this._slot.addEventListener("click", () => (this.checked = !this.checked)); } protected static get styles(): CSSResult[] { diff --git a/src/components/map/ha-location-editor.ts b/src/components/map/ha-location-editor.ts index c386967e58..f032516c6d 100644 --- a/src/components/map/ha-location-editor.ts +++ b/src/components/map/ha-location-editor.ts @@ -42,7 +42,12 @@ class LocationEditor extends LitElement { if (!this._leafletMap || !this.location) { return; } - this._leafletMap.setView(this.location, this.fitZoom); + if ((this._locationMarker as Circle).getBounds) { + this._leafletMap.fitBounds((this._locationMarker as Circle).getBounds()); + } else { + this._leafletMap.setView(this.location, this.fitZoom); + } + this._ignoreFitToMap = this.location; } protected render(): TemplateResult | void { @@ -74,7 +79,6 @@ class LocationEditor extends LitElement { ) { this.fitMap(); } - this._ignoreFitToMap = undefined; } if (changedProps.has("radius")) { this._updateRadius(); @@ -234,6 +238,7 @@ class LocationEditor extends LitElement { height: 100%; } .leaflet-edit-move { + border-radius: 50%; cursor: move !important; } .leaflet-edit-resize { diff --git a/src/components/map/ha-locations-editor.ts b/src/components/map/ha-locations-editor.ts index c8ef9325fe..905b2330a0 100644 --- a/src/components/map/ha-locations-editor.ts +++ b/src/components/map/ha-locations-editor.ts @@ -32,18 +32,20 @@ declare global { } } -export interface Location { +export interface MarkerLocation { latitude: number; longitude: number; - radius: number; - name: string; + radius?: number; + name?: string; id: string; - icon: string; + icon?: string; + radius_color?: string; + editable?: boolean; } @customElement("ha-locations-editor") export class HaLocationsEditor extends LitElement { - @property() public locations?: Location[]; + @property() public locations?: MarkerLocation[]; public fitZoom = 16; // tslint:disable-next-line @@ -51,6 +53,7 @@ export class HaLocationsEditor extends LitElement { // tslint:disable-next-line private _leafletMap?: Map; private _locationMarkers?: { [key: string]: Marker | Circle }; + private _circles: { [key: string]: Circle } = {}; public fitMap(): void { if ( @@ -74,7 +77,17 @@ export class HaLocationsEditor extends LitElement { if (!marker) { return; } - this._leafletMap.setView(marker.getLatLng(), this.fitZoom); + if ((marker as Circle).getBounds) { + this._leafletMap.fitBounds((marker as Circle).getBounds()); + (marker as Circle).bringToFront(); + } else { + const circle = this._circles[id]; + if (circle) { + this._leafletMap.fitBounds(circle.getBounds()); + } else { + this._leafletMap.setView(marker.getLatLng(), this.fitZoom); + } + } } protected render(): TemplateResult | void { @@ -155,6 +168,9 @@ export class HaLocationsEditor extends LitElement { marker.remove(); }); this._locationMarkers = undefined; + + Object.values(this._circles).forEach((circle) => circle.remove()); + this._circles = {}; } if (!this.locations || !this.locations.length) { @@ -163,60 +179,69 @@ export class HaLocationsEditor extends LitElement { this._locationMarkers = {}; - this.locations.forEach((location: Location) => { + this.locations.forEach((location: MarkerLocation) => { let icon: DivIcon | undefined; if (location.icon) { // create icon - let iconHTML = ""; - const el = document.createElement("ha-icon"); - el.setAttribute("icon", location.icon); - iconHTML = el.outerHTML; + const el = document.createElement("div"); + el.className = "named-icon"; + if (location.name) { + el.innerText = location.name; + } + const iconEl = document.createElement("ha-icon"); + iconEl.setAttribute("icon", location.icon); + el.prepend(iconEl); icon = this.Leaflet!.divIcon({ - html: iconHTML, + html: el.outerHTML, iconSize: [24, 24], - className: "light leaflet-edit-move", + className: "light", }); } if (location.radius) { const circle = this.Leaflet!.circle( [location.latitude, location.longitude], { - color: "#FF9800", + color: location.radius_color ? location.radius_color : "#FF9800", radius: location.radius, } ); - // @ts-ignore - circle.editing.enable(); circle.addTo(this._leafletMap!); - // @ts-ignore - const moveMarker = circle.editing._moveMarker; - // @ts-ignore - const resizeMarker = circle.editing._resizeMarkers[0]; - if (icon) { - moveMarker.setIcon(icon); - } - resizeMarker.id = moveMarker.id = location.id; - moveMarker - .addEventListener( + if (location.editable) { + // @ts-ignore + circle.editing.enable(); + // @ts-ignore + const moveMarker = circle.editing._moveMarker; + // @ts-ignore + const resizeMarker = circle.editing._resizeMarkers[0]; + if (icon) { + moveMarker.setIcon(icon); + } + resizeMarker.id = moveMarker.id = location.id; + moveMarker + .addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._updateLocation(ev) + ) + .addEventListener( + "click", + // @ts-ignore + (ev: MouseEvent) => this._markerClicked(ev) + ); + resizeMarker.addEventListener( "dragend", // @ts-ignore - (ev: DragEndEvent) => this._updateLocation(ev) - ) - .addEventListener( - "click", - // @ts-ignore - (ev: MouseEvent) => this._markerClicked(ev) + (ev: DragEndEvent) => this._updateRadius(ev) ); - resizeMarker.addEventListener( - "dragend", - // @ts-ignore - (ev: DragEndEvent) => this._updateRadius(ev) - ); - this._locationMarkers![location.id] = circle; - } else { + this._locationMarkers![location.id] = circle; + } else { + this._circles[location.id] = circle; + } + } + if (!location.radius || !location.editable) { const options: MarkerOptions = { - draggable: true, + draggable: Boolean(location.editable), title: location.name, }; @@ -255,13 +280,20 @@ export class HaLocationsEditor extends LitElement { #map { height: 100%; } - .leaflet-edit-move { + .leaflet-marker-draggable { cursor: move !important; } .leaflet-edit-resize { border-radius: 50%; cursor: nesw-resize !important; } + .named-icon { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + text-align: center; + } `; } } diff --git a/src/data/zone.ts b/src/data/zone.ts index b76f73aab0..1bd97a4351 100644 --- a/src/data/zone.ts +++ b/src/data/zone.ts @@ -4,8 +4,8 @@ export interface Zone { id: string; name: string; icon?: string; - latitude?: number; - longitude?: number; + latitude: number; + longitude: number; passive?: boolean; radius?: number; } diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index b864929901..d0b763ddc7 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -29,6 +29,8 @@ declare global { } } +const NO_SIDEBAR_PAGES = ["zone"]; + @customElement("ha-panel-config") class HaPanelConfig extends LitElement { @property() public hass!: HomeAssistant; @@ -95,8 +97,9 @@ class HaPanelConfig extends LitElement { const isWide = this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide; + const showSidebar = isWide && !NO_SIDEBAR_PAGES.includes(curPage); return html` - ${isWide + ${showSidebar ? html`