mirror of
https://github.com/home-assistant/frontend.git
synced 2026-02-15 07:25:54 +00:00
Light dashboard toggle area (#29363)
* Add toggle lights button on light dashboard * Use not * Use group card on desktop
This commit is contained in:
@@ -12,6 +12,11 @@ import type { LovelaceSectionRawConfig } from "../../../data/lovelace/config/sec
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { computeAreaTileCardConfig } from "../../lovelace/strategies/areas/helpers/areas-strategy-helper";
|
||||
import {
|
||||
LARGE_SCREEN_CONDITION,
|
||||
SMALL_SCREEN_CONDITION,
|
||||
} from "../../lovelace/strategies/helpers/screen-conditions";
|
||||
import type { ToggleGroupCardConfig } from "../../lovelace/cards/types";
|
||||
|
||||
export interface LightViewStrategyConfig {
|
||||
type: "light";
|
||||
@@ -45,6 +50,16 @@ const processAreasForLight = (
|
||||
}
|
||||
|
||||
if (areaCards.length > 0) {
|
||||
// Visibility condition: any light is on
|
||||
const anyOnCondition = {
|
||||
condition: "or" as const,
|
||||
conditions: areaLights.map((entityId) => ({
|
||||
condition: "state" as const,
|
||||
entity: entityId,
|
||||
state: "on",
|
||||
})),
|
||||
};
|
||||
|
||||
cards.push({
|
||||
heading_style: "subtitle",
|
||||
type: "heading",
|
||||
@@ -53,7 +68,56 @@ const processAreasForLight = (
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
},
|
||||
badges: [
|
||||
// Toggle buttons for mobile
|
||||
{
|
||||
type: "button",
|
||||
icon: "mdi:power",
|
||||
tap_action: {
|
||||
action: "perform-action",
|
||||
perform_action: "light.turn_on",
|
||||
target: {
|
||||
area_id: area.area_id,
|
||||
},
|
||||
},
|
||||
visibility: [
|
||||
SMALL_SCREEN_CONDITION,
|
||||
{
|
||||
condition: "not",
|
||||
conditions: [anyOnCondition],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
icon: "mdi:power",
|
||||
color: "amber",
|
||||
tap_action: {
|
||||
action: "perform-action",
|
||||
perform_action: "light.turn_off",
|
||||
target: {
|
||||
area_id: area.area_id,
|
||||
},
|
||||
},
|
||||
visibility: [SMALL_SCREEN_CONDITION, anyOnCondition],
|
||||
},
|
||||
] satisfies LovelaceCardConfig[],
|
||||
});
|
||||
|
||||
// Toggle group card for desktop
|
||||
cards.push({
|
||||
type: "toggle-group",
|
||||
title: hass.localize("ui.panel.lovelace.strategy.light.all_lights"),
|
||||
color: "amber",
|
||||
entities: areaLights,
|
||||
visibility: [LARGE_SCREEN_CONDITION],
|
||||
grid_options: {
|
||||
columns: 6,
|
||||
rows: 1,
|
||||
min_columns: 6,
|
||||
},
|
||||
} as ToggleGroupCardConfig);
|
||||
|
||||
cards.push(...areaCards);
|
||||
}
|
||||
}
|
||||
|
||||
161
src/panels/lovelace/cards/hui-toggle-group-card.ts
Normal file
161
src/panels/lovelace/cards/hui-toggle-group-card.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/tile/ha-tile-container";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../types";
|
||||
import type { ToggleGroupCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-toggle-group-card")
|
||||
export class HuiToggleGroupCard extends LitElement implements LovelaceCard {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: ToggleGroupCardConfig;
|
||||
|
||||
public setConfig(config: ToggleGroupCardConfig): void {
|
||||
if (!config.entities?.length) {
|
||||
throw new Error("Entities are required");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return this._config?.vertical ? 2 : 1;
|
||||
}
|
||||
|
||||
public getGridOptions(): LovelaceGridOptions {
|
||||
const columns = 6;
|
||||
let rows = 1;
|
||||
let min_columns = 6;
|
||||
|
||||
if (this._config?.vertical) {
|
||||
rows++;
|
||||
min_columns = 3;
|
||||
}
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
min_columns,
|
||||
min_rows: rows,
|
||||
};
|
||||
}
|
||||
|
||||
private _getOnEntities(): HassEntity[] {
|
||||
if (!this.hass || !this._config) return [];
|
||||
return this._config.entities
|
||||
.map((entityId) => this.hass!.states[entityId])
|
||||
.filter(
|
||||
(stateObj): stateObj is HassEntity =>
|
||||
stateObj !== undefined && stateActive(stateObj)
|
||||
);
|
||||
}
|
||||
|
||||
private _computeColor(): string | undefined {
|
||||
if (!this._config || !this.hass) return undefined;
|
||||
|
||||
const onEntities = this._getOnEntities();
|
||||
if (onEntities.length === 0) return undefined;
|
||||
|
||||
if (this._config.color) {
|
||||
return computeCssColor(this._config.color);
|
||||
}
|
||||
|
||||
return stateColorCss(onEntities[0]);
|
||||
}
|
||||
|
||||
private _computeSecondary(): string {
|
||||
if (!this.hass || !this._config) return "";
|
||||
const onCount = this._getOnEntities().length;
|
||||
const total = this._config.entities.length;
|
||||
|
||||
if (onCount === 0) {
|
||||
return this.hass.localize("ui.card.toggle-group.all_off");
|
||||
}
|
||||
if (onCount === total) {
|
||||
return this.hass.localize("ui.card.toggle-group.all_on");
|
||||
}
|
||||
return this.hass.localize("ui.card.toggle-group.some_on", {
|
||||
on_count: onCount,
|
||||
total_count: total,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleTap(): void {
|
||||
if (!this.hass || !this._config) return;
|
||||
const onEntities = this._getOnEntities();
|
||||
const domain = computeDomain(this._config.entities[0]);
|
||||
|
||||
let service: string;
|
||||
if (domain === "cover") {
|
||||
service = onEntities.length > 0 ? "close_cover" : "open_cover";
|
||||
} else {
|
||||
service = onEntities.length > 0 ? "turn_off" : "turn_on";
|
||||
}
|
||||
|
||||
this.hass.callService(domain, service, {
|
||||
entity_id: this._config.entities,
|
||||
});
|
||||
forwardHaptic(this, "light");
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const color = this._computeColor();
|
||||
const style = {
|
||||
"--tile-color": color,
|
||||
};
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)}>
|
||||
<ha-tile-container .vertical=${Boolean(this._config.vertical)}>
|
||||
<ha-tile-icon
|
||||
slot="icon"
|
||||
icon="mdi:power"
|
||||
.interactive=${true}
|
||||
@action=${this._handleTap}
|
||||
></ha-tile-icon>
|
||||
<ha-tile-info
|
||||
slot="info"
|
||||
.primary=${this._config.title}
|
||||
.secondary=${this._computeSecondary()}
|
||||
></ha-tile-info>
|
||||
</ha-tile-container>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--tile-color: var(--state-inactive-color);
|
||||
}
|
||||
ha-card {
|
||||
background: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
}
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-toggle-group-card": HuiToggleGroupCard;
|
||||
}
|
||||
}
|
||||
@@ -665,6 +665,13 @@ export interface HomeSummaryCard extends LovelaceCardConfig {
|
||||
double_tap_action?: ActionConfig;
|
||||
}
|
||||
|
||||
export interface ToggleGroupCardConfig extends LovelaceCardConfig {
|
||||
title: string;
|
||||
entities: string[];
|
||||
color?: string;
|
||||
vertical?: boolean;
|
||||
}
|
||||
|
||||
export interface DistributionEntityConfig extends EntityConfig {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ const LAZY_LOAD_TYPES = {
|
||||
picture: () => import("../cards/hui-picture-card"),
|
||||
"plant-status": () => import("../cards/hui-plant-status-card"),
|
||||
"recovery-mode": () => import("../cards/hui-recovery-mode-card"),
|
||||
"toggle-group": () => import("../cards/hui-toggle-group-card"),
|
||||
"todo-list": () => import("../cards/hui-todo-list-card"),
|
||||
"shopping-list": () => import("../cards/hui-shopping-list-card"),
|
||||
starting: () => import("../cards/hui-starting-card"),
|
||||
|
||||
14
src/panels/lovelace/strategies/helpers/screen-conditions.ts
Normal file
14
src/panels/lovelace/strategies/helpers/screen-conditions.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type {
|
||||
Condition,
|
||||
ScreenCondition,
|
||||
} from "../../common/validate-condition";
|
||||
|
||||
export const LARGE_SCREEN_CONDITION: ScreenCondition = {
|
||||
condition: "screen",
|
||||
media_query: "(min-width: 871px)",
|
||||
};
|
||||
|
||||
export const SMALL_SCREEN_CONDITION: Condition = {
|
||||
condition: "not",
|
||||
conditions: [LARGE_SCREEN_CONDITION],
|
||||
};
|
||||
@@ -27,7 +27,10 @@ import type {
|
||||
TileCardConfig,
|
||||
UpdatesCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import {
|
||||
LARGE_SCREEN_CONDITION,
|
||||
SMALL_SCREEN_CONDITION,
|
||||
} from "../helpers/screen-conditions";
|
||||
import type { CommonControlSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy";
|
||||
import { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries";
|
||||
import { OTHER_DEVICES_FILTERS } from "./helpers/other-devices-filters";
|
||||
@@ -84,16 +87,6 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const largeScreenCondition: Condition = {
|
||||
condition: "screen",
|
||||
media_query: "(min-width: 871px)",
|
||||
};
|
||||
|
||||
const smallScreenCondition: Condition = {
|
||||
condition: "screen",
|
||||
media_query: "(max-width: 870px)",
|
||||
};
|
||||
|
||||
const otherDevicesFilters = OTHER_DEVICES_FILTERS.map((filter) =>
|
||||
generateEntityFilter(hass, filter)
|
||||
);
|
||||
@@ -202,7 +195,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.lovelace.strategy.home.favorites"),
|
||||
heading_style: "title",
|
||||
visibility: [largeScreenCondition],
|
||||
visibility: [LARGE_SCREEN_CONDITION],
|
||||
grid_options: {
|
||||
rows: "auto", // Compact style
|
||||
},
|
||||
@@ -361,7 +354,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
? {
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
visibility: [smallScreenCondition],
|
||||
visibility: [SMALL_SCREEN_CONDITION],
|
||||
cards: [summaryHeadingCard, ...mobileSummaryCards],
|
||||
}
|
||||
: undefined;
|
||||
@@ -460,7 +453,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
sidebar_label: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home.summaries"
|
||||
),
|
||||
visibility: [largeScreenCondition],
|
||||
visibility: [LARGE_SCREEN_CONDITION],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -215,6 +215,11 @@
|
||||
"no_media_playing": "No media playing",
|
||||
"count_media_playing": "{count} {count, plural,\n one {playing}\n other {playing}\n}"
|
||||
},
|
||||
"toggle-group": {
|
||||
"all_off": "All off",
|
||||
"all_on": "All on",
|
||||
"some_on": "{on_count} of {total_count} on"
|
||||
},
|
||||
"discovered-devices": {
|
||||
"title": "Devices discovered",
|
||||
"count_devices": "{count} {count, plural,\n one {device to add}\n other {devices to add}\n}",
|
||||
@@ -7943,7 +7948,8 @@
|
||||
},
|
||||
"light": {
|
||||
"lights": "Lights",
|
||||
"other_lights": "Other lights"
|
||||
"other_lights": "Other lights",
|
||||
"all_lights": "All lights"
|
||||
},
|
||||
"security": {
|
||||
"devices": "Devices",
|
||||
|
||||
Reference in New Issue
Block a user