mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 05:47: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) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("area") && this.area) {
|
||||
this._areaPicker = true;
|
||||
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",
|
||||
Boolean(this._slot.assignedNodes().length)
|
||||
);
|
||||
this._slot.addEventListener("click", () => (this.checked = !this.checked));
|
||||
}
|
||||
|
||||
protected static get styles(): CSSResult[] {
|
||||
|
@ -42,8 +42,13 @@ class LocationEditor extends LitElement {
|
||||
if (!this._leafletMap || !this.location) {
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
return html`
|
||||
@ -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 {
|
||||
|
@ -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,8 +77,18 @@ export class HaLocationsEditor extends LitElement {
|
||||
if (!marker) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
@ -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,32 +179,37 @@ 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,
|
||||
}
|
||||
);
|
||||
circle.addTo(this._leafletMap!);
|
||||
if (location.editable) {
|
||||
// @ts-ignore
|
||||
circle.editing.enable();
|
||||
circle.addTo(this._leafletMap!);
|
||||
// @ts-ignore
|
||||
const moveMarker = circle.editing._moveMarker;
|
||||
// @ts-ignore
|
||||
@ -215,8 +236,12 @@ export class HaLocationsEditor extends LitElement {
|
||||
);
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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`
|
||||
<div class="side-bar">
|
||||
<div class="toolbar">Configuration</div>
|
||||
@ -137,7 +140,7 @@ class HaPanelConfig extends LitElement {
|
||||
.wideSidebar=${this._wideSidebar}
|
||||
.showAdvanced=${this._showAdvanced}
|
||||
.cloudStatus=${this._cloudStatus}
|
||||
class=${classMap({ "wide-config": isWide })}
|
||||
class=${classMap({ "wide-config": showSidebar })}
|
||||
></ha-config-router>
|
||||
`;
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import memoizeOne from "memoize-one";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-dialog";
|
||||
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/user/ha-user-picker";
|
||||
import "../../../components/ha-dialog";
|
||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -56,7 +56,7 @@ class DialogPersonDetail extends LitElement {
|
||||
}
|
||||
const nameInvalid = this._name.trim() === "";
|
||||
return html`
|
||||
<mwc-dialog
|
||||
<ha-dialog
|
||||
open
|
||||
@closing="${this._close}"
|
||||
.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.create")}
|
||||
</mwc-button>
|
||||
</mwc-dialog>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -224,10 +224,11 @@ class DialogPersonDetail extends LitElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
mwc-dialog {
|
||||
ha-dialog {
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
|
@ -9,14 +9,15 @@ import {
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-dialog";
|
||||
|
||||
import "../../../components/map/ha-location-editor";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-dialog";
|
||||
|
||||
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ZoneMutableParams } from "../../../data/zone";
|
||||
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
||||
|
||||
class DialogZoneDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@ -42,10 +43,15 @@ class DialogZoneDetail extends LitElement {
|
||||
this._passive = this._params.entry.passive || false;
|
||||
this._radius = this._params.entry.radius || 100;
|
||||
} else {
|
||||
const movedHomeLocation = addDistanceToCoord(
|
||||
[this.hass.config.latitude, this.hass.config.longitude],
|
||||
500,
|
||||
500
|
||||
);
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._latitude = this.hass.config.latitude;
|
||||
this._longitude = this.hass.config.longitude;
|
||||
this._latitude = movedHomeLocation[0];
|
||||
this._longitude = movedHomeLocation[1];
|
||||
this._passive = false;
|
||||
this._radius = 100;
|
||||
}
|
||||
@ -57,7 +63,7 @@ class DialogZoneDetail extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<mwc-dialog
|
||||
<ha-dialog
|
||||
open
|
||||
@closing="${this._close}"
|
||||
.title=${this._params.entry
|
||||
@ -99,9 +105,10 @@ class DialogZoneDetail extends LitElement {
|
||||
class="flex"
|
||||
.location=${this._locationValue}
|
||||
.radius=${this._radius}
|
||||
.icon=${this._icon || "hass:home"}
|
||||
.icon=${this._icon}
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
<div class="location">
|
||||
<paper-input
|
||||
.value=${this._latitude}
|
||||
.configValue=${"latitude"}
|
||||
@ -126,6 +133,7 @@ class DialogZoneDetail extends LitElement {
|
||||
)}"
|
||||
.invalid=${String(this._longitude) === ""}
|
||||
></paper-input>
|
||||
</div>
|
||||
<paper-input
|
||||
.value=${this._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.create")}
|
||||
</mwc-button>
|
||||
</mwc-dialog>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -235,17 +243,34 @@ class DialogZoneDetail extends LitElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
mwc-dialog {
|
||||
ha-dialog {
|
||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
mwc-dialog {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 600px;
|
||||
}
|
||||
}
|
||||
.form {
|
||||
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 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
@ -7,10 +7,12 @@ import {
|
||||
property,
|
||||
customElement,
|
||||
query,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
|
||||
@ -31,75 +33,153 @@ import {
|
||||
ZoneMutableParams,
|
||||
} from "../../../data/zone";
|
||||
// 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")
|
||||
export class HaConfigZone extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
@property() public narrow?: boolean;
|
||||
@property() private _storageItems?: Zone[];
|
||||
@property() private _stateItems?: HassEntity[];
|
||||
@property() private _activeEntry: string = "";
|
||||
@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 {
|
||||
if (!this.hass || this._storageItems === undefined) {
|
||||
if (
|
||||
!this.hass ||
|
||||
this._storageItems === undefined ||
|
||||
this._stateItems === undefined
|
||||
) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
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
|
||||
attr-for-selected="data-id"
|
||||
.selected=${this._activeEntry || ""}
|
||||
>
|
||||
${this._storageItems.map((entry) => {
|
||||
return html`
|
||||
<paper-icon-item data-id=${entry.id} @click=${
|
||||
this._itemClicked
|
||||
} .entry=${entry}>
|
||||
<ha-icon
|
||||
.icon=${entry.icon}
|
||||
slot="item-icon"
|
||||
<paper-icon-item
|
||||
data-id=${entry.id}
|
||||
@click=${this._itemClicked}
|
||||
.entry=${entry}
|
||||
>
|
||||
</ha-icon>
|
||||
<ha-icon .icon=${entry.icon} slot="item-icon"> </ha-icon>
|
||||
<paper-item-body>
|
||||
${entry.name}
|
||||
</paper-item-body>
|
||||
${
|
||||
!this.narrow
|
||||
${!this.narrow
|
||||
? html`
|
||||
<paper-icon-button
|
||||
icon="hass:information-outline"
|
||||
icon="hass:pencil"
|
||||
.entry=${entry}
|
||||
@click=${this._openEditEntry}
|
||||
></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>
|
||||
<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-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`
|
||||
<hass-subpage
|
||||
.header=${hass.localize("ui.panel.config.zone.caption")}
|
||||
.showBackButton=${!this.isWide}
|
||||
>
|
||||
<hass-subpage .header=${hass.localize("ui.panel.config.zone.caption")}>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
@ -114,7 +194,10 @@ export class HaConfigZone extends LitElement {
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-locations-editor
|
||||
.locations=${this._storageItems}
|
||||
.locations=${this._getZones(
|
||||
this._storageItems,
|
||||
this._stateItems
|
||||
)}
|
||||
@location-updated=${this._locationUpdated}
|
||||
@radius-updated=${this._radiusUpdated}
|
||||
@marker-clicked=${this._markerClicked}
|
||||
@ -134,15 +217,56 @@ export class HaConfigZone extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
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() {
|
||||
this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) =>
|
||||
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) {
|
||||
@ -181,9 +305,19 @@ export class HaConfigZone extends LitElement {
|
||||
this._openEditEntry(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -196,13 +330,29 @@ export class HaConfigZone extends LitElement {
|
||||
this._storageItems = this._storageItems!.concat(
|
||||
created
|
||||
).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);
|
||||
this._storageItems = this._storageItems!.map((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) {
|
||||
@ -217,6 +367,9 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
||||
try {
|
||||
await deleteZone(this.hass!, entry!.id);
|
||||
this._storageItems = this._storageItems!.filter((ent) => ent !== entry);
|
||||
if (!this.narrow) {
|
||||
this._map?.fitMap();
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
@ -228,7 +381,7 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
||||
entry,
|
||||
createEntry: (values) => this._createEntry(values),
|
||||
updateEntry: entry
|
||||
? (values) => this._updateEntry(entry, values)
|
||||
? (values) => this._updateEntry(entry, values, true)
|
||||
: undefined,
|
||||
removeEntry: entry ? () => this._removeEntry(entry) : undefined,
|
||||
});
|
||||
@ -244,6 +397,10 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
||||
margin: 16px auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-icon,
|
||||
paper-icon-button:not([disabled]) {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
@ -256,7 +413,8 @@ ${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`)
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
.flex paper-listbox {
|
||||
.flex paper-listbox,
|
||||
.flex .empty {
|
||||
border-left: 1px solid var(--divider-color);
|
||||
width: 250px;
|
||||
}
|
||||
|
@ -107,7 +107,6 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
||||
|
||||
Object.keys(hass.states).forEach((entityId) => {
|
||||
var entity = hass.states[entityId];
|
||||
var title = computeStateName(entity);
|
||||
|
||||
if (
|
||||
(entity.attributes.hidden && computeStateDomain(entity) !== "zone") ||
|
||||
@ -118,6 +117,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
var title = computeStateName(entity);
|
||||
var icon;
|
||||
|
||||
if (computeStateDomain(entity) === "zone") {
|
||||
|
@ -1360,6 +1360,8 @@
|
||||
"create_zone": "Create Zone",
|
||||
"add_zone": "Add 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": {
|
||||
"new_zone": "New Zone",
|
||||
"name": "Name",
|
||||
|
Loading…
x
Reference in New Issue
Block a user