Add more info for Person (#4848)

* Add more info for Person

* Update src/dialogs/more-info/controls/more-info-vacuum.ts

Co-Authored-By: Paulus Schoutsen <balloob@gmail.com>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Bram Kragten 2020-02-13 18:06:52 +01:00 committed by GitHub
parent e261fafdb3
commit c93e1b0123
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 446 additions and 14 deletions

View File

@ -44,6 +44,7 @@ export const DOMAINS_WITH_MORE_INFO = [
"light",
"lock",
"media_player",
"person",
"script",
"sun",
"timer",

View File

@ -0,0 +1,311 @@
import "@polymer/paper-icon-button/paper-icon-button";
import { Circle, Layer, Map, Marker } from "leaflet";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import {
LeafletModuleType,
setupLeafletMap,
} from "../../common/dom/setup-leaflet-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { debounce } from "../../common/util/debounce";
import { HomeAssistant } from "../../types";
import "../../panels/map/ha-entity-marker";
@customElement("ha-map")
class HaMap extends LitElement {
@property() public hass?: HomeAssistant;
@property() public entities?: string[];
@property() public darkMode = false;
@property() public zoom?: number;
// tslint:disable-next-line
private Leaflet?: LeafletModuleType;
private _leafletMap?: Map;
// @ts-ignore
private _resizeObserver?: ResizeObserver;
private _debouncedResizeListener = debounce(
() => {
if (!this._leafletMap) {
return;
}
this._leafletMap.invalidateSize();
},
100,
false
);
private _mapItems: Array<Marker | Circle> = [];
private _mapZones: Array<Marker | Circle> = [];
private _connected = false;
public connectedCallback(): void {
super.connectedCallback();
this._connected = true;
if (this.hasUpdated) {
this.loadMap();
this._attachObserver();
}
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._connected = false;
if (this._leafletMap) {
this._leafletMap.remove();
this._leafletMap = undefined;
this.Leaflet = undefined;
}
if (this._resizeObserver) {
this._resizeObserver.unobserve(this._mapEl);
} else {
window.removeEventListener("resize", this._debouncedResizeListener);
}
}
protected render(): TemplateResult {
if (!this.entities) {
return html``;
}
return html`
<div id="map"></div>
`;
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this.loadMap();
if (this._connected) {
this._attachObserver();
}
}
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("hass")) {
this._drawEntities();
this._fitMap();
}
}
private get _mapEl(): HTMLDivElement {
return this.shadowRoot!.getElementById("map") as HTMLDivElement;
}
private async loadMap(): Promise<void> {
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
this._mapEl,
this.darkMode
);
this._drawEntities();
this._leafletMap.invalidateSize();
this._fitMap();
}
private _fitMap(): void {
if (!this._leafletMap || !this.Leaflet || !this.hass) {
return;
}
if (this._mapItems.length === 0) {
this._leafletMap.setView(
new this.Leaflet.LatLng(
this.hass.config.latitude,
this.hass.config.longitude
),
this.zoom || 14
);
return;
}
const bounds = this.Leaflet.latLngBounds(
this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : []
);
this._leafletMap.fitBounds(bounds.pad(0.5));
if (this.zoom && this._leafletMap.getZoom() > this.zoom) {
this._leafletMap.setZoom(this.zoom);
}
}
private _drawEntities(): void {
const hass = this.hass;
const map = this._leafletMap;
const Leaflet = this.Leaflet;
if (!hass || !map || !Leaflet) {
return;
}
if (this._mapItems) {
this._mapItems.forEach((marker) => marker.remove());
}
const mapItems: Layer[] = (this._mapItems = []);
if (this._mapZones) {
this._mapZones.forEach((marker) => marker.remove());
}
const mapZones: Layer[] = (this._mapZones = []);
const allEntities = this.entities!.concat();
for (const entity of allEntities) {
const entityId = entity;
const stateObj = hass.states[entityId];
if (!stateObj) {
continue;
}
const title = computeStateName(stateObj);
const {
latitude,
longitude,
passive,
icon,
radius,
entity_picture: entityPicture,
gps_accuracy: gpsAccuracy,
} = stateObj.attributes;
if (!(latitude && longitude)) {
continue;
}
if (computeStateDomain(stateObj) === "zone") {
// DRAW ZONE
if (passive) {
continue;
}
// create icon
let iconHTML = "";
if (icon) {
const el = document.createElement("ha-icon");
el.setAttribute("icon", icon);
iconHTML = el.outerHTML;
} else {
const el = document.createElement("span");
el.innerHTML = title;
iconHTML = el.outerHTML;
}
// create marker with the icon
mapZones.push(
Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
html: iconHTML,
iconSize: [24, 24],
className: this.darkMode ? "dark" : "light",
}),
interactive: false,
title,
})
);
// create circle around it
mapZones.push(
Leaflet.circle([latitude, longitude], {
interactive: false,
color: "#FF9800",
radius,
})
);
continue;
}
// DRAW ENTITY
// create icon
const entityName = title
.split(" ")
.map((part) => part[0])
.join("")
.substr(0, 3);
// create market with the icon
mapItems.push(
Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
// Leaflet clones this element before adding it to the map. This messes up
// our Polymer object and we can't pass data through. Thus we hack like this.
html: `
<ha-entity-marker
entity-id="${entityId}"
entity-name="${entityName}"
entity-picture="${entityPicture || ""}"
></ha-entity-marker>
`,
iconSize: [48, 48],
className: "",
}),
title: computeStateName(stateObj),
})
);
// create circle around if entity has accuracy
if (gpsAccuracy) {
mapItems.push(
Leaflet.circle([latitude, longitude], {
interactive: false,
color: "#0288D1",
radius: gpsAccuracy,
})
);
}
}
this._mapItems.forEach((marker) => map.addLayer(marker));
this._mapZones.forEach((marker) => map.addLayer(marker));
}
private _attachObserver(): void {
// Observe changes to map size and invalidate to prevent broken rendering
// Uses ResizeObserver in Chrome, otherwise window resize event
// @ts-ignore
if (typeof ResizeObserver === "function") {
// @ts-ignore
this._resizeObserver = new ResizeObserver(() =>
this._debouncedResizeListener()
);
this._resizeObserver.observe(this._mapEl);
} else {
window.addEventListener("resize", this._debouncedResizeListener);
}
}
static get styles(): CSSResult {
return css`
:host {
display: block;
height: 300px;
}
#map {
height: 100%;
}
#map.dark {
background: #090909;
}
.dark {
color: #ffffff;
}
.light {
color: #000000;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-map": HaMap;
}
}

View File

@ -1,4 +1,5 @@
import { HomeAssistant } from "../types";
import { navigate } from "../common/navigate";
export const defaultRadiusColor = "#FF9800";
export const homeRadiusColor: string = "#03a9f4";
@ -48,3 +49,19 @@ export const deleteZone = (hass: HomeAssistant, zoneId: string) =>
type: "zone/delete",
zone_id: zoneId,
});
let inititialZoneEditorData: Partial<ZoneMutableParams> | undefined;
export const showZoneEditor = (
el: HTMLElement,
data?: Partial<ZoneMutableParams>
) => {
inititialZoneEditorData = data;
navigate(el, "/config/zone/new");
};
export const getZoneEditorInitData = () => {
const data = inititialZoneEditorData;
inititialZoneEditorData = undefined;
return data;
};

View File

@ -16,6 +16,7 @@ import "./more-info-input_datetime";
import "./more-info-light";
import "./more-info-lock";
import "./more-info-media_player";
import "./more-info-person";
import "./more-info-script";
import "./more-info-sun";
import "./more-info-timer";

View File

@ -0,0 +1,85 @@
import {
LitElement,
html,
TemplateResult,
CSSResult,
css,
property,
customElement,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket";
import "@material/mwc-button";
import "../../../components/map/ha-map";
import { HomeAssistant } from "../../../types";
import { showZoneEditor } from "../../../data/zone";
import { fireEvent } from "../../../common/dom/fire_event";
@customElement("more-info-person")
class MoreInfoPerson extends LitElement {
@property() public hass!: HomeAssistant;
@property() public stateObj?: HassEntity;
protected render(): TemplateResult {
if (!this.hass || !this.stateObj) {
return html``;
}
return html`
<ha-attributes
.stateObj=${this.stateObj}
extraFilters="id,user_id,editable"
></ha-attributes>
${this.stateObj.attributes.latitude && this.stateObj.attributes.longitude
? html`
<ha-map
.hass=${this.hass}
.entities=${[this.stateObj.entity_id]}
></ha-map>
`
: ""}
${this.hass.user?.is_admin &&
this.stateObj.state === "not_home" &&
this.stateObj.attributes.latitude &&
this.stateObj.attributes.longitude
? html`
<div class="actions">
<mwc-button @click=${this._handleAction}>
${this.hass.localize(
"ui.dialogs.more_info_control.person.create_zone"
)}
</mwc-button>
</div>
`
: ""}
`;
}
private _handleAction() {
showZoneEditor(this, {
latitude: this.stateObj!.attributes.latitude,
longitude: this.stateObj!.attributes.longitude,
});
fireEvent(this, "hass-more-info", { entityId: null });
}
static get styles(): CSSResult {
return css`
.flex {
display: flex;
justify-content: space-between;
}
.actions {
margin: 36px 0 8px 0;
text-align: right;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"more-info-person": MoreInfoPerson;
}
}

View File

@ -123,12 +123,15 @@ class MoreInfoVacuum extends LitElement {
: ""}
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY)
? html`
<div">
<div>
<span>
<iron-icon .icon=${stateObj.attributes.battery_icon}></iron-icon>
${stateObj.attributes.battery_level} %
</span>
</div>`
<iron-icon
.icon=${stateObj.attributes.battery_icon}
></iron-icon>
${stateObj.attributes.battery_level}%
</span>
</div>
`
: ""}
</div>

View File

@ -20,6 +20,7 @@ import {
ZoneMutableParams,
passiveRadiusColor,
defaultRadiusColor,
getZoneEditorInitData,
} from "../../../data/zone";
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
@ -47,15 +48,20 @@ 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 = "mdi:map-marker";
this._latitude = movedHomeLocation[0];
this._longitude = movedHomeLocation[1];
const initConfig = getZoneEditorInitData();
let movedHomeLocation;
if (!initConfig?.latitude || !initConfig?.longitude) {
movedHomeLocation = addDistanceToCoord(
[this.hass.config.latitude, this.hass.config.longitude],
Math.random() * 500 * (Math.random() < 0.5 ? -1 : 1),
Math.random() * 500 * (Math.random() < 0.5 ? -1 : 1)
);
}
this._latitude = initConfig?.latitude || movedHomeLocation[0];
this._longitude = initConfig?.longitude || movedHomeLocation[1];
this._name = initConfig?.name || "";
this._icon = initConfig?.icon || "mdi:map-marker";
this._passive = false;
this._radius = 100;
}

View File

@ -46,6 +46,7 @@ import memoizeOne from "memoize-one";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { subscribeEntityRegistry } from "../../../data/entity_registry";
import { configSections } from "../ha-panel-config";
import { navigate } from "../../../common/navigate";
@customElement("ha-config-zone")
export class HaConfigZone extends SubscribeMixin(LitElement) {
@ -234,6 +235,10 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchData();
if (this.route.path === "/new") {
navigate(this, "/config/zone", true);
this._createZone();
}
}
protected updated(changedProps: PropertyValues) {

View File

@ -630,6 +630,9 @@
"locate": "Locate",
"return_home": "Return home",
"start_pause": "Start/Pause"
},
"person": {
"create_zone": "Create zone from current location"
}
},
"entity_registry": {