mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Add cover controls to area card and improve areas dashboard (#25892)
This commit is contained in:
parent
5c1a8029bf
commit
174d54396f
68
src/common/entity/group_entities.ts
Normal file
68
src/common/entity/group_entities.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { callService, type HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
export const computeGroupEntitiesState = (states: HassEntity[]): string => {
|
||||
if (!states.length) {
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
const validState = states.filter((stateObj) => isUnavailableState(stateObj));
|
||||
|
||||
if (!validState) {
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Use the first state to determine the domain
|
||||
// This assumes all states in the group have the same domain
|
||||
const domain = computeStateDomain(states[0]);
|
||||
|
||||
if (domain === "cover") {
|
||||
for (const s of ["opening", "closing", "open"]) {
|
||||
if (states.some((stateObj) => stateObj.state === s)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return "closed";
|
||||
}
|
||||
|
||||
if (states.some((stateObj) => stateObj.state === "on")) {
|
||||
return "on";
|
||||
}
|
||||
return "off";
|
||||
};
|
||||
|
||||
export const toggleGroupEntities = (
|
||||
hass: HomeAssistant,
|
||||
states: HassEntity[]
|
||||
) => {
|
||||
if (!states.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the first state to determine the domain
|
||||
// This assumes all states in the group have the same domain
|
||||
const domain = computeStateDomain(states[0]);
|
||||
|
||||
const state = computeGroupEntitiesState(states);
|
||||
|
||||
const isOn = state === "on" || state === "open";
|
||||
|
||||
let service = isOn ? "turn_off" : "turn_on";
|
||||
if (domain === "cover") {
|
||||
if (state === "opening" || state === "closing") {
|
||||
// If the cover is opening or closing, we toggle it to stop it
|
||||
service = "stop_cover";
|
||||
} else {
|
||||
// For covers, we use the open/close service
|
||||
service = isOn ? "close_cover" : "open_cover";
|
||||
}
|
||||
}
|
||||
|
||||
const entitiesIds = states.map((stateObj) => stateObj.entity_id);
|
||||
|
||||
callService(hass.connection, domain, service, {
|
||||
entity_id: entitiesIds,
|
||||
});
|
||||
};
|
@ -64,15 +64,27 @@ export const domainStateColorProperties = (
|
||||
const compareState = state !== undefined ? state : stateObj.state;
|
||||
const active = stateActive(stateObj, state);
|
||||
|
||||
return domainColorProperties(
|
||||
domain,
|
||||
stateObj.attributes.device_class,
|
||||
compareState,
|
||||
active
|
||||
);
|
||||
};
|
||||
|
||||
export const domainColorProperties = (
|
||||
domain: string,
|
||||
deviceClass: string | undefined,
|
||||
state: string,
|
||||
active: boolean
|
||||
) => {
|
||||
const properties: string[] = [];
|
||||
|
||||
const stateKey = slugify(compareState, "_");
|
||||
const stateKey = slugify(state, "_");
|
||||
const activeKey = active ? "active" : "inactive";
|
||||
|
||||
const dc = stateObj.attributes.device_class;
|
||||
|
||||
if (dc) {
|
||||
properties.push(`--state-${domain}-${dc}-${stateKey}-color`);
|
||||
if (deviceClass) {
|
||||
properties.push(`--state-${domain}-${deviceClass}-${stateKey}-color`);
|
||||
}
|
||||
|
||||
properties.push(
|
||||
|
@ -26,6 +26,7 @@ export class HaControlButtonGroup extends LitElement {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: var(--control-button-group-alignment, start);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ export class HaDomainIcon extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public deviceClass?: string;
|
||||
|
||||
@property({ attribute: false }) public state?: string;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@property({ attribute: "brand-fallback", type: Boolean })
|
||||
@ -36,14 +38,17 @@ export class HaDomainIcon extends LitElement {
|
||||
return this._renderFallback();
|
||||
}
|
||||
|
||||
const icon = domainIcon(this.hass, this.domain, this.deviceClass).then(
|
||||
(icn) => {
|
||||
const icon = domainIcon(
|
||||
this.hass,
|
||||
this.domain,
|
||||
this.deviceClass,
|
||||
this.state
|
||||
).then((icn) => {
|
||||
if (icn) {
|
||||
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
||||
}
|
||||
return this._renderFallback();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return html`${until(icon)}`;
|
||||
}
|
||||
|
@ -504,14 +504,25 @@ export const serviceSectionIcon = async (
|
||||
export const domainIcon = async (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
deviceClass?: string
|
||||
deviceClass?: string,
|
||||
state?: string
|
||||
): Promise<string | undefined> => {
|
||||
const entityComponentIcons = await getComponentIcons(hass, domain);
|
||||
if (entityComponentIcons) {
|
||||
const translations =
|
||||
(deviceClass && entityComponentIcons[deviceClass]) ||
|
||||
entityComponentIcons._;
|
||||
return translations?.default;
|
||||
// First check for exact state match
|
||||
if (state && translations.state?.[state]) {
|
||||
return translations.state[state];
|
||||
}
|
||||
// Then check for range-based icons if we have a numeric state
|
||||
if (state !== undefined && translations.range && !isNaN(Number(state))) {
|
||||
return getIconFromRange(Number(state), translations.range);
|
||||
}
|
||||
// Fallback to default icon
|
||||
return translations.default;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
@ -25,6 +25,9 @@ export const cardFeatureStyles = css`
|
||||
flex-basis: 20px;
|
||||
--control-button-padding: 0px;
|
||||
}
|
||||
ha-control-button-group[no-stretch] > ha-control-button {
|
||||
max-width: 48px;
|
||||
}
|
||||
ha-control-button {
|
||||
--control-button-focus-color: var(--feature-color);
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
import { mdiFan, mdiLightbulb, mdiToggleSwitch } from "@mdi/js";
|
||||
import { callService, type HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
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 memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { generateEntityFilter } from "../../../common/entity/entity_filter";
|
||||
import {
|
||||
generateEntityFilter,
|
||||
type EntityFilter,
|
||||
} from "../../../common/entity/entity_filter";
|
||||
computeGroupEntitiesState,
|
||||
toggleGroupEntities,
|
||||
} from "../../../common/entity/group_entities";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { domainColorProperties } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { computeCssVariable } from "../../../resources/css-variables";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
@ -19,41 +24,55 @@ import type {
|
||||
AreaControl,
|
||||
AreaControlsCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
LovelaceCardFeaturePosition,
|
||||
} from "./types";
|
||||
import { AREA_CONTROLS } from "./types";
|
||||
|
||||
interface AreaControlsButton {
|
||||
iconPath: string;
|
||||
onService: string;
|
||||
offService: string;
|
||||
filter: EntityFilter;
|
||||
offIcon?: string;
|
||||
onIcon?: string;
|
||||
filter: {
|
||||
domain: string;
|
||||
device_class?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const coverButton = (deviceClass: string) => ({
|
||||
filter: {
|
||||
domain: "cover",
|
||||
device_class: deviceClass,
|
||||
},
|
||||
});
|
||||
|
||||
export const AREA_CONTROLS_BUTTONS: Record<AreaControl, AreaControlsButton> = {
|
||||
light: {
|
||||
iconPath: mdiLightbulb,
|
||||
// Overrides the icons for lights
|
||||
offIcon: "mdi:lightbulb-off",
|
||||
onIcon: "mdi:lightbulb",
|
||||
filter: {
|
||||
domain: "light",
|
||||
},
|
||||
onService: "light.turn_on",
|
||||
offService: "light.turn_off",
|
||||
},
|
||||
fan: {
|
||||
iconPath: mdiFan,
|
||||
filter: {
|
||||
domain: "fan",
|
||||
},
|
||||
onService: "fan.turn_on",
|
||||
offService: "fan.turn_off",
|
||||
},
|
||||
switch: {
|
||||
iconPath: mdiToggleSwitch,
|
||||
filter: {
|
||||
domain: "switch",
|
||||
},
|
||||
onService: "switch.turn_on",
|
||||
offService: "switch.turn_off",
|
||||
},
|
||||
"cover-blind": coverButton("blind"),
|
||||
"cover-curtain": coverButton("curtain"),
|
||||
"cover-damper": coverButton("damper"),
|
||||
"cover-awning": coverButton("awning"),
|
||||
"cover-door": coverButton("door"),
|
||||
"cover-garage": coverButton("garage"),
|
||||
"cover-gate": coverButton("gate"),
|
||||
"cover-shade": coverButton("shade"),
|
||||
"cover-shutter": coverButton("shutter"),
|
||||
"cover-window": coverButton("window"),
|
||||
};
|
||||
|
||||
export const supportsAreaControlsCardFeature = (
|
||||
@ -87,6 +106,8 @@ export const getAreaControlEntities = (
|
||||
{} as Record<AreaControl, string[]>
|
||||
);
|
||||
|
||||
export const MAX_DEFAULT_AREA_CONTROLS = 4;
|
||||
|
||||
@customElement("hui-area-controls-card-feature")
|
||||
class HuiAreaControlsCardFeature
|
||||
extends LitElement
|
||||
@ -96,6 +117,9 @@ class HuiAreaControlsCardFeature
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@property({ attribute: false })
|
||||
public position?: LovelaceCardFeaturePosition;
|
||||
|
||||
@state() private _config?: AreaControlsCardFeatureConfig;
|
||||
|
||||
private get _area() {
|
||||
@ -151,17 +175,12 @@ class HuiAreaControlsCardFeature
|
||||
);
|
||||
const entitiesIds = controlEntities[control];
|
||||
|
||||
const { onService, offService } = AREA_CONTROLS_BUTTONS[control];
|
||||
const entities = entitiesIds
|
||||
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
|
||||
.filter((v): v is HassEntity => Boolean(v));
|
||||
|
||||
const isOn = entitiesIds.some((entityId) =>
|
||||
stateActive(this.hass!.states[entityId] as HassEntity)
|
||||
);
|
||||
|
||||
const [domain, service] = (isOn ? offService : onService).split(".");
|
||||
|
||||
callService(this.hass!.connection, domain, service, {
|
||||
entity_id: entitiesIds,
|
||||
});
|
||||
forwardHaptic("light");
|
||||
toggleGroupEntities(this.hass, entities);
|
||||
}
|
||||
|
||||
private _controlEntities = memoizeOne(
|
||||
@ -200,33 +219,67 @@ class HuiAreaControlsCardFeature
|
||||
(control) => controlEntities[control].length > 0
|
||||
);
|
||||
|
||||
if (!supportedControls.length) {
|
||||
const displayControls = this._config.controls
|
||||
? supportedControls
|
||||
: supportedControls.slice(0, MAX_DEFAULT_AREA_CONTROLS); // Limit to max if using default controls
|
||||
|
||||
if (!displayControls.length) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-control-button-group>
|
||||
${supportedControls.map((control) => {
|
||||
<ha-control-button-group ?no-stretch=${this.position === "inline"}>
|
||||
${displayControls.map((control) => {
|
||||
const button = AREA_CONTROLS_BUTTONS[control];
|
||||
|
||||
const entities = controlEntities[control];
|
||||
const active = entities.some((entityId) => {
|
||||
const stateObj = this.hass!.states[entityId] as
|
||||
| HassEntity
|
||||
| undefined;
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return stateActive(stateObj);
|
||||
});
|
||||
const entityIds = controlEntities[control];
|
||||
|
||||
const entities = entityIds
|
||||
.map(
|
||||
(entityId) =>
|
||||
this.hass!.states[entityId] as HassEntity | undefined
|
||||
)
|
||||
.filter((v): v is HassEntity => Boolean(v));
|
||||
|
||||
const groupState = computeGroupEntitiesState(entities);
|
||||
|
||||
const active = entities[0]
|
||||
? stateActive(entities[0], groupState)
|
||||
: false;
|
||||
|
||||
const label = this.hass!.localize(
|
||||
`ui.card_features.area_controls.${control}.${active ? "off" : "on"}`
|
||||
);
|
||||
|
||||
const icon = active ? button.onIcon : button.offIcon;
|
||||
|
||||
const domain = button.filter.domain;
|
||||
const deviceClass = button.filter.device_class
|
||||
? ensureArray(button.filter.device_class)[0]
|
||||
: undefined;
|
||||
|
||||
const activeColor = computeCssVariable(
|
||||
domainColorProperties(domain, deviceClass, groupState, true)
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-control-button
|
||||
style=${styleMap({
|
||||
"--active-color": activeColor,
|
||||
})}
|
||||
.title=${label}
|
||||
aria-label=${label}
|
||||
class=${active ? "active" : ""}
|
||||
.control=${control}
|
||||
@click=${this._handleButtonTap}
|
||||
>
|
||||
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
|
||||
<ha-domain-icon
|
||||
.hass=${this.hass}
|
||||
.icon=${icon}
|
||||
.domain=${domain}
|
||||
.deviceClass=${deviceClass}
|
||||
.state=${groupState}
|
||||
></ha-domain-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
})}
|
||||
@ -238,6 +291,9 @@ class HuiAreaControlsCardFeature
|
||||
return [
|
||||
cardFeatureStyles,
|
||||
css`
|
||||
ha-control-button-group {
|
||||
--control-button-group-alignment: flex-end;
|
||||
}
|
||||
ha-control-button {
|
||||
--active-color: var(--state-active-color);
|
||||
--control-button-focus-color: var(--state-active-color);
|
||||
|
@ -7,6 +7,7 @@ import type { LovelaceCardFeature } from "../types";
|
||||
import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
LovelaceCardFeaturePosition,
|
||||
} from "./types";
|
||||
|
||||
@customElement("hui-card-feature")
|
||||
@ -19,6 +20,9 @@ export class HuiCardFeature extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public color?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public position?: LovelaceCardFeaturePosition;
|
||||
|
||||
private _element?: LovelaceCardFeature | HuiErrorCard;
|
||||
|
||||
private _getFeatureElement(feature: LovelaceCardFeatureConfig) {
|
||||
@ -41,6 +45,7 @@ export class HuiCardFeature extends LitElement {
|
||||
element.hass = this.hass;
|
||||
element.context = this.context;
|
||||
element.color = this.color;
|
||||
element.position = this.position;
|
||||
// Backwards compatibility from custom card features
|
||||
if (this.context.entity_id) {
|
||||
const stateObj = this.hass.states[this.context.entity_id];
|
||||
|
@ -5,6 +5,7 @@ import "./hui-card-feature";
|
||||
import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
LovelaceCardFeaturePosition,
|
||||
} from "./types";
|
||||
|
||||
@customElement("hui-card-features")
|
||||
@ -17,6 +18,9 @@ export class HuiCardFeatures extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public color?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public position?: LovelaceCardFeaturePosition;
|
||||
|
||||
protected render() {
|
||||
if (!this.features) {
|
||||
return nothing;
|
||||
@ -29,6 +33,7 @@ export class HuiCardFeatures extends LitElement {
|
||||
.context=${this.context}
|
||||
.color=${this.color}
|
||||
.feature=${feature}
|
||||
.position=${this.position}
|
||||
></hui-card-feature>
|
||||
`
|
||||
)}
|
||||
|
@ -158,7 +158,21 @@ export interface UpdateActionsCardFeatureConfig {
|
||||
backup?: "yes" | "no" | "ask";
|
||||
}
|
||||
|
||||
export const AREA_CONTROLS = ["light", "fan", "switch"] as const;
|
||||
export const AREA_CONTROLS = [
|
||||
"light",
|
||||
"fan",
|
||||
"cover-shutter",
|
||||
"cover-blind",
|
||||
"cover-curtain",
|
||||
"cover-shade",
|
||||
"cover-awning",
|
||||
"cover-garage",
|
||||
"cover-gate",
|
||||
"cover-door",
|
||||
"cover-window",
|
||||
"cover-damper",
|
||||
"switch",
|
||||
] as const;
|
||||
|
||||
export type AreaControl = (typeof AREA_CONTROLS)[number];
|
||||
|
||||
@ -168,6 +182,8 @@ export interface AreaControlsCardFeatureConfig {
|
||||
exclude_entities?: string[];
|
||||
}
|
||||
|
||||
export type LovelaceCardFeaturePosition = "bottom" | "inline";
|
||||
|
||||
export type LovelaceCardFeatureConfig =
|
||||
| AlarmModesCardFeatureConfig
|
||||
| ClimateFanModesCardFeatureConfig
|
||||
|
@ -514,6 +514,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
.context=${this._featureContext}
|
||||
.color=${this._config.color}
|
||||
.features=${features}
|
||||
.position=${featurePosition}
|
||||
></hui-card-features>
|
||||
`
|
||||
: nothing}
|
||||
|
@ -9,7 +9,10 @@ import type {
|
||||
ThemeMode,
|
||||
TranslationDict,
|
||||
} from "../../../types";
|
||||
import type { LovelaceCardFeatureConfig } from "../card-features/types";
|
||||
import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
LovelaceCardFeaturePosition,
|
||||
} from "../card-features/types";
|
||||
import type { LegacyStateFilter } from "../common/evaluate-filter";
|
||||
import type { Condition, LegacyCondition } from "../common/validate-condition";
|
||||
import type { HuiImage } from "../components/hui-image";
|
||||
@ -113,7 +116,7 @@ export interface AreaCardConfig extends LovelaceCardConfig {
|
||||
sensor_classes?: string[];
|
||||
alert_classes?: string[];
|
||||
features?: LovelaceCardFeatureConfig[];
|
||||
features_position?: "bottom" | "inline";
|
||||
features_position?: LovelaceCardFeaturePosition;
|
||||
}
|
||||
|
||||
export interface ButtonCardConfig extends LovelaceCardConfig {
|
||||
@ -564,7 +567,7 @@ export interface TileCardConfig extends LovelaceCardConfig {
|
||||
icon_hold_action?: ActionConfig;
|
||||
icon_double_tap_action?: ActionConfig;
|
||||
features?: LovelaceCardFeatureConfig[];
|
||||
features_position?: "bottom" | "inline";
|
||||
features_position?: LovelaceCardFeaturePosition;
|
||||
}
|
||||
|
||||
export interface HeadingCardConfig extends LovelaceCardConfig {
|
||||
|
@ -9,7 +9,10 @@ import type {
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
|
||||
import {
|
||||
getAreaControlEntities,
|
||||
MAX_DEFAULT_AREA_CONTROLS,
|
||||
} from "../../card-features/hui-area-controls-card-feature";
|
||||
import {
|
||||
AREA_CONTROLS,
|
||||
type AreaControl,
|
||||
@ -72,7 +75,7 @@ export class HuiAreaControlsCardFeatureEditor
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
private _compatibleControls = memoizeOne(
|
||||
private _supportedControls = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
// needed to update memoized function when entities, devices or areas change
|
||||
@ -99,14 +102,14 @@ export class HuiAreaControlsCardFeatureEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const compatibleControls = this._compatibleControls(
|
||||
const supportedControls = this._supportedControls(
|
||||
this.context.area_id,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas
|
||||
);
|
||||
|
||||
if (compatibleControls.length === 0) {
|
||||
if (supportedControls.length === 0) {
|
||||
return html`
|
||||
<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
@ -124,7 +127,7 @@ export class HuiAreaControlsCardFeatureEditor
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
data.customize_controls,
|
||||
compatibleControls
|
||||
supportedControls
|
||||
);
|
||||
|
||||
return html`
|
||||
@ -143,12 +146,12 @@ export class HuiAreaControlsCardFeatureEditor
|
||||
.value as AreaControlsCardFeatureData;
|
||||
|
||||
if (customize_controls && !config.controls) {
|
||||
config.controls = this._compatibleControls(
|
||||
config.controls = this._supportedControls(
|
||||
this.context!.area_id!,
|
||||
this.hass!.entities,
|
||||
this.hass!.devices,
|
||||
this.hass!.areas
|
||||
).concat();
|
||||
).slice(0, MAX_DEFAULT_AREA_CONTROLS); // Limit to max default controls
|
||||
}
|
||||
|
||||
if (!customize_controls && config.controls) {
|
||||
|
@ -6,7 +6,7 @@ import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/sec
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
|
||||
import type { AreaControl } from "../../card-features/types";
|
||||
import { AREA_CONTROLS, type AreaControl } from "../../card-features/types";
|
||||
import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types";
|
||||
import type { EntitiesDisplay } from "./area-view-strategy";
|
||||
import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper";
|
||||
@ -77,7 +77,9 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
|
||||
.map((display) => display.hidden || [])
|
||||
.flat();
|
||||
|
||||
const controls: AreaControl[] = ["light", "fan"];
|
||||
const controls: AreaControl[] = AREA_CONTROLS.filter(
|
||||
(a) => a !== "switch" // Exclude switches control for areas as we don't know what the switches control
|
||||
);
|
||||
const controlEntities = getAreaControlEntities(
|
||||
controls,
|
||||
area.area_id,
|
||||
@ -112,6 +114,11 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
|
||||
},
|
||||
]
|
||||
: [],
|
||||
grid_options: {
|
||||
rows: 1,
|
||||
columns: 12,
|
||||
},
|
||||
features_position: "inline",
|
||||
navigation_path: path,
|
||||
};
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ import type { Constructor, HomeAssistant } from "../../types";
|
||||
import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
LovelaceCardFeaturePosition,
|
||||
} from "./card-features/types";
|
||||
import type { LovelaceElement, LovelaceElementConfig } from "./elements/types";
|
||||
import type { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
|
||||
@ -179,6 +180,7 @@ export interface LovelaceCardFeature extends HTMLElement {
|
||||
context?: LovelaceCardFeatureContext;
|
||||
setConfig(config: LovelaceCardFeatureConfig);
|
||||
color?: string;
|
||||
position?: LovelaceCardFeaturePosition;
|
||||
}
|
||||
|
||||
export interface LovelaceCardFeatureConstructor
|
||||
|
@ -325,6 +325,62 @@
|
||||
"low": "Low"
|
||||
}
|
||||
},
|
||||
"card_features": {
|
||||
"area_controls": {
|
||||
"light": {
|
||||
"on": "Turn on area lights",
|
||||
"off": "Turn off area lights"
|
||||
},
|
||||
"fan": {
|
||||
"on": "Turn on area fans",
|
||||
"off": "Turn off area fans"
|
||||
},
|
||||
"switch": {
|
||||
"on": "Turn on area switches",
|
||||
"off": "Turn off area switches"
|
||||
},
|
||||
"cover-awning": {
|
||||
"on": "Open area awnings",
|
||||
"off": "Close area awnings"
|
||||
},
|
||||
"cover-blind": {
|
||||
"on": "Open area blinds",
|
||||
"off": "Close area blinds"
|
||||
},
|
||||
"cover-curtain": {
|
||||
"on": "Open area curtains",
|
||||
"off": "Close area curtains"
|
||||
},
|
||||
"cover-damper": {
|
||||
"on": "Open area dampers",
|
||||
"off": "Close area dampers"
|
||||
},
|
||||
"cover-door": {
|
||||
"on": "Open area doors",
|
||||
"off": "Close area doors"
|
||||
},
|
||||
"cover-garage": {
|
||||
"on": "Open garage door",
|
||||
"off": "Close garage door"
|
||||
},
|
||||
"cover-gate": {
|
||||
"on": "Open area gates",
|
||||
"off": "Close area gates"
|
||||
},
|
||||
"cover-shade": {
|
||||
"on": "Open area shades",
|
||||
"off": "Close area shades"
|
||||
},
|
||||
"cover-shutter": {
|
||||
"on": "Open area shutters",
|
||||
"off": "Close area shutters"
|
||||
},
|
||||
"cover-window": {
|
||||
"on": "Open area windows",
|
||||
"off": "Close area windows"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"and": "and",
|
||||
"continue": "Continue",
|
||||
@ -383,6 +439,7 @@
|
||||
"markdown": "Markdown",
|
||||
"suggest_ai": "Suggest with AI"
|
||||
},
|
||||
|
||||
"components": {
|
||||
"selectors": {
|
||||
"media": {
|
||||
@ -7857,7 +7914,17 @@
|
||||
"controls_options": {
|
||||
"light": "Lights",
|
||||
"fan": "Fans",
|
||||
"switch": "Switches"
|
||||
"switch": "Switches",
|
||||
"cover-awning": "Awnings",
|
||||
"cover-blind": "Blinds",
|
||||
"cover-curtain": "Curtains",
|
||||
"cover-damper": "Dampers",
|
||||
"cover-door": "Doors",
|
||||
"cover-garage": "Garage doors",
|
||||
"cover-gate": "Gates",
|
||||
"cover-shade": "Shades",
|
||||
"cover-shutter": "Shutters",
|
||||
"cover-window": "Windows"
|
||||
},
|
||||
"no_compatible_controls": "No compatible controls available for this area"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user