diff --git a/src/panels/config/dashboard/dialog-new-dashboard.ts b/src/panels/config/dashboard/dialog-new-dashboard.ts index 89a74d0a50..10b65cee0c 100644 --- a/src/panels/config/dashboard/dialog-new-dashboard.ts +++ b/src/panels/config/dashboard/dialog-new-dashboard.ts @@ -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 { > + ${STRATEGIES.map( + (strategy) => html` + + + ${this.hass.localize( + `ui.panel.config.lovelace.dashboards.dialog_new.strategy.${strategy.type}.title` + )} + + ${this.hass.localize( + `ui.panel.config.lovelace.dashboards.dialog_new.strategy.${strategy.type}.description` + )} + + + + ` + )} `; } + 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(); } diff --git a/src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts b/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts similarity index 83% rename from src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts rename to src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts index e114547246..0a826b11b0 100644 --- a/src/panels/lovelace/strategies/device-registry-detail/dialog-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts @@ -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") diff --git a/src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts b/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor.ts similarity index 80% rename from src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts rename to src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor.ts index 9ad34cc51d..d7571464f9 100644 --- a/src/panels/lovelace/strategies/device-registry-detail/show-dialog-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor.ts @@ -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; diff --git a/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts b/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts index 48c18f8424..c5abdd4409 100644 --- a/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/editor/dashboard-strategy-editor/hui-original-states-dashboard-strategy-editor.ts @@ -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; } } diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 8257f0e2cb..b0115082bb 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -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, diff --git a/src/panels/lovelace/strategies/get-strategy.ts b/src/panels/lovelace/strategies/get-strategy.ts index 927089dedd..5613d23111 100644 --- a/src/panels/lovelace/strategies/get-strategy.ts +++ b/src/panels/lovelace/strategies/get-strategy.ts @@ -22,11 +22,15 @@ const CUSTOM_PREFIX = "custom:"; const STRATEGIES: Record> = { 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: {}, }; diff --git a/src/panels/lovelace/strategies/map/map-dashboard-strategy.ts b/src/panels/lovelace/strategies/map/map-dashboard-strategy.ts new file mode 100644 index 0000000000..91c217ce08 --- /dev/null +++ b/src/panels/lovelace/strategies/map/map-dashboard-strategy.ts @@ -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 { + return { + title: hass.localize("panel.map"), + views: [ + { + strategy: config, + }, + ], + }; + } + + static noEditor = true; +} + +declare global { + interface HTMLElementTagNameMap { + "map-dashboard-strategy": MapDashboardStrategy; + } +} diff --git a/src/panels/lovelace/strategies/map/map-view-strategy.ts b/src/panels/lovelace/strategies/map/map-view-strategy.ts new file mode 100644 index 0000000000..e9776fa030 --- /dev/null +++ b/src/panels/lovelace/strategies/map/map-view-strategy.ts @@ -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(); + 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 { + 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; + } +} diff --git a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts b/src/panels/lovelace/strategies/original-states/original-states-dashboard-strategy.ts similarity index 78% rename from src/panels/lovelace/strategies/original-states-dashboard-strategy.ts rename to src/panels/lovelace/strategies/original-states/original-states-dashboard-strategy.ts index 0c7ae3c14f..3f7faae9f4 100644 --- a/src/panels/lovelace/strategies/original-states-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/original-states/original-states-dashboard-strategy.ts @@ -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 { 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" diff --git a/src/panels/lovelace/strategies/original-states-view-strategy.ts b/src/panels/lovelace/strategies/original-states/original-states-view-strategy.ts similarity index 82% rename from src/panels/lovelace/strategies/original-states-view-strategy.ts rename to src/panels/lovelace/strategies/original-states/original-states-view-strategy.ts index 386f953fcf..a9c64793ce 100644 --- a/src/panels/lovelace/strategies/original-states-view-strategy.ts +++ b/src/panels/lovelace/strategies/original-states/original-states-view-strategy.ts @@ -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"; diff --git a/src/panels/lovelace/strategies/types.ts b/src/panels/lovelace/strategies/types.ts index ae1be1bbca..ac3a07de37 100644 --- a/src/panels/lovelace/strategies/types.ts +++ b/src/panels/lovelace/strategies/types.ts @@ -8,6 +8,7 @@ import { LovelaceGenericElementEditor } from "../types"; export type LovelaceStrategy = { generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise; getConfigElement?: () => LovelaceStrategyEditor; + noEditor?: boolean; }; export interface LovelaceDashboardStrategy diff --git a/src/translations/en.json b/src/translations/en.json index fe9874df11..c016bba08a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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": {