@@ -162,8 +163,19 @@ class ConfigCoreForm extends LitElement {
input.inputElement.appendChild(createTimezoneListEl());
}
- private _locationValue = memoizeOne(
- (location, lat, lng) => location || [Number(lat), Number(lng)]
+ private _markerLocation = memoizeOne(
+ (
+ lat: number,
+ lng: number,
+ location?: [number, number]
+ ): MarkerLocation[] => [
+ {
+ id: "location",
+ latitude: location ? location[0] : lat,
+ longitude: location ? location[1] : lng,
+ location_editable: true,
+ },
+ ]
);
private get _elevationValue() {
@@ -192,7 +204,7 @@ class ConfigCoreForm extends LitElement {
}
private _locationChanged(ev) {
- this._location = ev.currentTarget.location;
+ this._location = ev.detail.location;
}
private _unitSystemChanged(
@@ -204,11 +216,10 @@ class ConfigCoreForm extends LitElement {
private async _save() {
this._working = true;
try {
- const location = this._locationValue(
- this._location,
+ const location = this._location || [
this.hass.config.latitude,
- this.hass.config.longitude
- );
+ this.hass.config.longitude,
+ ];
await saveCoreConfig(this.hass, {
latitude: location[0],
longitude: location[1],
diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts
index ac4cedc32a..f49ab613e3 100644
--- a/src/panels/config/zone/dialog-zone-detail.ts
+++ b/src/panels/config/zone/dialog-zone-detail.ts
@@ -9,13 +9,9 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield";
import "../../../components/ha-switch";
-import "../../../components/map/ha-location-editor";
-import {
- defaultRadiusColor,
- getZoneEditorInitData,
- passiveRadiusColor,
- ZoneMutableParams,
-} from "../../../data/zone";
+import "../../../components/map/ha-locations-editor";
+import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
+import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
@@ -132,17 +128,19 @@ class DialogZoneDetail extends LitElement {
)}"
.invalid=${iconValid}
>
-
+ .locations=${this._location(
+ this._latitude,
+ this._longitude,
+ this._radius,
+ this._passive,
+ this._icon
+ )}
+ @location-updated=${this._locationChanged}
+ @radius-updated=${this._radiusChanged}
+ >
[Number(lat), Number(lng)]);
+ private _location = memoizeOne(
+ (
+ lat: number,
+ lng: number,
+ radius: number,
+ passive: boolean,
+ icon: string
+ ): MarkerLocation[] => {
+ const computedStyles = getComputedStyle(this);
+ const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
+ const passiveRadiusColor = computedStyles.getPropertyValue(
+ "--secondary-text-color"
+ );
+ return [
+ {
+ id: "location",
+ latitude: Number(lat),
+ longitude: Number(lng),
+ radius,
+ radius_color: passive ? passiveRadiusColor : zoneRadiusColor,
+ icon,
+ location_editable: true,
+ radius_editable: true,
+ },
+ ];
+ }
+ );
- private _locationChanged(ev) {
- [this._latitude, this._longitude] = ev.currentTarget.location;
- this._radius = ev.currentTarget.radius;
+ private _locationChanged(ev: CustomEvent) {
+ [this._latitude, this._longitude] = ev.detail.location;
+ }
+
+ private _radiusChanged(ev: CustomEvent) {
+ this._radius = ev.detail.radius;
}
private _passiveChanged(ev) {
@@ -292,7 +319,7 @@ class DialogZoneDetail extends LitElement {
.location > *:last-child {
margin-left: 4px;
}
- ha-location-editor {
+ ha-locations-editor {
margin-top: 16px;
}
a {
diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts
index e5f6883d20..ad1526f0bf 100644
--- a/src/panels/config/zone/ha-config-zone.ts
+++ b/src/panels/config/zone/ha-config-zone.ts
@@ -31,11 +31,8 @@ import { saveCoreConfig } from "../../../data/core";
import { subscribeEntityRegistry } from "../../../data/entity_registry";
import {
createZone,
- defaultRadiusColor,
deleteZone,
fetchZones,
- homeRadiusColor,
- passiveRadiusColor,
updateZone,
Zone,
ZoneMutableParams,
@@ -73,6 +70,15 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
private _getZones = memoizeOne(
(storageItems: Zone[], stateItems: HassEntity[]): MarkerLocation[] => {
+ const computedStyles = getComputedStyle(this);
+ const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
+ const passiveRadiusColor = computedStyles.getPropertyValue(
+ "--secondary-text-color"
+ );
+ const homeRadiusColor = computedStyles.getPropertyValue(
+ "--primary-color"
+ );
+
const stateLocations: MarkerLocation[] = stateItems.map(
(entityState) => ({
id: entityState.entity_id,
@@ -86,7 +92,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
? homeRadiusColor
: entityState.attributes.passive
? passiveRadiusColor
- : defaultRadiusColor,
+ : zoneRadiusColor,
location_editable:
entityState.entity_id === "zone.home" && this._canEditCore,
radius_editable: false,
@@ -94,7 +100,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
);
const storageLocations: MarkerLocation[] = storageItems.map((zone) => ({
...zone,
- radius_color: zone.passive ? passiveRadiusColor : defaultRadiusColor,
+ radius_color: zone.passive ? passiveRadiusColor : zoneRadiusColor,
location_editable: true,
radius_editable: true,
}));
@@ -274,7 +280,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
}
}
- protected updated(changedProps: PropertyValues) {
+ public willUpdate(changedProps: PropertyValues) {
super.updated(changedProps);
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (oldHass && this._stateItems) {
@@ -410,8 +416,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
if (this.narrow) {
return;
}
- await this.updateComplete;
this._activeEntry = created.id;
+ await this.updateComplete;
+ await this._map?.updateComplete;
this._map?.fitMarker(created.id);
}
@@ -427,8 +434,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
if (this.narrow || !fitMap) {
return;
}
- await this.updateComplete;
this._activeEntry = entry.id;
+ await this.updateComplete;
+ await this._map?.updateComplete;
this._map?.fitMarker(entry.id);
}
diff --git a/src/panels/logbook/ha-panel-logbook.ts b/src/panels/logbook/ha-panel-logbook.ts
index 9c93f077ee..5a6b89ee92 100644
--- a/src/panels/logbook/ha-panel-logbook.ts
+++ b/src/panels/logbook/ha-panel-logbook.ts
@@ -1,4 +1,5 @@
import { mdiRefresh } from "@mdi/js";
+import "@material/mwc-icon-button";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { css, html, LitElement, PropertyValues } from "lit";
@@ -9,7 +10,6 @@ import "../../components/entity/ha-entity-picker";
import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
-import "../../components/ha-icon-button";
import "../../components/ha-menu-button";
import {
clearLogbookCache,
diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts
index 493ee46823..e6c825d9b9 100644
--- a/src/panels/lovelace/cards/hui-map-card.ts
+++ b/src/panels/lovelace/cards/hui-map-card.ts
@@ -1,14 +1,5 @@
-import { HassEntity } from "home-assistant-js-websocket";
-import {
- Circle,
- CircleMarker,
- LatLngTuple,
- Layer,
- Map,
- Marker,
- Polyline,
- TileLayer,
-} from "leaflet";
+import { HassEntities, HassEntity } from "home-assistant-js-websocket";
+import { LatLngTuple } from "leaflet";
import {
css,
CSSResultGroup,
@@ -17,32 +8,106 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
-import { customElement, property } from "lit/decorators";
-import { classMap } from "lit/directives/class-map";
-import {
- LeafletModuleType,
- replaceTileLayer,
- setupLeafletMap,
-} from "../../../common/dom/setup-leaflet-map";
+import { customElement, property, query, state } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
-import { computeStateDomain } from "../../../common/entity/compute_state_domain";
-import { computeStateName } from "../../../common/entity/compute_state_name";
-import { debounce } from "../../../common/util/debounce";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import { fetchRecent } from "../../../data/history";
import { HomeAssistant } from "../../../types";
-import "../../map/ha-entity-marker";
+import "../../../components/map/ha-entity-marker";
import { findEntities } from "../common/find-entities";
-import { installResizeObserver } from "../common/install-resize-observer";
import { processConfigEntities } from "../common/process-config-entities";
import { EntityConfig } from "../entity-rows/types";
import { LovelaceCard } from "../types";
import { MapCardConfig } from "./types";
+import "../../../components/map/ha-map";
+import { mdiImageFilterCenterFocus } from "@mdi/js";
+import type { HaMap, HaMapPaths } from "../../../components/map/ha-map";
+import memoizeOne from "memoize-one";
+const MINUTE = 60000;
+
+const COLORS = [
+ "#0288D1",
+ "#00AA00",
+ "#984ea3",
+ "#00d2d5",
+ "#ff7f00",
+ "#af8d00",
+ "#7f80cd",
+ "#b3e900",
+ "#c42e60",
+ "#a65628",
+ "#f781bf",
+ "#8dd3c7",
+];
@customElement("hui-map-card")
class HuiMapCard extends LitElement implements LovelaceCard {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ type: Boolean, reflect: true })
+ public isPanel = false;
+
+ @state()
+ private _history?: HassEntity[][];
+
+ @state()
+ private _config?: MapCardConfig;
+
+ @query("ha-map")
+ private _map?: HaMap;
+
+ private _date?: Date;
+
+ private _configEntities?: string[];
+
+ private _colorDict: Record = {};
+
+ private _colorIndex = 0;
+
+ public setConfig(config: MapCardConfig): void {
+ if (!config) {
+ throw new Error("Error in card configuration.");
+ }
+
+ if (!config.entities?.length && !config.geo_location_sources) {
+ throw new Error(
+ "Either entities or geo_location_sources must be specified"
+ );
+ }
+ if (config.entities && !Array.isArray(config.entities)) {
+ throw new Error("Entities need to be an array");
+ }
+ if (
+ config.geo_location_sources &&
+ !Array.isArray(config.geo_location_sources)
+ ) {
+ throw new Error("Geo_location_sources needs to be an array");
+ }
+
+ this._config = config;
+ this._configEntities = (config.entities
+ ? processConfigEntities(config.entities)
+ : []
+ ).map((entity) => entity.entity);
+
+ this._cleanupHistory();
+ }
+
+ public getCardSize(): number {
+ if (!this._config?.aspect_ratio) {
+ return 7;
+ }
+
+ const ratio = parseAspectRatio(this._config.aspect_ratio);
+ const ar =
+ ratio && ratio.w > 0 && ratio.h > 0
+ ? `${((100 * ratio.h) / ratio.w).toFixed(2)}`
+ : "100";
+ return 1 + Math.floor(Number(ar) / 25) || 3;
+ }
+
public static async getConfigElement() {
await import("../editor/config-elements/hui-map-card-editor");
return document.createElement("hui-map-card-editor");
@@ -66,129 +131,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return { type: "map", entities: foundEntities };
}
- @property({ attribute: false }) public hass!: HomeAssistant;
-
- @property({ type: Boolean, reflect: true })
- public isPanel = false;
-
- @property()
- private _history?: HassEntity[][];
-
- private _date?: Date;
-
- @property()
- private _config?: MapCardConfig;
-
- private _configEntities?: EntityConfig[];
-
- // eslint-disable-next-line
- private Leaflet?: LeafletModuleType;
-
- private _leafletMap?: Map;
-
- private _tileLayer?: TileLayer;
-
- private _resizeObserver?: ResizeObserver;
-
- private _debouncedResizeListener = debounce(
- () => {
- if (!this.isConnected || !this._leafletMap) {
- return;
- }
- this._leafletMap.invalidateSize();
- },
- 250,
- false
- );
-
- private _mapItems: Array = [];
-
- private _mapZones: Array = [];
-
- private _mapPaths: Array = [];
-
- private _colorDict: Record = {};
-
- private _colorIndex = 0;
-
- private _colors: string[] = [
- "#0288D1",
- "#00AA00",
- "#984ea3",
- "#00d2d5",
- "#ff7f00",
- "#af8d00",
- "#7f80cd",
- "#b3e900",
- "#c42e60",
- "#a65628",
- "#f781bf",
- "#8dd3c7",
- ];
-
- public setConfig(config: MapCardConfig): void {
- if (!config) {
- throw new Error("Error in card configuration.");
- }
-
- if (!config.entities?.length && !config.geo_location_sources) {
- throw new Error(
- "Either entities or geo_location_sources must be specified"
- );
- }
- if (config.entities && !Array.isArray(config.entities)) {
- throw new Error("Entities need to be an array");
- }
- if (
- config.geo_location_sources &&
- !Array.isArray(config.geo_location_sources)
- ) {
- throw new Error("Geo_location_sources needs to be an array");
- }
-
- this._config = config;
- this._configEntities = config.entities
- ? processConfigEntities(config.entities)
- : [];
-
- this._cleanupHistory();
- }
-
- public getCardSize(): number {
- if (!this._config?.aspect_ratio) {
- return 7;
- }
-
- const ratio = parseAspectRatio(this._config.aspect_ratio);
- const ar =
- ratio && ratio.w > 0 && ratio.h > 0
- ? `${((100 * ratio.h) / ratio.w).toFixed(2)}`
- : "100";
- return 1 + Math.floor(Number(ar) / 25) || 3;
- }
-
- public connectedCallback(): void {
- super.connectedCallback();
- this._attachObserver();
- if (this.hasUpdated) {
- this.loadMap();
- }
- }
-
- public disconnectedCallback(): void {
- super.disconnectedCallback();
-
- if (this._leafletMap) {
- this._leafletMap.remove();
- this._leafletMap = undefined;
- this.Leaflet = undefined;
- }
-
- if (this._resizeObserver) {
- this._resizeObserver.unobserve(this._mapEl);
- }
- }
-
protected render(): TemplateResult {
if (!this._config) {
return html``;
@@ -196,22 +138,29 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return html`
`;
}
- protected shouldUpdate(changedProps) {
+ protected shouldUpdate(changedProps: PropertyValues) {
if (!changedProps.has("hass") || changedProps.size > 1) {
return true;
}
@@ -228,7 +177,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
// Check if any state has changed
for (const entity of this._configEntities) {
- if (oldHass.states[entity.entity] !== this.hass!.states[entity.entity]) {
+ if (oldHass.states[entity] !== this.hass!.states[entity]) {
return true;
}
}
@@ -238,17 +187,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
- if (this.isConnected) {
- this.loadMap();
- }
const root = this.shadowRoot!.getElementById("root");
if (!this._config || this.isPanel || !root) {
return;
}
- this._attachObserver();
-
if (!this._config.aspect_ratio) {
root.style.paddingBottom = "100%";
return;
@@ -263,172 +207,86 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
protected updated(changedProps: PropertyValues): void {
- if (changedProps.has("hass") || changedProps.has("_history")) {
- this._drawEntities();
- this._fitMap();
- }
- if (changedProps.has("hass")) {
- const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
- if (oldHass && oldHass.themes.darkMode !== this.hass.themes.darkMode) {
- this._replaceTileLayer();
- }
- }
- if (
- changedProps.has("_config") &&
- changedProps.get("_config") !== undefined
- ) {
- this.updateMap(changedProps.get("_config") as MapCardConfig);
- }
-
if (this._config?.hours_to_show && this._configEntities?.length) {
- const minute = 60000;
if (changedProps.has("_config")) {
this._getHistory();
- } else if (Date.now() - this._date!.getTime() >= minute) {
+ } else if (Date.now() - this._date!.getTime() >= MINUTE) {
this._getHistory();
}
}
}
- private get _mapEl(): HTMLDivElement {
- return this.shadowRoot!.getElementById("map") as HTMLDivElement;
+ private _fitMap() {
+ this._map?.fitMap();
}
- private async loadMap(): Promise {
- [this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
- this._mapEl,
- this._config!.dark_mode ?? this.hass.themes.darkMode
- );
- this._drawEntities();
- this._leafletMap.invalidateSize();
- this._fitMap();
- }
-
- private _replaceTileLayer() {
- const map = this._leafletMap;
- const config = this._config;
- const Leaflet = this.Leaflet;
- if (!map || !config || !Leaflet || !this._tileLayer) {
- return;
- }
- this._tileLayer = replaceTileLayer(
- Leaflet,
- map,
- this._tileLayer,
- this._config!.dark_mode ?? this.hass.themes.darkMode
- );
- }
-
- private updateMap(oldConfig: MapCardConfig): void {
- const map = this._leafletMap;
- const config = this._config;
- const Leaflet = this.Leaflet;
- if (!map || !config || !Leaflet || !this._tileLayer) {
- return;
- }
- if (this._config!.dark_mode !== oldConfig.dark_mode) {
- this._replaceTileLayer();
- }
- if (
- config.entities !== oldConfig.entities ||
- config.geo_location_sources !== oldConfig.geo_location_sources
- ) {
- this._drawEntities();
- }
- map.invalidateSize();
- this._fitMap();
- }
-
- private _fitMap(): void {
- if (!this._leafletMap || !this.Leaflet || !this._config || !this.hass) {
- return;
- }
- const zoom = this._config.default_zoom;
- if (this._mapItems.length === 0) {
- this._leafletMap.setView(
- new this.Leaflet.LatLng(
- this.hass.config.latitude,
- this.hass.config.longitude
- ),
- zoom || 14
- );
- return;
- }
-
- const bounds = this.Leaflet.featureGroup(this._mapItems).getBounds();
- this._leafletMap.fitBounds(bounds.pad(0.5));
-
- if (zoom && this._leafletMap.getZoom() > zoom) {
- this._leafletMap.setZoom(zoom);
- }
- }
-
- private _getColor(entityId: string) {
- let color;
- if (this._colorDict[entityId]) {
- color = this._colorDict[entityId];
- } else {
- color = this._colors[this._colorIndex];
- this._colorIndex = (this._colorIndex + 1) % this._colors.length;
- this._colorDict[entityId] = color;
+ private _getColor(entityId: string): string {
+ let color = this._colorDict[entityId];
+ if (color) {
+ return color;
}
+ color = COLORS[this._colorIndex % COLORS.length];
+ this._colorIndex++;
+ this._colorDict[entityId] = color;
return color;
}
- private _drawEntities(): void {
- const hass = this.hass;
- const map = this._leafletMap;
- const config = this._config;
- const Leaflet = this.Leaflet;
- if (!hass || !map || !config || !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 = []);
-
- if (this._mapPaths) {
- this._mapPaths.forEach((marker) => marker.remove());
- }
- const mapPaths: Layer[] = (this._mapPaths = []);
-
- const allEntities = this._configEntities!.concat();
-
- // Calculate visible geo location sources
- if (config.geo_location_sources) {
- const includesAll = config.geo_location_sources.includes("all");
- for (const entityId of Object.keys(hass.states)) {
- const stateObj = hass.states[entityId];
- if (
- computeDomain(entityId) === "geo_location" &&
- (includesAll ||
- config.geo_location_sources.includes(stateObj.attributes.source))
- ) {
- allEntities.push({ entity: entityId });
- }
+ private _getEntities = memoizeOne(
+ (
+ states: HassEntities,
+ config: MapCardConfig,
+ configEntities?: string[]
+ ) => {
+ if (!states || !config) {
+ return undefined;
}
- }
- // DRAW history
- if (this._config!.hours_to_show && this._history) {
- for (const entityStates of this._history) {
+ let entities = configEntities || [];
+
+ if (config.geo_location_sources) {
+ const geoEntities: string[] = [];
+ // Calculate visible geo location sources
+ const includesAll = config.geo_location_sources.includes("all");
+ for (const stateObj of Object.values(states)) {
+ if (
+ computeDomain(stateObj.entity_id) === "geo_location" &&
+ (includesAll ||
+ config.geo_location_sources.includes(stateObj.attributes.source))
+ ) {
+ geoEntities.push(stateObj.entity_id);
+ }
+ }
+
+ entities = [...entities, ...geoEntities];
+ }
+
+ return entities.map((entity) => ({
+ entity_id: entity,
+ color: this._getColor(entity),
+ }));
+ }
+ );
+
+ private _getHistoryPaths = memoizeOne(
+ (
+ config: MapCardConfig,
+ history?: HassEntity[][]
+ ): HaMapPaths[] | undefined => {
+ if (!config.hours_to_show || !history) {
+ return undefined;
+ }
+
+ const paths: HaMapPaths[] = [];
+
+ for (const entityStates of history) {
if (entityStates?.length <= 1) {
continue;
}
- const entityId = entityStates[0].entity_id;
-
// filter location data from states and remove all invalid locations
- const path = entityStates.reduce(
- (accumulator: LatLngTuple[], state) => {
- const latitude = state.attributes.latitude;
- const longitude = state.attributes.longitude;
+ const points = entityStates.reduce(
+ (accumulator: LatLngTuple[], entityState) => {
+ const latitude = entityState.attributes.latitude;
+ const longitude = entityState.attributes.longitude;
if (latitude && longitude) {
accumulator.push([latitude, longitude] as LatLngTuple);
}
@@ -437,162 +295,15 @@ class HuiMapCard extends LitElement implements LovelaceCard {
[]
) as LatLngTuple[];
- // DRAW HISTORY
- for (
- let markerIndex = 0;
- markerIndex < path.length - 1;
- markerIndex++
- ) {
- const opacityStep = 0.8 / (path.length - 2);
- const opacity = 0.2 + markerIndex * opacityStep;
-
- // DRAW history path dots
- mapPaths.push(
- Leaflet.circleMarker(path[markerIndex], {
- radius: 3,
- color: this._getColor(entityId),
- opacity,
- interactive: false,
- })
- );
-
- // DRAW history path lines
- const line = [path[markerIndex], path[markerIndex + 1]];
- mapPaths.push(
- Leaflet.polyline(line, {
- color: this._getColor(entityId),
- opacity,
- interactive: false,
- })
- );
- }
+ paths.push({
+ points,
+ color: this._getColor(entityStates[0].entity_id),
+ gradualOpacity: 0.8,
+ });
}
+ return paths;
}
-
- // DRAW entities
- for (const entity of allEntities) {
- const entityId = entity.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._config!.dark_mode
- ? "dark"
- : this._config!.dark_mode === false
- ? "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: `
-
- `,
- 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: this._getColor(entityId),
- radius: gpsAccuracy,
- })
- );
- }
- }
-
- this._mapItems.forEach((marker) => map.addLayer(marker));
- this._mapZones.forEach((marker) => map.addLayer(marker));
- this._mapPaths.forEach((marker) => map.addLayer(marker));
- }
-
- private async _attachObserver(): Promise {
- // Observe changes to map size and invalidate to prevent broken rendering
-
- if (!this._resizeObserver) {
- await installResizeObserver();
- this._resizeObserver = new ResizeObserver(this._debouncedResizeListener);
- }
- this._resizeObserver.observe(this);
- }
+ );
private async _getHistory(): Promise {
this._date = new Date();
@@ -601,9 +312,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return;
}
- const entityIds = this._configEntities!.map((entity) => entity.entity).join(
- ","
- );
+ const entityIds = this._configEntities!.join(",");
const endTime = new Date();
const startTime = new Date();
startTime.setHours(endTime.getHours() - this._config!.hours_to_show!);
@@ -624,7 +333,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
if (stateHistory.length < 1) {
return;
}
-
this._history = stateHistory;
}
@@ -636,13 +344,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
this._history = undefined;
} else {
// remove unused entities
- const configEntityIds = this._configEntities?.map(
- (configEntity) => configEntity.entity
- );
this._history = this._history!.reduce(
(accumulator: HassEntity[][], entityStates) => {
const entityId = entityStates[0].entity_id;
- if (configEntityIds?.includes(entityId)) {
+ if (this._configEntities?.includes(entityId)) {
accumulator.push(entityStates);
}
return accumulator;
@@ -660,7 +365,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
height: 100%;
}
- #map {
+ ha-map {
z-index: 0;
border: none;
position: absolute;
@@ -671,7 +376,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
background: inherit;
}
- ha-icon-button {
+ mwc-icon-button {
position: absolute;
top: 75px;
left: 3px;
@@ -685,14 +390,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
:host([ispanel]) #root {
height: 100%;
}
-
- .dark {
- color: #ffffff;
- }
-
- .light {
- color: #000000;
- }
`;
}
}
diff --git a/src/panels/lovelace/components/hui-input-list-editor.ts b/src/panels/lovelace/components/hui-input-list-editor.ts
index ae64dea7b7..ea92b47553 100644
--- a/src/panels/lovelace/components/hui-input-list-editor.ts
+++ b/src/panels/lovelace/components/hui-input-list-editor.ts
@@ -29,6 +29,7 @@ export class HuiInputListEditor extends LitElement {
.index=${index}
@value-changed=${this._valueChanged}
@blur=${this._consolidateEntries}
+ @keydown=${this._handleKeyDown}
>
-
-
-
- [[entityName]]
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: {
- type: Object,
- },
-
- entityId: {
- type: String,
- value: "",
- },
-
- entityName: {
- type: String,
- value: null,
- },
-
- entityPicture: {
- type: String,
- value: null,
- },
-
- entityColor: {
- type: String,
- value: null,
- },
- };
- }
-
- ready() {
- super.ready();
- this.addEventListener("click", (ev) => this.badgeTap(ev));
- }
-
- badgeTap(ev) {
- ev.stopPropagation();
- if (this.entityId) {
- this.fire("hass-more-info", { entityId: this.entityId });
- }
- }
-}
-
-customElements.define("ha-entity-marker", HaEntityMarker);
diff --git a/src/panels/map/ha-panel-map.js b/src/panels/map/ha-panel-map.js
deleted file mode 100644
index 108c984765..0000000000
--- a/src/panels/map/ha-panel-map.js
+++ /dev/null
@@ -1,263 +0,0 @@
-import "@polymer/app-layout/app-toolbar/app-toolbar";
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-/* eslint-plugin-disable lit */
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-import {
- replaceTileLayer,
- setupLeafletMap,
-} from "../../common/dom/setup-leaflet-map";
-import { computeStateDomain } from "../../common/entity/compute_state_domain";
-import { computeStateName } from "../../common/entity/compute_state_name";
-import { navigate } from "../../common/navigate";
-import "../../components/ha-icon";
-import "../../components/ha-menu-button";
-import { defaultRadiusColor } from "../../data/zone";
-import "../../layouts/ha-app-layout";
-import LocalizeMixin from "../../mixins/localize-mixin";
-import "../../styles/polymer-ha-style";
-import "./ha-entity-marker";
-
-/*
- * @appliesMixin LocalizeMixin
- */
-class HaPanelMap extends LocalizeMixin(PolymerElement) {
- static get template() {
- return html`
-
-
-
-
-
-
- [[localize('panel.map')]]
-
-
-
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: {
- type: Object,
- observer: "drawEntities",
- },
- narrow: Boolean,
- };
- }
-
- connectedCallback() {
- super.connectedCallback();
- this.loadMap();
- }
-
- async loadMap() {
- this._darkMode = this.hass.themes.darkMode;
- [this._map, this.Leaflet, this._tileLayer] = await setupLeafletMap(
- this.$.map,
- this._darkMode
- );
- this.drawEntities(this.hass);
- this._map.invalidateSize();
- this.fitMap();
- }
-
- disconnectedCallback() {
- if (this._map) {
- this._map.remove();
- }
- }
-
- computeShowEditZone(hass) {
- return !__DEMO__ && hass.user.is_admin;
- }
-
- openZonesEditor() {
- navigate("/config/zone");
- }
-
- fitMap() {
- let bounds;
-
- if (this._mapItems.length === 0) {
- this._map.setView(
- new this.Leaflet.LatLng(
- this.hass.config.latitude,
- this.hass.config.longitude
- ),
- 14
- );
- } else {
- bounds = new this.Leaflet.latLngBounds(
- this._mapItems.map((item) => item.getLatLng())
- );
- this._map.fitBounds(bounds.pad(0.5));
- }
- }
-
- drawEntities(hass) {
- /* eslint-disable vars-on-top */
- const map = this._map;
- if (!map) return;
-
- if (this._darkMode !== this.hass.themes.darkMode) {
- this._darkMode = this.hass.themes.darkMode;
- this._tileLayer = replaceTileLayer(
- this.Leaflet,
- map,
- this._tileLayer,
- this.hass.themes.darkMode
- );
- }
-
- if (this._mapItems) {
- this._mapItems.forEach(function (marker) {
- marker.remove();
- });
- }
- const mapItems = (this._mapItems = []);
-
- if (this._mapZones) {
- this._mapZones.forEach(function (marker) {
- marker.remove();
- });
- }
- const mapZones = (this._mapZones = []);
-
- Object.keys(hass.states).forEach((entityId) => {
- const entity = hass.states[entityId];
-
- if (
- entity.state === "home" ||
- !("latitude" in entity.attributes) ||
- !("longitude" in entity.attributes)
- ) {
- return;
- }
-
- const title = computeStateName(entity);
- let icon;
-
- if (computeStateDomain(entity) === "zone") {
- // DRAW ZONE
- if (entity.attributes.passive) return;
-
- // create icon
- let iconHTML = "";
- if (entity.attributes.icon) {
- const el = document.createElement("ha-icon");
- el.setAttribute("icon", entity.attributes.icon);
- iconHTML = el.outerHTML;
- } else {
- const el = document.createElement("span");
- el.innerHTML = title;
- iconHTML = el.outerHTML;
- }
-
- icon = this.Leaflet.divIcon({
- html: iconHTML,
- iconSize: [24, 24],
- className: "icon",
- });
-
- // create marker with the icon
- mapZones.push(
- this.Leaflet.marker(
- [entity.attributes.latitude, entity.attributes.longitude],
- {
- icon: icon,
- interactive: false,
- title: title,
- }
- ).addTo(map)
- );
-
- // create circle around it
- mapZones.push(
- this.Leaflet.circle(
- [entity.attributes.latitude, entity.attributes.longitude],
- {
- interactive: false,
- color: defaultRadiusColor,
- radius: entity.attributes.radius,
- }
- ).addTo(map)
- );
-
- return;
- }
-
- // DRAW ENTITY
- // create icon
- const entityPicture = entity.attributes.entity_picture || "";
- const entityName = title
- .split(" ")
- .map(function (part) {
- return part.substr(0, 1);
- })
- .join("");
- /* 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. */
- icon = this.Leaflet.divIcon({
- html:
- "",
- iconSize: [45, 45],
- className: "",
- });
-
- // create market with the icon
- mapItems.push(
- this.Leaflet.marker(
- [entity.attributes.latitude, entity.attributes.longitude],
- {
- icon: icon,
- title: computeStateName(entity),
- }
- ).addTo(map)
- );
-
- // create circle around if entity has accuracy
- if (entity.attributes.gps_accuracy) {
- mapItems.push(
- this.Leaflet.circle(
- [entity.attributes.latitude, entity.attributes.longitude],
- {
- interactive: false,
- color: "#0288D1",
- radius: entity.attributes.gps_accuracy,
- }
- ).addTo(map)
- );
- }
- });
- }
-}
-
-customElements.define("ha-panel-map", HaPanelMap);
diff --git a/src/panels/map/ha-panel-map.ts b/src/panels/map/ha-panel-map.ts
new file mode 100644
index 0000000000..d391668c9d
--- /dev/null
+++ b/src/panels/map/ha-panel-map.ts
@@ -0,0 +1,103 @@
+import { mdiPencil } from "@mdi/js";
+import "@material/mwc-icon-button";
+import "@polymer/app-layout/app-toolbar/app-toolbar";
+import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
+import { property } from "lit/decorators";
+import { computeStateDomain } from "../../common/entity/compute_state_domain";
+import { navigate } from "../../common/navigate";
+import "../../components/ha-svg-icon";
+import "../../components/ha-menu-button";
+import "../../layouts/ha-app-layout";
+import { HomeAssistant } from "../../types";
+import "../../components/map/ha-map";
+import { haStyle } from "../../resources/styles";
+
+class HaPanelMap extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ type: Boolean }) public narrow!: boolean;
+
+ private _entities: string[] = [];
+
+ protected render() {
+ return html`
+
+
+
+
+ ${this.hass.localize("panel.map")}
+ ${!__DEMO__ && this.hass.user?.is_admin
+ ? html``
+ : ""}
+
+
+
+
+ `;
+ }
+
+ private _openZonesEditor() {
+ navigate("/config/zone");
+ }
+
+ public willUpdate(changedProps: PropertyValues) {
+ super.willUpdate(changedProps);
+ if (!changedProps.has("hass")) {
+ return;
+ }
+ const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
+ this._getStates(oldHass);
+ }
+
+ private _getStates(oldHass?: HomeAssistant) {
+ let changed = false;
+ const personSources = new Set();
+ const locationEntities: string[] = [];
+ Object.values(this.hass!.states).forEach((entity) => {
+ if (
+ entity.state === "home" ||
+ !("latitude" in entity.attributes) ||
+ !("longitude" in entity.attributes)
+ ) {
+ return;
+ }
+ locationEntities.push(entity.entity_id);
+ if (computeStateDomain(entity) === "person" && entity.attributes.source) {
+ personSources.add(entity.attributes.source);
+ }
+ if (oldHass?.states[entity.entity_id] !== entity) {
+ changed = true;
+ }
+ });
+
+ if (changed) {
+ this._entities = locationEntities.filter(
+ (entity) => !personSources.has(entity)
+ );
+ }
+ }
+
+ static get styles(): CSSResultGroup {
+ return [
+ haStyle,
+ css`
+ ha-map {
+ height: calc(100vh - var(--header-height));
+ }
+ `,
+ ];
+ }
+}
+
+customElements.define("ha-panel-map", HaPanelMap);
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-panel-map": HaPanelMap;
+ }
+}
diff --git a/src/panels/profile/ha-push-notifications-row.js b/src/panels/profile/ha-push-notifications-row.js
index 429173ffd2..69f2e54246 100644
--- a/src/panels/profile/ha-push-notifications-row.js
+++ b/src/panels/profile/ha-push-notifications-row.js
@@ -1,5 +1,4 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
-import "@polymer/iron-label/iron-label";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts
index f3e9dcb757..82439c105e 100644
--- a/src/resources/ha-style.ts
+++ b/src/resources/ha-style.ts
@@ -29,10 +29,10 @@ documentContainer.innerHTML = `
--disabled-text-color: #bdbdbd;
/* main interface colors */
- --primary-color: #03a9f4;
+ --primary-color: ${DEFAULT_PRIMARY_COLOR};
--dark-primary-color: #0288d1;
--light-primary-color: #b3e5fC;
- --accent-color: #ff9800;
+ --accent-color: ${DEFAULT_ACCENT_COLOR};
--divider-color: rgba(0, 0, 0, .12);
--scrollbar-thumb-color: rgb(194, 194, 194);
diff --git a/yarn.lock b/yarn.lock
index 8fd9b8db50..eae3bc00de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2104,13 +2104,6 @@
"@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
-"@polymer/iron-image@^3.0.1":
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/@polymer/iron-image/-/iron-image-3.0.2.tgz#425ee6269634e024dbea726a91a61724ae4402b6"
- integrity sha512-VyYtnewGozDb5sUeoLR1OvKzlt5WAL6b8Od7fPpio5oYL+9t061/nTV8+ZMrpMgF2WgB0zqM/3K53o3pbK5v8Q==
- dependencies:
- "@polymer/polymer" "^3.0.0"
-
"@polymer/iron-input@^3.0.0-pre.26", "@polymer/iron-input@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-input/-/iron-input-3.0.1.tgz#dc866a25107f9b38d9ca4512dd9a3e51b78b4915"
@@ -2120,13 +2113,6 @@
"@polymer/iron-validatable-behavior" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
-"@polymer/iron-label@^3.0.1":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/@polymer/iron-label/-/iron-label-3.0.1.tgz#170247dc50d63f4e2ae6c80711dbf5b64fa953d6"
- integrity sha512-MkIZ1WfOy10pnIxRwTVPfsoDZYlqMkUp0hmimMj0pGRHmrc9n5phuJUY1pC+S7WoKP1/98iH2qnXQukPGTzoVA==
- dependencies:
- "@polymer/polymer" "^3.0.0"
-
"@polymer/iron-list@^3.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@polymer/iron-list/-/iron-list-3.0.2.tgz#9e6b80e503328dc29217dbe26f94faa47adb4124"