mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 13:27:22 +00:00
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:
parent
e261fafdb3
commit
c93e1b0123
@ -44,6 +44,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"light",
|
||||
"lock",
|
||||
"media_player",
|
||||
"person",
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
|
311
src/components/map/ha-map.ts
Normal file
311
src/components/map/ha-map.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
@ -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";
|
||||
|
85
src/dialogs/more-info/controls/more-info-person.ts
Normal file
85
src/dialogs/more-info/controls/more-info-person.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -630,6 +630,9 @@
|
||||
"locate": "Locate",
|
||||
"return_home": "Return home",
|
||||
"start_pause": "Start/Pause"
|
||||
},
|
||||
"person": {
|
||||
"create_zone": "Create zone from current location"
|
||||
}
|
||||
},
|
||||
"entity_registry": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user