diff --git a/src/components/ha-control-button.ts b/src/components/ha-control-button.ts index 61cedc44fd..df56d809f2 100644 --- a/src/components/ha-control-button.ts +++ b/src/components/ha-control-button.ts @@ -72,6 +72,9 @@ export class HaControlButton extends LitElement { color 180ms ease-in-out; color: var(--control-button-icon-color); } + :host([vertical]) .button { + flex-direction: column; + } .button:focus-visible { box-shadow: 0 0 0 2px var(--control-button-focus-color); } diff --git a/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts b/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts index 2f1826db6c..def075a63d 100644 --- a/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts @@ -15,13 +15,10 @@ import "../../../components/ha-control-button-group"; import "../../../components/ha-domain-icon"; import "../../../components/ha-svg-icon"; import type { AreaRegistryEntry } from "../../../data/area_registry"; -import { domainIcon } from "../../../data/icons"; -import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import { computeCssVariable } from "../../../resources/css-variables"; import type { HomeAssistant } from "../../../types"; import type { AreaCardFeatureContext } from "../cards/hui-area-card"; -import type { ButtonCardConfig, TileCardConfig } from "../cards/types"; -import { showDashboardDialog } from "../dialogs/show-dashboard-dialog"; +import { showGroupControlDialog } from "../dialogs/show-group-toggle-dialog"; import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { cardFeatureStyles } from "./common/card-feature-styles"; import type { @@ -177,98 +174,13 @@ class HuiAreaControlsCardFeature ); const entitiesIds = controlEntities[control]; - const entities = entitiesIds - .map((entityId) => this.hass!.states[entityId] as HassEntity | undefined) - .filter((v): v is HassEntity => Boolean(v)); + const domain = AREA_CONTROLS_BUTTONS[control].filter.domain; - const controlButton = AREA_CONTROLS_BUTTONS[control]; - const onIcon = - controlButton.onIcon || - (await domainIcon( - this.hass!, - controlButton.filter.domain, - controlButton.filter.device_class, - "off" - )); - const offIcon = - controlButton.offIcon || - (await domainIcon( - this.hass!, - controlButton.filter.domain, - controlButton.filter.device_class, - "on" - )); - - const sectionConfig: LovelaceSectionConfig = { - type: "grid", - cards: [ - { - type: "heading", - heading: "Actions", - heading_style: "subtitle", - }, - { - type: "button", - icon: offIcon, - icon_height: "24px", - name: "Turn all off", - tap_action: { - action: "perform-action", - target: { - entity_id: entitiesIds, - }, - perform_action: "light.turn_off", - }, - grid_options: { - min_rows: 1, - rows: 1, - columns: 6, - }, - } as ButtonCardConfig, - { - type: "button", - icon: onIcon, - icon_height: "24px", - name: "Turn all on", - tap_action: { - action: "perform-action", - target: { - entity_id: entitiesIds, - }, - perform_action: "light.turn_on", - }, - grid_options: { - min_rows: 1, - rows: 1, - columns: 6, - }, - } as ButtonCardConfig, - { - type: "heading", - heading: "Controls", - heading_style: "subtitle", - }, - ...entities.map((entity) => ({ - type: "tile", - entity: entity.entity_id, - features_position: "inline", - features: [ - { - type: "light-brightness", - }, - ], - })), - ], - }; - - showDashboardDialog(this, { - sections: [sectionConfig], + showGroupControlDialog(this, { title: computeAreaName(this._area!) || "", - subtitle: control, + subtitle: domain, + entityIds: entitiesIds, }); - - // forwardHaptic("light"); - // toggleGroupEntities(this.hass, entities); } private _controlEntities = memoizeOne( diff --git a/src/panels/lovelace/dialogs/hui-dialog-group-toggle.ts b/src/panels/lovelace/dialogs/hui-dialog-group-toggle.ts new file mode 100644 index 0000000000..34cb63ded4 --- /dev/null +++ b/src/panels/lovelace/dialogs/hui-dialog-group-toggle.ts @@ -0,0 +1,220 @@ +import { mdiClose, mdiLightbulb, mdiLightbulbOff } from "@mdi/js"; +import type { HassEntity } from "home-assistant-js-websocket"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { computeGroupEntitiesState } from "../../../common/entity/group_entities"; +import "../../../components/ha-control-button"; +import "../../../components/ha-control-button-group"; +import "../../../components/ha-dialog"; +import "../../../components/ha-dialog-header"; +import "../../../components/ha-domain-icon"; +import "../../../components/ha-header-bar"; +import { forwardHaptic } from "../../../data/haptics"; +import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; +import "../../../dialogs/more-info/components/ha-more-info-state-header"; +import { haStyleDialog } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; +import type { TileCardConfig } from "../cards/types"; +import "../sections/hui-section"; +import type { GroupToggleDialogParams } from "./show-group-toggle-dialog"; + +@customElement("hui-dialog-group-toggle") +class HuiGroupToggleDialog extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _params?: GroupToggleDialogParams; + + public async showDialog(params: GroupToggleDialogParams): Promise { + this._params = params; + } + + public async closeDialog(): Promise { + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + private _sectionConfig = memoizeOne( + (entities: string[]): LovelaceSectionConfig => ({ + type: "grid", + cards: entities.map((entity) => ({ + type: "tile", + entity: entity, + icon_tap_action: { + action: "toggle", + }, + tap_action: { + action: "more-info", + }, + grid_options: { + columns: 12, + }, + })), + }) + ); + + protected render() { + if (!this._params) { + return nothing; + } + + const sectionConfig = this._sectionConfig(this._params.entityIds); + + const entities = this._params.entityIds + .map((entityId) => this.hass!.states[entityId] as HassEntity | undefined) + .filter((v): v is HassEntity => Boolean(v)); + + const mainStateObj = entities[0]; + + const groupState = computeGroupEntitiesState(entities); + const formattedGroupState = this.hass.formatEntityState( + mainStateObj, + groupState + ); + + const domain = computeStateDomain(mainStateObj); + + const deviceClass = mainStateObj.attributes.device_class; + + return html` + + + + ${this._params.title} + ${this._params.subtitle + ? html`${this._params.subtitle}` + : nothing} + +
+ + + + ${domain !== "light" + ? html`` + : html` `} +

${domain === "cover" ? "Open all" : "Turn all on"}

+
+ + ${domain !== "light" + ? html`` + : html` `} +

${domain === "cover" ? "Close all" : "Turn all off"}

+
+
+ +
+
+ `; + } + + private _turnAllOff() { + if (!this._params) { + return; + } + + forwardHaptic("light"); + const domain = computeDomain(this._params.entityIds[0]); + if (domain === "cover") { + this.hass.callService("cover", "close_cover", { + entity_id: this._params.entityIds, + }); + return; + } + this.hass.callService("homeassistant", "turn_off", { + entity_id: this._params.entityIds, + }); + } + + private _turnAllOn() { + if (!this._params) { + return; + } + + forwardHaptic("light"); + const domain = computeDomain(this._params.entityIds[0]); + if (domain === "cover") { + this.hass.callService("cover", "open_cover", { + entity_id: this._params.entityIds, + }); + return; + } + this.hass.callService("homeassistant", "turn_on", { + entity_id: this._params.entityIds, + }); + } + + static styles = [ + haStyleDialog, + css` + ha-dialog { + --dialog-content-padding: 0; + } + .content { + padding: 0 16px 16px 16px; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; + } + ha-control-button-group { + --control-button-group-spacing: 12px; + --control-button-group-thickness: 130px; + margin-bottom: 32px; + } + ha-control-button { + --control-button-border-radius: 16px; + --mdc-icon-size: 24px; + color: #006787; + --control-button-padding: 16px 8px; + --control-button-icon-color: #006787; + --control-button-background-color: #eff9fe; + --control-button-background-opacity: 1; + --control-button-focus-color: #006787; + --ha-ripple-color: #006787; + } + ha-control-button p { + margin: 0; + } + hui-section { + width: 100%; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-group-toggle": HuiGroupToggleDialog; + } +} diff --git a/src/panels/lovelace/dialogs/show-group-toggle-dialog.ts b/src/panels/lovelace/dialogs/show-group-toggle-dialog.ts new file mode 100644 index 0000000000..6bbef38ff0 --- /dev/null +++ b/src/panels/lovelace/dialogs/show-group-toggle-dialog.ts @@ -0,0 +1,18 @@ +import { fireEvent } from "../../../common/dom/fire_event"; + +export interface GroupToggleDialogParams { + title: string; + subtitle?: string; + entityIds: string[]; +} + +export const showGroupControlDialog = ( + element: HTMLElement, + dialogParams: GroupToggleDialogParams +) => { + fireEvent(element, "show-dialog", { + dialogTag: "hui-dialog-group-toggle", + dialogImport: () => import("./hui-dialog-group-toggle"), + dialogParams: dialogParams, + }); +}; diff --git a/src/panels/lovelace/strategies/areas/area-view-strategy.ts b/src/panels/lovelace/strategies/areas/area-view-strategy.ts index 3ce785fbfd..3bf7f88b73 100644 --- a/src/panels/lovelace/strategies/areas/area-view-strategy.ts +++ b/src/panels/lovelace/strategies/areas/area-view-strategy.ts @@ -1,5 +1,6 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; +import { computeDomain } from "../../../../common/entity/compute_domain"; import { clamp } from "../../../../common/number/clamp"; import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge"; import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; @@ -148,7 +149,22 @@ export class AreaViewStrategy extends ReactiveElement { hass.localize("ui.panel.lovelace.strategy.areas.groups.security"), AREA_STRATEGY_GROUP_ICONS.security ), - ...security.map(computeTileCard), + ...security.map((entityId) => { + const domain = computeDomain(entityId); + if (domain === "camera") { + return { + type: "picture-entity", + entity: entityId, + show_state: false, + show_name: false, + grid_options: { + columns: 6, + rows: 2, + }, + }; + } + return computeTileCard(entityId); + }), ], }); } diff --git a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts index 79582d02fa..0502a6f2a8 100644 --- a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts +++ b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts @@ -1,4 +1,3 @@ -import { computeDomain } from "../../../../../common/entity/compute_domain"; import { computeStateName } from "../../../../../common/entity/compute_state_name"; import type { EntityFilterFunc } from "../../../../../common/entity/entity_filter"; import { generateEntityFilter } from "../../../../../common/entity/entity_filter"; @@ -10,7 +9,6 @@ import { import type { AreaRegistryEntry } from "../../../../../data/area_registry"; import { areaCompare } from "../../../../../data/area_registry"; import type { FloorRegistryEntry } from "../../../../../data/floor_registry"; -import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card"; import type { HomeAssistant } from "../../../../../types"; import { supportsAlarmModesCardFeature } from "../../../card-features/hui-alarm-modes-card-feature"; import { supportsCoverOpenCloseCardFeature } from "../../../card-features/hui-cover-open-close-card-feature"; @@ -210,7 +208,7 @@ export const getAreaGroupedEntities = ( export const computeAreaTileCardConfig = (hass: HomeAssistant, prefix: string, includeFeature?: boolean) => - (entity: string): LovelaceCardConfig => { + (entity: string): TileCardConfig => { const stateObj = hass.states[entity]; const context: LovelaceCardFeatureContext = { @@ -219,21 +217,6 @@ export const computeAreaTileCardConfig = const additionalCardConfig: Partial = {}; - const domain = computeDomain(entity); - - if (domain === "camera") { - return { - type: "picture-entity", - entity: entity, - show_state: false, - show_name: false, - grid_options: { - columns: 6, - rows: 2, - }, - }; - } - let feature: LovelaceCardFeatureConfig | undefined; if (includeFeature) { if (supportsLightBrightnessCardFeature(hass, context)) {