mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 22:07:20 +00:00
Improve zones UI (#4576)
* Improve zones UI * Make home blue * Only fit after edit in dialog * Filter states from UI edtor * Comments * bring editable circle to front * Update ha-config-zone.ts * Update ha-config-zone.ts * Hide side navigation for zones * Fix initial ignore fit, fix click on switch label, space dialog buttons
This commit is contained in:
parent
1f38d13b3b
commit
b7a3fe6e91
12
src/common/location/add_distance_to_coord.ts
Normal file
12
src/common/location/add_distance_to_coord.ts
Normal file
@ -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];
|
||||||
|
};
|
@ -239,6 +239,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
if (changedProps.has("area") && this.area) {
|
if (changedProps.has("area") && this.area) {
|
||||||
this._areaPicker = true;
|
this._areaPicker = true;
|
||||||
this.value = this.area;
|
this.value = this.area;
|
||||||
|
28
src/components/ha-dialog.ts
Normal file
28
src/components/ha-dialog.ts
Normal file
@ -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<Dialog>;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ export class HaSwitch extends MwcSwitch {
|
|||||||
"slotted",
|
"slotted",
|
||||||
Boolean(this._slot.assignedNodes().length)
|
Boolean(this._slot.assignedNodes().length)
|
||||||
);
|
);
|
||||||
|
this._slot.addEventListener("click", () => (this.checked = !this.checked));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static get styles(): CSSResult[] {
|
protected static get styles(): CSSResult[] {
|
||||||
|
@ -42,8 +42,13 @@ class LocationEditor extends LitElement {
|
|||||||
if (!this._leafletMap || !this.location) {
|
if (!this._leafletMap || !this.location) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((this._locationMarker as Circle).getBounds) {
|
||||||
|
this._leafletMap.fitBounds((this._locationMarker as Circle).getBounds());
|
||||||
|
} else {
|
||||||
this._leafletMap.setView(this.location, this.fitZoom);
|
this._leafletMap.setView(this.location, this.fitZoom);
|
||||||
}
|
}
|
||||||
|
this._ignoreFitToMap = this.location;
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
@ -74,7 +79,6 @@ class LocationEditor extends LitElement {
|
|||||||
) {
|
) {
|
||||||
this.fitMap();
|
this.fitMap();
|
||||||
}
|
}
|
||||||
this._ignoreFitToMap = undefined;
|
|
||||||
}
|
}
|
||||||
if (changedProps.has("radius")) {
|
if (changedProps.has("radius")) {
|
||||||
this._updateRadius();
|
this._updateRadius();
|
||||||
@ -234,6 +238,7 @@ class LocationEditor extends LitElement {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.leaflet-edit-move {
|
.leaflet-edit-move {
|
||||||
|
border-radius: 50%;
|
||||||
cursor: move !important;
|
cursor: move !important;
|
||||||
}
|
}
|
||||||
.leaflet-edit-resize {
|
.leaflet-edit-resize {
|
||||||
|
@ -32,18 +32,20 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Location {
|
export interface MarkerLocation {
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
radius: number;
|
radius?: number;
|
||||||
name: string;
|
name?: string;
|
||||||
id: string;
|
id: string;
|
||||||
icon: string;
|
icon?: string;
|
||||||
|
radius_color?: string;
|
||||||
|
editable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ha-locations-editor")
|
@customElement("ha-locations-editor")
|
||||||
export class HaLocationsEditor extends LitElement {
|
export class HaLocationsEditor extends LitElement {
|
||||||
@property() public locations?: Location[];
|
@property() public locations?: MarkerLocation[];
|
||||||
public fitZoom = 16;
|
public fitZoom = 16;
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
@ -51,6 +53,7 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
private _leafletMap?: Map;
|
private _leafletMap?: Map;
|
||||||
private _locationMarkers?: { [key: string]: Marker | Circle };
|
private _locationMarkers?: { [key: string]: Marker | Circle };
|
||||||
|
private _circles: { [key: string]: Circle } = {};
|
||||||
|
|
||||||
public fitMap(): void {
|
public fitMap(): void {
|
||||||
if (
|
if (
|
||||||
@ -74,8 +77,18 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
if (!marker) {
|
if (!marker) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
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);
|
this._leafletMap.setView(marker.getLatLng(), this.fitZoom);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
@ -155,6 +168,9 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
marker.remove();
|
marker.remove();
|
||||||
});
|
});
|
||||||
this._locationMarkers = undefined;
|
this._locationMarkers = undefined;
|
||||||
|
|
||||||
|
Object.values(this._circles).forEach((circle) => circle.remove());
|
||||||
|
this._circles = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.locations || !this.locations.length) {
|
if (!this.locations || !this.locations.length) {
|
||||||
@ -163,32 +179,37 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
|
|
||||||
this._locationMarkers = {};
|
this._locationMarkers = {};
|
||||||
|
|
||||||
this.locations.forEach((location: Location) => {
|
this.locations.forEach((location: MarkerLocation) => {
|
||||||
let icon: DivIcon | undefined;
|
let icon: DivIcon | undefined;
|
||||||
if (location.icon) {
|
if (location.icon) {
|
||||||
// create icon
|
// create icon
|
||||||
let iconHTML = "";
|
const el = document.createElement("div");
|
||||||
const el = document.createElement("ha-icon");
|
el.className = "named-icon";
|
||||||
el.setAttribute("icon", location.icon);
|
if (location.name) {
|
||||||
iconHTML = el.outerHTML;
|
el.innerText = location.name;
|
||||||
|
}
|
||||||
|
const iconEl = document.createElement("ha-icon");
|
||||||
|
iconEl.setAttribute("icon", location.icon);
|
||||||
|
el.prepend(iconEl);
|
||||||
|
|
||||||
icon = this.Leaflet!.divIcon({
|
icon = this.Leaflet!.divIcon({
|
||||||
html: iconHTML,
|
html: el.outerHTML,
|
||||||
iconSize: [24, 24],
|
iconSize: [24, 24],
|
||||||
className: "light leaflet-edit-move",
|
className: "light",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (location.radius) {
|
if (location.radius) {
|
||||||
const circle = this.Leaflet!.circle(
|
const circle = this.Leaflet!.circle(
|
||||||
[location.latitude, location.longitude],
|
[location.latitude, location.longitude],
|
||||||
{
|
{
|
||||||
color: "#FF9800",
|
color: location.radius_color ? location.radius_color : "#FF9800",
|
||||||
radius: location.radius,
|
radius: location.radius,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
circle.addTo(this._leafletMap!);
|
||||||
|
if (location.editable) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
circle.editing.enable();
|
circle.editing.enable();
|
||||||
circle.addTo(this._leafletMap!);
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const moveMarker = circle.editing._moveMarker;
|
const moveMarker = circle.editing._moveMarker;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -215,8 +236,12 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
);
|
);
|
||||||
this._locationMarkers![location.id] = circle;
|
this._locationMarkers![location.id] = circle;
|
||||||
} else {
|
} else {
|
||||||
|
this._circles[location.id] = circle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!location.radius || !location.editable) {
|
||||||
const options: MarkerOptions = {
|
const options: MarkerOptions = {
|
||||||
draggable: true,
|
draggable: Boolean(location.editable),
|
||||||
title: location.name,
|
title: location.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -255,13 +280,20 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.leaflet-edit-move {
|
.leaflet-marker-draggable {
|
||||||
cursor: move !important;
|
cursor: move !important;
|
||||||
}
|
}
|
||||||
.leaflet-edit-resize {
|
.leaflet-edit-resize {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: nesw-resize !important;
|
cursor: nesw-resize !important;
|
||||||
}
|
}
|
||||||
|
.named-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ export interface Zone {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
latitude?: number;
|
latitude: number;
|
||||||
longitude?: number;
|
longitude: number;
|
||||||
passive?: boolean;
|
passive?: boolean;
|
||||||
radius?: number;
|
radius?: number;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NO_SIDEBAR_PAGES = ["zone"];
|
||||||
|
|
||||||
@customElement("ha-panel-config")
|
@customElement("ha-panel-config")
|
||||||
class HaPanelConfig extends LitElement {
|
class HaPanelConfig extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -95,8 +97,9 @@ class HaPanelConfig extends LitElement {
|
|||||||
const isWide =
|
const isWide =
|
||||||
this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide;
|
this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide;
|
||||||
|
|
||||||
|
const showSidebar = isWide && !NO_SIDEBAR_PAGES.includes(curPage);
|
||||||
return html`
|
return html`
|
||||||
${isWide
|
${showSidebar
|
||||||
? html`
|
? html`
|
||||||
<div class="side-bar">
|
<div class="side-bar">
|
||||||
<div class="toolbar">Configuration</div>
|
<div class="toolbar">Configuration</div>
|
||||||
@ -137,7 +140,7 @@ class HaPanelConfig extends LitElement {
|
|||||||
.wideSidebar=${this._wideSidebar}
|
.wideSidebar=${this._wideSidebar}
|
||||||
.showAdvanced=${this._showAdvanced}
|
.showAdvanced=${this._showAdvanced}
|
||||||
.cloudStatus=${this._cloudStatus}
|
.cloudStatus=${this._cloudStatus}
|
||||||
class=${classMap({ "wide-config": isWide })}
|
class=${classMap({ "wide-config": showSidebar })}
|
||||||
></ha-config-router>
|
></ha-config-router>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import memoizeOne from "memoize-one";
|
|||||||
|
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-dialog";
|
|
||||||
|
|
||||||
import "../../../components/entity/ha-entities-picker";
|
import "../../../components/entity/ha-entities-picker";
|
||||||
import "../../../components/user/ha-user-picker";
|
import "../../../components/user/ha-user-picker";
|
||||||
|
import "../../../components/ha-dialog";
|
||||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
||||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -56,7 +56,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
const nameInvalid = this._name.trim() === "";
|
const nameInvalid = this._name.trim() === "";
|
||||||
return html`
|
return html`
|
||||||
<mwc-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@closing="${this._close}"
|
@closing="${this._close}"
|
||||||
.title=${this._params.entry
|
.title=${this._params.entry
|
||||||
@ -162,7 +162,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
? this.hass!.localize("ui.panel.config.person.detail.update")
|
? this.hass!.localize("ui.panel.config.person.detail.update")
|
||||||
: this.hass!.localize("ui.panel.config.person.detail.create")}
|
: this.hass!.localize("ui.panel.config.person.detail.create")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</mwc-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,10 +224,11 @@ class DialogPersonDetail extends LitElement {
|
|||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
mwc-dialog {
|
ha-dialog {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||||
|
--justify-action-buttons: space-between;
|
||||||
}
|
}
|
||||||
.form {
|
.form {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
|
@ -9,14 +9,15 @@ import {
|
|||||||
|
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-dialog";
|
|
||||||
|
|
||||||
import "../../../components/map/ha-location-editor";
|
import "../../../components/map/ha-location-editor";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
|
import "../../../components/ha-dialog";
|
||||||
|
|
||||||
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ZoneMutableParams } from "../../../data/zone";
|
import { ZoneMutableParams } from "../../../data/zone";
|
||||||
|
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
||||||
|
|
||||||
class DialogZoneDetail extends LitElement {
|
class DialogZoneDetail extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -42,10 +43,15 @@ class DialogZoneDetail extends LitElement {
|
|||||||
this._passive = this._params.entry.passive || false;
|
this._passive = this._params.entry.passive || false;
|
||||||
this._radius = this._params.entry.radius || 100;
|
this._radius = this._params.entry.radius || 100;
|
||||||
} else {
|
} else {
|
||||||
|
const movedHomeLocation = addDistanceToCoord(
|
||||||
|
[this.hass.config.latitude, this.hass.config.longitude],
|
||||||
|
500,
|
||||||
|
500
|
||||||
|
);
|
||||||
this._name = "";
|
this._name = "";
|
||||||
this._icon = "";
|
this._icon = "";
|
||||||
this._latitude = this.hass.config.latitude;
|
this._latitude = movedHomeLocation[0];
|
||||||
this._longitude = this.hass.config.longitude;
|
this._longitude = movedHomeLocation[1];
|
||||||
this._passive = false;
|
this._passive = false;
|
||||||
this._radius = 100;
|
this._radius = 100;
|
||||||
}
|
}
|
||||||
@ -57,7 +63,7 @@ class DialogZoneDetail extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<mwc-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@closing="${this._close}"
|
@closing="${this._close}"
|
||||||
.title=${this._params.entry
|
.title=${this._params.entry
|
||||||
@ -99,9 +105,10 @@ class DialogZoneDetail extends LitElement {
|
|||||||
class="flex"
|
class="flex"
|
||||||
.location=${this._locationValue}
|
.location=${this._locationValue}
|
||||||
.radius=${this._radius}
|
.radius=${this._radius}
|
||||||
.icon=${this._icon || "hass:home"}
|
.icon=${this._icon}
|
||||||
@change=${this._locationChanged}
|
@change=${this._locationChanged}
|
||||||
></ha-location-editor>
|
></ha-location-editor>
|
||||||
|
<div class="location">
|
||||||
<paper-input
|
<paper-input
|
||||||
.value=${this._latitude}
|
.value=${this._latitude}
|
||||||
.configValue=${"latitude"}
|
.configValue=${"latitude"}
|
||||||
@ -126,6 +133,7 @@ class DialogZoneDetail extends LitElement {
|
|||||||
)}"
|
)}"
|
||||||
.invalid=${String(this._longitude) === ""}
|
.invalid=${String(this._longitude) === ""}
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
</div>
|
||||||
<paper-input
|
<paper-input
|
||||||
.value=${this._radius}
|
.value=${this._radius}
|
||||||
.configValue=${"radius"}
|
.configValue=${"radius"}
|
||||||
@ -169,7 +177,7 @@ class DialogZoneDetail extends LitElement {
|
|||||||
? this.hass!.localize("ui.panel.config.zone.detail.update")
|
? this.hass!.localize("ui.panel.config.zone.detail.update")
|
||||||
: this.hass!.localize("ui.panel.config.zone.detail.create")}
|
: this.hass!.localize("ui.panel.config.zone.detail.create")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</mwc-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,17 +243,34 @@ class DialogZoneDetail extends LitElement {
|
|||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
mwc-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||||
|
--justify-action-buttons: space-between;
|
||||||
}
|
}
|
||||||
@media only screen and (min-width: 600px) {
|
@media only screen and (min-width: 600px) {
|
||||||
mwc-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-min-width: 600px;
|
--mdc-dialog-min-width: 600px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.form {
|
.form {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
.location {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.location > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.location > *:first-child {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.location > *:last-child {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
ha-location-editor {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
ha-user-picker {
|
ha-user-picker {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,12 @@ import {
|
|||||||
property,
|
property,
|
||||||
customElement,
|
customElement,
|
||||||
query,
|
query,
|
||||||
|
PropertyValues,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
|
||||||
import "../../../components/map/ha-locations-editor";
|
import "../../../components/map/ha-locations-editor";
|
||||||
|
|
||||||
@ -31,75 +33,153 @@ import {
|
|||||||
ZoneMutableParams,
|
ZoneMutableParams,
|
||||||
} from "../../../data/zone";
|
} from "../../../data/zone";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { HaLocationsEditor } from "../../../components/map/ha-locations-editor";
|
import {
|
||||||
|
HaLocationsEditor,
|
||||||
|
MarkerLocation,
|
||||||
|
} from "../../../components/map/ha-locations-editor";
|
||||||
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
|
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
|
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
||||||
|
|
||||||
@customElement("ha-config-zone")
|
@customElement("ha-config-zone")
|
||||||
export class HaConfigZone extends LitElement {
|
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public isWide?: boolean;
|
@property() public isWide?: boolean;
|
||||||
@property() public narrow?: boolean;
|
@property() public narrow?: boolean;
|
||||||
@property() private _storageItems?: Zone[];
|
@property() private _storageItems?: Zone[];
|
||||||
|
@property() private _stateItems?: HassEntity[];
|
||||||
@property() private _activeEntry: string = "";
|
@property() private _activeEntry: string = "";
|
||||||
@query("ha-locations-editor") private _map?: HaLocationsEditor;
|
@query("ha-locations-editor") private _map?: HaLocationsEditor;
|
||||||
|
private _regEntities: string[] = [];
|
||||||
|
|
||||||
|
private _getZones = memoizeOne(
|
||||||
|
(storageItems: Zone[], stateItems: HassEntity[]): MarkerLocation[] => {
|
||||||
|
const stateLocations: MarkerLocation[] = stateItems.map((state) => {
|
||||||
|
return {
|
||||||
|
id: state.entity_id,
|
||||||
|
icon: state.attributes.icon,
|
||||||
|
name: state.attributes.friendly_name || state.entity_id,
|
||||||
|
latitude: state.attributes.latitude,
|
||||||
|
longitude: state.attributes.longitude,
|
||||||
|
radius: state.attributes.radius,
|
||||||
|
radius_color:
|
||||||
|
state.entity_id === "zone.home"
|
||||||
|
? "#03a9f4"
|
||||||
|
: state.attributes.passive
|
||||||
|
? "#9b9b9b"
|
||||||
|
: "#FF9800",
|
||||||
|
editable: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const storageLocations: MarkerLocation[] = storageItems.map((zone) => {
|
||||||
|
return {
|
||||||
|
...zone,
|
||||||
|
radius_color: zone.passive ? "#9b9b9b" : "#FF9800",
|
||||||
|
editable: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return storageLocations.concat(stateLocations);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||||
|
this._regEntities = entities.map(
|
||||||
|
(registryEntry) => registryEntry.entity_id
|
||||||
|
);
|
||||||
|
this._filterStates();
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || this._storageItems === undefined) {
|
if (
|
||||||
|
!this.hass ||
|
||||||
|
this._storageItems === undefined ||
|
||||||
|
this._stateItems === undefined
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hass-loading-screen></hass-loading-screen>
|
<hass-loading-screen></hass-loading-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
const hass = this.hass;
|
const hass = this.hass;
|
||||||
const listBox = html`
|
const listBox =
|
||||||
|
this._storageItems.length === 0 && this._stateItems.length === 0
|
||||||
|
? html`
|
||||||
|
<div class="empty">
|
||||||
|
${hass.localize("ui.panel.config.zone.no_zones_created_yet")}
|
||||||
|
<br />
|
||||||
|
<mwc-button @click=${this._createZone}>
|
||||||
|
${hass.localize("ui.panel.config.zone.create_zone")}</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
<paper-listbox
|
<paper-listbox
|
||||||
attr-for-selected="data-id"
|
attr-for-selected="data-id"
|
||||||
.selected=${this._activeEntry || ""}
|
.selected=${this._activeEntry || ""}
|
||||||
>
|
>
|
||||||
${this._storageItems.map((entry) => {
|
${this._storageItems.map((entry) => {
|
||||||
return html`
|
return html`
|
||||||
<paper-icon-item data-id=${entry.id} @click=${
|
<paper-icon-item
|
||||||
this._itemClicked
|
data-id=${entry.id}
|
||||||
} .entry=${entry}>
|
@click=${this._itemClicked}
|
||||||
<ha-icon
|
.entry=${entry}
|
||||||
.icon=${entry.icon}
|
|
||||||
slot="item-icon"
|
|
||||||
>
|
>
|
||||||
</ha-icon>
|
<ha-icon .icon=${entry.icon} slot="item-icon"> </ha-icon>
|
||||||
<paper-item-body>
|
<paper-item-body>
|
||||||
${entry.name}
|
${entry.name}
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
${
|
${!this.narrow
|
||||||
!this.narrow
|
|
||||||
? html`
|
? html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:information-outline"
|
icon="hass:pencil"
|
||||||
.entry=${entry}
|
.entry=${entry}
|
||||||
@click=${this._openEditEntry}
|
@click=${this._openEditEntry}
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
`
|
`
|
||||||
: ""
|
: ""}
|
||||||
}
|
</paper-icon-item>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
${this._stateItems.map((state) => {
|
||||||
|
return html`
|
||||||
|
<paper-icon-item
|
||||||
|
data-id=${state.entity_id}
|
||||||
|
@click=${this._stateItemClicked}
|
||||||
|
>
|
||||||
|
<ha-icon .icon=${state.attributes.icon} slot="item-icon">
|
||||||
</ha-icon>
|
</ha-icon>
|
||||||
|
<paper-item-body>
|
||||||
|
${state.attributes.friendly_name || state.entity_id}
|
||||||
|
</paper-item-body>
|
||||||
|
<div style="display:inline-block">
|
||||||
|
<paper-icon-button
|
||||||
|
.entityId=${state.entity_id}
|
||||||
|
icon="hass:pencil"
|
||||||
|
disabled
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip position="left">
|
||||||
|
${state.entity_id === "zone.home"
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.zone.edit_home_zone"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.zone.configured_in_yaml"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
</div>
|
||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
${this._storageItems.length === 0
|
|
||||||
? html`
|
|
||||||
<div class="empty">
|
|
||||||
${hass.localize("ui.panel.config.zone.no_zones_created_yet")}
|
|
||||||
<mwc-button @click=${this._createZone}>
|
|
||||||
${hass.localize("ui.panel.config.zone.create_zone")}</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: html``}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage
|
<hass-subpage .header=${hass.localize("ui.panel.config.zone.caption")}>
|
||||||
.header=${hass.localize("ui.panel.config.zone.caption")}
|
|
||||||
.showBackButton=${!this.isWide}
|
|
||||||
>
|
|
||||||
${this.narrow
|
${this.narrow
|
||||||
? html`
|
? html`
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
@ -114,7 +194,10 @@ export class HaConfigZone extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ha-locations-editor
|
<ha-locations-editor
|
||||||
.locations=${this._storageItems}
|
.locations=${this._getZones(
|
||||||
|
this._storageItems,
|
||||||
|
this._stateItems
|
||||||
|
)}
|
||||||
@location-updated=${this._locationUpdated}
|
@location-updated=${this._locationUpdated}
|
||||||
@radius-updated=${this._radiusUpdated}
|
@radius-updated=${this._radiusUpdated}
|
||||||
@marker-clicked=${this._markerClicked}
|
@marker-clicked=${this._markerClicked}
|
||||||
@ -134,15 +217,56 @@ export class HaConfigZone extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._fetchData();
|
this._fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (oldHass && this._stateItems) {
|
||||||
|
this._getStates(oldHass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _fetchData() {
|
private async _fetchData() {
|
||||||
this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) =>
|
this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) =>
|
||||||
compare(ent1.name, ent2.name)
|
compare(ent1.name, ent2.name)
|
||||||
);
|
);
|
||||||
|
this._getStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getStates(oldHass?: HomeAssistant) {
|
||||||
|
let changed = false;
|
||||||
|
const tempStates = Object.values(this.hass!.states).filter((entity) => {
|
||||||
|
if (computeStateDomain(entity) !== "zone") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (oldHass?.states[entity.entity_id] !== entity) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (this._regEntities.includes(entity.entity_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
this._stateItems = tempStates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterStates() {
|
||||||
|
if (!this._stateItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tempStates = this._stateItems.filter(
|
||||||
|
(entity) => !this._regEntities.includes(entity.entity_id)
|
||||||
|
);
|
||||||
|
if (tempStates.length !== this._stateItems.length) {
|
||||||
|
this._stateItems = tempStates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _locationUpdated(ev: CustomEvent) {
|
private _locationUpdated(ev: CustomEvent) {
|
||||||
@ -181,9 +305,19 @@ export class HaConfigZone extends LitElement {
|
|||||||
this._openEditEntry(ev);
|
this._openEditEntry(ev);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry: Zone = (ev.currentTarget! as any).entry;
|
const entry: Zone = (ev.currentTarget! as any).entry;
|
||||||
this._map?.fitMarker(entry.id);
|
this._zoomZone(entry.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _stateItemClicked(ev: MouseEvent) {
|
||||||
|
const entityId = (ev.currentTarget! as HTMLElement).getAttribute(
|
||||||
|
"data-id"
|
||||||
|
)!;
|
||||||
|
this._zoomZone(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _zoomZone(id: string) {
|
||||||
|
this._map?.fitMarker(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openEditEntry(ev: MouseEvent) {
|
private _openEditEntry(ev: MouseEvent) {
|
||||||
@ -196,13 +330,29 @@ export class HaConfigZone extends LitElement {
|
|||||||
this._storageItems = this._storageItems!.concat(
|
this._storageItems = this._storageItems!.concat(
|
||||||
created
|
created
|
||||||
).sort((ent1, ent2) => compare(ent1.name, ent2.name));
|
).sort((ent1, ent2) => compare(ent1.name, ent2.name));
|
||||||
|
if (this.narrow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.updateComplete;
|
||||||
|
this._activeEntry = created.id;
|
||||||
|
this._map?.fitMarker(created.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateEntry(entry: Zone, values: Partial<ZoneMutableParams>) {
|
private async _updateEntry(
|
||||||
|
entry: Zone,
|
||||||
|
values: Partial<ZoneMutableParams>,
|
||||||
|
fitMap: boolean = false
|
||||||
|
) {
|
||||||
const updated = await updateZone(this.hass!, entry!.id, values);
|
const updated = await updateZone(this.hass!, entry!.id, values);
|
||||||
this._storageItems = this._storageItems!.map((ent) =>
|
this._storageItems = this._storageItems!.map((ent) =>
|
||||||
ent === entry ? updated : ent
|
ent === entry ? updated : ent
|
||||||
);
|
);
|
||||||
|
if (this.narrow || !fitMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.updateComplete;
|
||||||
|
this._activeEntry = entry.id;
|
||||||
|
this._map?.fitMarker(entry.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _removeEntry(entry: Zone) {
|
private async _removeEntry(entry: Zone) {
|
||||||
@ -217,6 +367,9 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
|||||||
try {
|
try {
|
||||||
await deleteZone(this.hass!, entry!.id);
|
await deleteZone(this.hass!, entry!.id);
|
||||||
this._storageItems = this._storageItems!.filter((ent) => ent !== entry);
|
this._storageItems = this._storageItems!.filter((ent) => ent !== entry);
|
||||||
|
if (!this.narrow) {
|
||||||
|
this._map?.fitMap();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
@ -228,7 +381,7 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
|||||||
entry,
|
entry,
|
||||||
createEntry: (values) => this._createEntry(values),
|
createEntry: (values) => this._createEntry(values),
|
||||||
updateEntry: entry
|
updateEntry: entry
|
||||||
? (values) => this._updateEntry(entry, values)
|
? (values) => this._updateEntry(entry, values, true)
|
||||||
: undefined,
|
: undefined,
|
||||||
removeEntry: entry ? () => this._removeEntry(entry) : undefined,
|
removeEntry: entry ? () => this._removeEntry(entry) : undefined,
|
||||||
});
|
});
|
||||||
@ -244,6 +397,10 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
|||||||
margin: 16px auto;
|
margin: 16px auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
ha-icon,
|
||||||
|
paper-icon-button:not([disabled]) {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
.empty {
|
.empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@ -256,7 +413,8 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.flex paper-listbox {
|
.flex paper-listbox,
|
||||||
|
.flex .empty {
|
||||||
border-left: 1px solid var(--divider-color);
|
border-left: 1px solid var(--divider-color);
|
||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,6 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
|||||||
|
|
||||||
Object.keys(hass.states).forEach((entityId) => {
|
Object.keys(hass.states).forEach((entityId) => {
|
||||||
var entity = hass.states[entityId];
|
var entity = hass.states[entityId];
|
||||||
var title = computeStateName(entity);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(entity.attributes.hidden && computeStateDomain(entity) !== "zone") ||
|
(entity.attributes.hidden && computeStateDomain(entity) !== "zone") ||
|
||||||
@ -118,6 +117,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var title = computeStateName(entity);
|
||||||
var icon;
|
var icon;
|
||||||
|
|
||||||
if (computeStateDomain(entity) === "zone") {
|
if (computeStateDomain(entity) === "zone") {
|
||||||
|
@ -1360,6 +1360,8 @@
|
|||||||
"create_zone": "Create Zone",
|
"create_zone": "Create Zone",
|
||||||
"add_zone": "Add Zone",
|
"add_zone": "Add Zone",
|
||||||
"confirm_delete": "Are you sure you want to delete this zone?",
|
"confirm_delete": "Are you sure you want to delete this zone?",
|
||||||
|
"configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.",
|
||||||
|
"edit_home_zone": "The location of your home can be changed in the gerenal config.",
|
||||||
"detail": {
|
"detail": {
|
||||||
"new_zone": "New Zone",
|
"new_zone": "New Zone",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user