Add map strategy (#20067)

This commit is contained in:
Paul Bottein 2024-03-14 14:22:24 +01:00 committed by GitHub
parent 56a23c5c3d
commit 31797c55df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 205 additions and 40 deletions

View File

@ -1,20 +1,32 @@
import "@material/mwc-list/mwc-list";
import { mdiPencilOutline, mdiShape } from "@mdi/js";
import { mdiMap, mdiPencilOutline, mdiShape } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-icon-next";
import "../../../components/ha-list-item";
import { LovelaceRawConfig } from "../../../data/lovelace/config/types";
import { HassDialog } from "../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import { NewDashboardDialogParams } from "./show-dialog-new-dashboard";
import { LovelaceRawConfig } from "../../../data/lovelace/config/types";
const EMPTY_CONFIG: LovelaceRawConfig = { views: [{ title: "Home" }] };
type Strategy = {
type: string;
iconPath: string;
};
const STRATEGIES = [
{
type: "map",
iconPath: mdiMap,
},
] as const satisfies Strategy[];
@customElement("ha-dialog-new-dashboard")
class DialogNewDashboard extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -100,16 +112,55 @@ class DialogNewDashboard extends LitElement implements HassDialog {
>
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
${STRATEGIES.map(
(strategy) => html`
<ha-list-item
hasmeta
twoline
graphic="icon"
.strategy=${strategy.type}
@request-selected=${this._selected}
>
<ha-svg-icon
slot="graphic"
.path=${strategy.iconPath}
></ha-svg-icon>
${this.hass.localize(
`ui.panel.config.lovelace.dashboards.dialog_new.strategy.${strategy.type}.title`
)}
<span slot="secondary">
${this.hass.localize(
`ui.panel.config.lovelace.dashboards.dialog_new.strategy.${strategy.type}.description`
)}
</span>
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
`
)}
</mwc-list>
</ha-dialog>
`;
}
private _generateStrategyConfig(strategy: string) {
return {
strategy: {
type: strategy,
},
};
}
private async _selected(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
const config = (ev.currentTarget! as any).config;
const target = ev.currentTarget as any;
const config =
target.config ||
(target.strategy && this._generateStrategyConfig(target.strategy)) ||
null;
this._params?.selectConfig(config);
this.closeDialog();
}

View File

@ -6,22 +6,22 @@ import {
} from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-button";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-icon-button";
import { LovelaceStrategyConfig } from "../../../../data/lovelace/config/strategy";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import "../../editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor";
import type { HuiDashboardStrategyElementEditor } from "../../editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor";
import { ConfigChangedEvent } from "../../editor/hui-element-editor";
import { GUIModeChangedEvent } from "../../editor/types";
import { cleanLegacyStrategyConfig } from "../legacy-strategy";
import { HASSDomEvent, fireEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/ha-button";
import "../../../../../components/ha-button-menu";
import "../../../../../components/ha-dialog";
import "../../../../../components/ha-dialog-header";
import "../../../../../components/ha-icon-button";
import { LovelaceStrategyConfig } from "../../../../../data/lovelace/config/strategy";
import { haStyleDialog } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import { showSaveSuccessToast } from "../../../../../util/toast-saved-success";
import "../hui-dashboard-strategy-element-editor";
import type { HuiDashboardStrategyElementEditor } from "../hui-dashboard-strategy-element-editor";
import { ConfigChangedEvent } from "../../hui-element-editor";
import { GUIModeChangedEvent } from "../../types";
import { cleanLegacyStrategyConfig } from "../../../strategies/legacy-strategy";
import type { DashboardStrategyEditorDialogParams } from "./show-dialog-dashboard-strategy-editor";
@customElement("dialog-dashboard-strategy-editor")

View File

@ -1,5 +1,5 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import { LovelaceDashboardStrategyConfig } from "../../../../data/lovelace/config/types";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { LovelaceDashboardStrategyConfig } from "../../../../../data/lovelace/config/types";
export interface DashboardStrategyEditorDialogParams {
config: LovelaceDashboardStrategyConfig;

View File

@ -7,7 +7,7 @@ import type {
SchemaUnion,
} from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import { OriginalStatesDashboardStrategyConfig } from "../../strategies/original-states-dashboard-strategy";
import { OriginalStatesDashboardStrategyConfig } from "../../strategies/original-states/original-states-dashboard-strategy";
import { LovelaceStrategyEditor } from "../../strategies/types";
const SCHEMA = [
@ -38,7 +38,7 @@ const SCHEMA = [
] as const satisfies readonly HaFormSchema[];
@customElement("hui-original-states-dashboard-strategy-editor")
export class HuiOriginalStatesDashboarStrategyEditor
export class HuiOriginalStatesDashboardStrategyEditor
extends LitElement
implements LovelaceStrategyEditor
{
@ -88,6 +88,6 @@ export class HuiOriginalStatesDashboarStrategyEditor
declare global {
interface HTMLElementTagNameMap {
"hui-original-states-dashboard-strategy-editor": HuiOriginalStatesDashboarStrategyEditor;
"hui-original-states-dashboard-strategy-editor": HuiOriginalStatesDashboardStrategyEditor;
}
}

View File

@ -63,7 +63,7 @@ import { documentationUrl } from "../../util/documentation-url";
import { swapView } from "./editor/config-util";
import { showEditLovelaceDialog } from "./editor/lovelace-editor/show-edit-lovelace-dialog";
import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
import { showDashboardStrategyEditorDialog } from "./strategies/device-registry-detail/show-dialog-dashboard-strategy-editor";
import { showDashboardStrategyEditorDialog } from "./editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor";
import type { Lovelace } from "./types";
import "./views/hui-view";
import type { HUIView } from "./views/hui-view";
@ -75,6 +75,7 @@ import {
import { showSaveDialog } from "./editor/show-save-config-dialog";
import { isLegacyStrategyConfig } from "./strategies/legacy-strategy";
import { LocalizeKeys } from "../../common/translations/localize";
import { getLovelaceStrategy } from "./strategies/get-strategy";
@customElement("hui-root")
class HUIRoot extends LitElement {
@ -709,7 +710,7 @@ class HUIRoot extends LitElement {
this._enableEditMode();
}
private _enableEditMode(): void {
private async _enableEditMode() {
if (this._yamlMode) {
showAlertDialog(this, {
text: this.hass!.localize("ui.panel.lovelace.editor.yaml_unsupported"),
@ -720,6 +721,18 @@ class HUIRoot extends LitElement {
isStrategyDashboard(this.lovelace!.rawConfig) &&
!isLegacyStrategyConfig(this.lovelace!.rawConfig.strategy)
) {
const strategyClass = await getLovelaceStrategy(
"dashboard",
this.lovelace!.rawConfig.strategy.type
).catch((_err) => undefined);
if (strategyClass?.noEditor) {
showSaveDialog(this, {
lovelace: this.lovelace!,
mode: "storage",
narrow: this.narrow!,
});
return;
}
showDashboardStrategyEditorDialog(this, {
config: this.lovelace!.rawConfig,
saveConfig: this.lovelace!.saveConfig,

View File

@ -22,11 +22,15 @@ const CUSTOM_PREFIX = "custom:";
const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
dashboard: {
"original-states": () => import("./original-states-dashboard-strategy"),
"original-states": () =>
import("./original-states/original-states-dashboard-strategy"),
map: () => import("./map/map-dashboard-strategy"),
},
view: {
"original-states": () => import("./original-states-view-strategy"),
"original-states": () =>
import("./original-states/original-states-view-strategy"),
energy: () => import("../../energy/strategies/energy-view-strategy"),
map: () => import("./map/map-view-strategy"),
},
section: {},
};

View File

@ -0,0 +1,32 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { LovelaceConfig } from "../../../../data/lovelace/config/types";
import { HomeAssistant } from "../../../../types";
import { MapViewStrategyConfig } from "./map-view-strategy";
export type MapDashboardStrategyConfig = MapViewStrategyConfig;
@customElement("map-dashboard-strategy")
export class MapDashboardStrategy extends ReactiveElement {
static async generate(
config: MapDashboardStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceConfig> {
return {
title: hass.localize("panel.map"),
views: [
{
strategy: config,
},
],
};
}
static noEditor = true;
}
declare global {
interface HTMLElementTagNameMap {
"map-dashboard-strategy": MapDashboardStrategy;
}
}

View File

@ -0,0 +1,58 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
import { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import { HomeAssistant } from "../../../../types";
import { MapCardConfig } from "../../cards/types";
export type MapViewStrategyConfig = {
type: "map";
};
const getMapEntities = (hass: HomeAssistant) => {
const personSources = new Set<string>();
const locationEntities: string[] = [];
Object.values(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);
}
});
return locationEntities.filter((entity) => !personSources.has(entity));
};
@customElement("map-view-strategy")
export class MapViewStrategy extends ReactiveElement {
static async generate(
_config: MapViewStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const entities = getMapEntities(hass);
return {
type: "panel",
title: hass.localize("panel.map"),
icon: "mdi:map",
cards: [
{
type: "map",
auto_fit: true,
entities: entities,
} as MapCardConfig,
],
};
}
}
declare global {
interface HTMLElementTagNameMap {
"map-view-strategy": MapViewStrategy;
}
}

View File

@ -1,9 +1,9 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { LovelaceConfig } from "../../../data/lovelace/config/types";
import { HomeAssistant } from "../../../types";
import { LovelaceConfig } from "../../../../data/lovelace/config/types";
import { HomeAssistant } from "../../../../types";
import { OriginalStatesViewStrategyConfig } from "./original-states-view-strategy";
import { LovelaceStrategyEditor } from "./types";
import { LovelaceStrategyEditor } from "../types";
export type OriginalStatesDashboardStrategyConfig =
OriginalStatesViewStrategyConfig;
@ -26,7 +26,7 @@ export class OriginalStatesDashboardStrategy extends ReactiveElement {
public static async getConfigElement(): Promise<LovelaceStrategyEditor> {
await import(
"../editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor"
"../../editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor"
);
return document.createElement(
"hui-original-states-dashboard-strategy-editor"

View File

@ -1,12 +1,12 @@
import { STATE_NOT_RUNNING } from "home-assistant-js-websocket";
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import type { AreaFilterValue } from "../../../components/ha-area-filter";
import { getEnergyPreferences } from "../../../data/energy";
import { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { HomeAssistant } from "../../../types";
import { generateDefaultViewConfig } from "../common/generate-lovelace-config";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import type { AreaFilterValue } from "../../../../components/ha-area-filter";
import { getEnergyPreferences } from "../../../../data/energy";
import { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import { HomeAssistant } from "../../../../types";
import { generateDefaultViewConfig } from "../../common/generate-lovelace-config";
export type OriginalStatesViewStrategyConfig = {
type: "original-states";

View File

@ -8,6 +8,7 @@ import { LovelaceGenericElementEditor } from "../types";
export type LovelaceStrategy<T = any> = {
generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise<T>;
getConfigElement?: () => LovelaceStrategyEditor;
noEditor?: boolean;
};
export interface LovelaceDashboardStrategy

View File

@ -2207,7 +2207,13 @@
"create_empty": "New dashboard from scratch",
"create_empty_description": "Start with an empty dashboard from scratch",
"default": "Default dashboard",
"default_description": "Display your devices grouped by area"
"default_description": "Display your devices grouped by area",
"strategy": {
"map": {
"title": "[%key:panel::map%]",
"description": "Display people and your devices on a map"
}
}
},
"picker": {
"headers": {