Compare commits

...

3 Commits

2 changed files with 102 additions and 21 deletions

View File

@@ -0,0 +1,93 @@
import { consume } from "@lit/context";
import type { HassEntities, HassEntity } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../../types";
import { entitiesContext, statesContext } from "../../data/context";
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
import { transform } from "./transform";
interface ConsumeEntryConfig {
entityIdPath: readonly string[];
}
const resolveAtPath = (host: unknown, path: readonly string[]) => {
let cur: any = host;
for (const seg of path) {
if (cur == null) return undefined;
cur = cur[seg];
}
return cur;
};
const composeDecorator = <T, V>(
context: Parameters<typeof consume>[0]["context"],
watchKey: string | undefined,
select: (this: unknown, value: T) => V | undefined
) => {
const transformDec = transform<T, V | undefined>({
transformer: function (this: unknown, value) {
return select.call(this, value);
},
watch: watchKey ? [watchKey] : [],
});
const consumeDec = consume<any>({ context, subscribe: true });
return (proto: any, propertyKey: string) => {
transformDec(proto, propertyKey);
consumeDec(proto, propertyKey);
};
};
/**
* Consumes `statesContext` and narrows it to the `HassEntity` for the entity
* ID found at `entityIdPath` on the host (e.g. `["_config", "entity"]`).
*
* The first path segment is watched on the host — changes to it re-run the
* lookup. Deeper segments are traversed at lookup time and short-circuit on
* nullish values.
*/
export const consumeEntityState = (config: ConsumeEntryConfig) =>
composeDecorator<HassEntities, HassEntity>(
statesContext,
config.entityIdPath[0],
function (states) {
const id = resolveAtPath(this, config.entityIdPath);
return typeof id === "string" ? states?.[id] : undefined;
}
);
/**
* Like {@link consumeEntityState} but for an array of entity IDs at
* `entityIdPath`. Resolves to a `HassEntity[]` containing one entry per
* currently-available entity (missing entities and non-string IDs are
* filtered out; original order is preserved).
*/
export const consumeEntityStates = (config: ConsumeEntryConfig) =>
composeDecorator<HassEntities, HassEntity[]>(
statesContext,
config.entityIdPath[0],
function (states) {
const ids = resolveAtPath(this, config.entityIdPath);
if (!Array.isArray(ids) || !states) return undefined;
const result: HassEntity[] = [];
for (const id of ids) {
if (typeof id !== "string") continue;
const state = states[id];
if (state !== undefined) result.push(state);
}
return result;
}
);
/**
* Consumes `entitiesContext` and narrows it to the
* `EntityRegistryDisplayEntry` for the entity ID found at `entityIdPath` on
* the host. See {@link consumeEntityState} for semantics.
*/
export const consumeEntityRegistryEntry = (config: ConsumeEntryConfig) =>
composeDecorator<HomeAssistant["entities"], EntityRegistryDisplayEntry>(
entitiesContext,
config.entityIdPath[0],
function (entities) {
const id = resolveAtPath(this, config.entityIdPath);
return typeof id === "string" ? entities?.[id] : undefined;
}
);

View File

@@ -1,5 +1,5 @@
import { consume, type ContextType } from "@lit/context";
import type { HassEntities, HassEntity } from "home-assistant-js-websocket";
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, state } from "lit/decorators";
@@ -7,6 +7,10 @@ import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../../common/color/compute-color";
import { DOMAINS_TOGGLE } from "../../../common/const";
import {
consumeEntityRegistryEntry,
consumeEntityState,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
@@ -22,11 +26,7 @@ import { iconColorCSS } from "../../../common/style/icon_color_css";
import "../../../components/ha-card";
import "../../../components/ha-ripple";
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../../data/climate";
import {
entitiesContext,
statesContext,
uiContext,
} from "../../../data/context";
import { uiContext } from "../../../data/context";
import type { EntityRegistryDisplayEntry } from "../../../data/entity/entity_registry";
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import type { Themes } from "../../../data/ws-themes";
@@ -94,13 +94,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
@state() private _config?: ButtonCardConfig;
@state()
@consume<any>({ context: statesContext, subscribe: true })
@transform({
transformer: function (this: HuiButtonCard, value: HassEntities) {
return this._config?.entity ? value?.[this._config?.entity] : undefined;
},
watch: ["_config"],
})
@consumeEntityState({ entityIdPath: ["_config", "entity"] })
private _stateObj?: HassEntity;
@state()
@@ -111,13 +105,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
private _themes!: Themes;
@state()
@consume<any>({ context: entitiesContext, subscribe: true })
@transform<ContextType<typeof entitiesContext>, EntityRegistryDisplayEntry>({
transformer: function (this: HuiButtonCard, value) {
return this._config?.entity ? value?.[this._config?.entity] : undefined;
},
watch: ["_config"],
})
@consumeEntityRegistryEntry({ entityIdPath: ["_config", "entity"] })
_entity?: EntityRegistryDisplayEntry;
public getCardSize(): number {