mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-17 22:01:56 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5bf35690b | |||
| d98eb47490 | |||
| 738e92d27d | |||
| ade2e9272b | |||
| d8ce60dfb6 | |||
| db9374925e | |||
| 1bcd1293c0 |
@@ -110,6 +110,15 @@ export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set([
|
||||
"media_player",
|
||||
]);
|
||||
|
||||
/** Domains that use a timestamp for state. */
|
||||
export const TIMESTAMP_STATE_DOMAINS = [
|
||||
"button",
|
||||
"infrared",
|
||||
"input_button",
|
||||
"radio_frequency",
|
||||
"scene",
|
||||
];
|
||||
|
||||
/** Temperature units. */
|
||||
export const UNIT_C = "°C";
|
||||
export const UNIT_F = "°F";
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { supportsFeature } from "./supports-feature";
|
||||
|
||||
export type FeatureClassNames<T extends number = number> = Partial<
|
||||
Record<T, string>
|
||||
>;
|
||||
|
||||
// Expects classNames to be an object mapping feature-bit -> className
|
||||
export const featureClassNames = (
|
||||
stateObj: HassEntity,
|
||||
classNames: FeatureClassNames
|
||||
) => {
|
||||
if (!stateObj || !stateObj.attributes.supported_features) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return Object.keys(classNames)
|
||||
.map((feature) =>
|
||||
supportsFeature(stateObj, Number(feature)) ? classNames[feature] : ""
|
||||
)
|
||||
.filter((attr) => attr !== "")
|
||||
.join(" ");
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { AITaskEntityFeature } from "../../data/ai_task";
|
||||
import { AlarmControlPanelEntityFeature } from "../../data/alarm_control_panel";
|
||||
import { AssistSatelliteEntityFeature } from "../../data/assist_satellite";
|
||||
import { CalendarEntityFeature } from "../../data/calendar";
|
||||
import { ClimateEntityFeature } from "../../data/climate";
|
||||
import { ConversationEntityFeature } from "../../data/conversation";
|
||||
import { CoverEntityFeature } from "../../data/cover";
|
||||
import { FanEntityFeature } from "../../data/fan";
|
||||
import { HumidifierEntityFeature } from "../../data/humidifier";
|
||||
import { LawnMowerEntityFeature } from "../../data/lawn_mower";
|
||||
import { LightEntityFeature } from "../../data/light";
|
||||
import { LockEntityFeature } from "../../data/lock";
|
||||
import { MediaPlayerEntityFeature } from "../../data/media-player";
|
||||
import { TodoListEntityFeature } from "../../data/todo";
|
||||
import { UpdateEntityFeature } from "../../data/update";
|
||||
import { VacuumEntityFeature } from "../../data/vacuum";
|
||||
import { ValveEntityFeature } from "../../data/valve";
|
||||
import { WaterHeaterEntityFeature } from "../../data/water_heater";
|
||||
import { WeatherEntityFeature } from "../../data/weather";
|
||||
|
||||
export type FeatureEnum = Record<string | number, string | number>;
|
||||
|
||||
const DOMAIN_ENUMS = {
|
||||
ai_task: AITaskEntityFeature,
|
||||
alarm_control_panel: AlarmControlPanelEntityFeature,
|
||||
assist_satellite: AssistSatelliteEntityFeature,
|
||||
calendar: CalendarEntityFeature,
|
||||
climate: ClimateEntityFeature,
|
||||
conversation: ConversationEntityFeature,
|
||||
cover: CoverEntityFeature,
|
||||
fan: FanEntityFeature,
|
||||
humidifier: HumidifierEntityFeature,
|
||||
lawn_mower: LawnMowerEntityFeature,
|
||||
light: LightEntityFeature,
|
||||
lock: LockEntityFeature,
|
||||
media_player: MediaPlayerEntityFeature,
|
||||
todo: TodoListEntityFeature,
|
||||
update: UpdateEntityFeature,
|
||||
vacuum: VacuumEntityFeature,
|
||||
valve: ValveEntityFeature,
|
||||
water_heater: WaterHeaterEntityFeature,
|
||||
weather: WeatherEntityFeature,
|
||||
};
|
||||
|
||||
export function getFeatures(domain: string): FeatureEnum | undefined {
|
||||
const enumObj = DOMAIN_ENUMS[domain] as FeatureEnum;
|
||||
return enumObj;
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../ha-svg-icon";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCheckCircle,
|
||||
mdiCloseCircle,
|
||||
mdiHelpCircle,
|
||||
} from "@mdi/js";
|
||||
|
||||
export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
|
||||
@@ -19,46 +26,59 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
|
||||
@property() public label = "";
|
||||
|
||||
private get _iconPath() {
|
||||
switch (this.state) {
|
||||
case "pass":
|
||||
return mdiCheckCircle;
|
||||
case "fail":
|
||||
return mdiCloseCircle;
|
||||
case "invalid":
|
||||
return mdiAlertCircle;
|
||||
default:
|
||||
return mdiHelpCircle;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
id="indicator"
|
||||
role="status"
|
||||
tabindex="0"
|
||||
aria-label=${this.label}
|
||||
></div>
|
||||
<div id="indicator" role="status" tabindex="0" aria-label=${this.label}>
|
||||
<ha-svg-icon .path=${this._iconPath}></ha-svg-icon>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
inset-inline-end: -6px;
|
||||
top: -8px;
|
||||
inset-inline-end: -8px;
|
||||
display: inline-block;
|
||||
}
|
||||
#indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
border: var(--ha-border-width-md) solid;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 0 0 2px var(--card-background-color);
|
||||
transition: all var(--ha-animation-duration-normal) ease-in-out;
|
||||
}
|
||||
#indicator ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
:host([state="pass"]) #indicator {
|
||||
background-color: var(--ha-color-green-60);
|
||||
border-color: var(--ha-color-green-60);
|
||||
color: var(--ha-color-green-60);
|
||||
}
|
||||
:host([state="fail"]) #indicator {
|
||||
border-color: var(--ha-color-orange-60);
|
||||
color: var(--ha-color-orange-60);
|
||||
}
|
||||
:host([state="invalid"]) #indicator {
|
||||
border-color: var(--ha-color-red-60);
|
||||
color: var(--ha-color-red-60);
|
||||
}
|
||||
:host([state="unknown"]) #indicator {
|
||||
border-color: var(--ha-color-neutral-60);
|
||||
color: var(--ha-color-neutral-60);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import {
|
||||
configContext,
|
||||
connectionContext,
|
||||
entitiesContext,
|
||||
} from "../data/context";
|
||||
import { attributeIcon } from "../data/icons";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-attribute-icon")
|
||||
export class HaAttributeIcon extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
@property() public attribute?: string;
|
||||
@@ -19,6 +23,18 @@ export class HaAttributeIcon extends LitElement {
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@state()
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
private _config?: ContextType<typeof configContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: connectionContext, subscribe: true })
|
||||
private _connection?: ContextType<typeof connectionContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: entitiesContext, subscribe: true })
|
||||
private _entities?: ContextType<typeof entitiesContext>;
|
||||
|
||||
protected render() {
|
||||
if (this.icon) {
|
||||
return html`<ha-icon .icon=${this.icon}></ha-icon>`;
|
||||
@@ -28,12 +44,14 @@ export class HaAttributeIcon extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (!this.hass) {
|
||||
if (!this._config || !this._connection || !this._entities) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const icon = attributeIcon(
|
||||
this.hass,
|
||||
this._config.config,
|
||||
this._connection.connection,
|
||||
this._entities,
|
||||
this.stateObj,
|
||||
this.attribute,
|
||||
this.attributeValue
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { formatNumber } from "../common/number/format_number";
|
||||
import { blankBeforeUnit } from "../common/translations/blank_before_unit";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { internationalizationContext } from "../data/context";
|
||||
|
||||
@customElement("ha-big-number")
|
||||
export class HaBigNumber extends LitElement {
|
||||
@@ -15,17 +17,16 @@ export class HaBigNumber extends LitElement {
|
||||
@property({ attribute: "unit-position" })
|
||||
public unitPosition: "top" | "bottom" = "top";
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public formatOptions: Intl.NumberFormatOptions = {};
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n?: ContextType<typeof internationalizationContext>;
|
||||
|
||||
protected render() {
|
||||
const formatted = formatNumber(
|
||||
this.value,
|
||||
this.hass?.locale,
|
||||
this.formatOptions
|
||||
);
|
||||
const locale = this._i18n!.locale;
|
||||
const formatted = formatNumber(this.value, locale, this.formatOptions);
|
||||
const [integer] = formatted.includes(".")
|
||||
? formatted.split(".")
|
||||
: formatted.split(",");
|
||||
@@ -33,9 +34,7 @@ export class HaBigNumber extends LitElement {
|
||||
const temperatureDecimal = formatted.replace(integer, "");
|
||||
|
||||
const formattedValue = `${this.value}${
|
||||
this.unit
|
||||
? `${blankBeforeUnit(this.unit, this.hass?.locale)}${this.unit}`
|
||||
: ""
|
||||
this.unit ? `${blankBeforeUnit(this.unit, locale)}${this.unit}` : ""
|
||||
}`;
|
||||
|
||||
const unitBottom = this.unitPosition === "bottom";
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../ha-time-format-picker";
|
||||
|
||||
@customElement("ha-selector-ui_time_format")
|
||||
export class HaSelectorUiTimeFormat extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-time-format-picker
|
||||
.label=${this.label}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-time-format-picker>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-ui_time_format": HaSelectorUiTimeFormat;
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ const LOAD_ELEMENTS = {
|
||||
ui_action: () => import("./ha-selector-ui-action"),
|
||||
ui_color: () => import("./ha-selector-ui-color"),
|
||||
ui_state_content: () => import("./ha-selector-ui-state-content"),
|
||||
ui_time_format: () => import("./ha-selector-ui-time-format"),
|
||||
};
|
||||
|
||||
const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { consumeLocalize } from "../common/decorators/consume-context-entry";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "./ha-select";
|
||||
import { TIMESTAMP_RENDERING_FORMATS } from "../panels/lovelace/components/types";
|
||||
|
||||
@customElement("ha-time-format-picker")
|
||||
export class HaTimeFormatPicker extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
private _options = memoizeOne((localize: LocalizeFunc) =>
|
||||
[{ label: localize("ui.common.auto"), value: "auto" }].concat(
|
||||
TIMESTAMP_RENDERING_FORMATS.map((format) => ({
|
||||
label:
|
||||
localize(`ui.components.time-format-picker.formats.${format}`) ||
|
||||
format,
|
||||
value: format,
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${this.label ?? ""}
|
||||
.value=${this.value || "auto"}
|
||||
.helper=${this.helper ?? ""}
|
||||
.disabled=${this.disabled}
|
||||
@selected=${this._selectChanged}
|
||||
.options=${this._options(this._localize)}
|
||||
>
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail?.value === "auto" && this.value !== undefined) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: ev.detail.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-time-format-picker": HaTimeFormatPicker;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { Selector } from "./selector";
|
||||
|
||||
export const enum AITaskEntityFeature {
|
||||
export enum AITaskEntityFeature {
|
||||
GENERATE_DATA = 1,
|
||||
SUPPORT_ATTACHMENTS = 2,
|
||||
GENERATE_IMAGE = 4,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { getExtendedEntityRegistryEntry } from "./entity/entity_registry";
|
||||
export const FORMAT_TEXT = "text";
|
||||
export const FORMAT_NUMBER = "number";
|
||||
|
||||
export const enum AlarmControlPanelEntityFeature {
|
||||
export enum AlarmControlPanelEntityFeature {
|
||||
ARM_HOME = 1,
|
||||
ARM_AWAY = 2,
|
||||
ARM_NIGHT = 4,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
|
||||
export const enum AssistSatelliteEntityFeature {
|
||||
export enum AssistSatelliteEntityFeature {
|
||||
ANNOUNCE = 1,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export enum RecurrenceRange {
|
||||
THISANDFUTURE = "THISANDFUTURE",
|
||||
}
|
||||
|
||||
export const enum CalendarEntityFeature {
|
||||
export enum CalendarEntityFeature {
|
||||
CREATE_EVENT = 1,
|
||||
DELETE_EVENT = 2,
|
||||
UPDATE_EVENT = 4,
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ export type ClimateEntity = HassEntityBase & {
|
||||
};
|
||||
};
|
||||
|
||||
export const enum ClimateEntityFeature {
|
||||
export enum ClimateEntityFeature {
|
||||
TARGET_TEMPERATURE = 1,
|
||||
TARGET_TEMPERATURE_RANGE = 2,
|
||||
TARGET_HUMIDITY = 4,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const enum ConversationEntityFeature {
|
||||
export enum ConversationEntityFeature {
|
||||
CONTROL = 1,
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { HomeAssistantFormatters } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
|
||||
export const enum CoverEntityFeature {
|
||||
export enum CoverEntityFeature {
|
||||
OPEN = 1,
|
||||
CLOSE = 2,
|
||||
SET_POSITION = 4,
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import type {
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const enum FanEntityFeature {
|
||||
export enum FanEntityFeature {
|
||||
SET_SPEED = 1,
|
||||
OSCILLATE = 2,
|
||||
DIRECTION = 4,
|
||||
|
||||
@@ -20,7 +20,7 @@ export type HumidifierEntity = HassEntityBase & {
|
||||
};
|
||||
};
|
||||
|
||||
export const enum HumidifierEntityFeature {
|
||||
export enum HumidifierEntityFeature {
|
||||
MODES = 1,
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -548,7 +548,9 @@ const getEntityIcon = async (
|
||||
};
|
||||
|
||||
export const attributeIcon = async (
|
||||
hass: HomeAssistant,
|
||||
hassConfig: HomeAssistant["config"],
|
||||
hassConnection: HomeAssistant["connection"],
|
||||
entities: HomeAssistant["entities"],
|
||||
state: HassEntity,
|
||||
attribute: string,
|
||||
attributeValue?: string
|
||||
@@ -556,7 +558,7 @@ export const attributeIcon = async (
|
||||
let icon: string | undefined;
|
||||
const domain = computeStateDomain(state);
|
||||
const deviceClass = state.attributes.device_class;
|
||||
const entity = hass.entities?.[state.entity_id] as
|
||||
const entity = entities[state.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
const platform = entity?.platform;
|
||||
@@ -567,8 +569,8 @@ export const attributeIcon = async (
|
||||
|
||||
if (translation_key && platform) {
|
||||
const platformIcons = await getPlatformIcons(
|
||||
hass.config,
|
||||
hass.connection,
|
||||
hassConfig,
|
||||
hassConnection,
|
||||
platform
|
||||
);
|
||||
if (platformIcons) {
|
||||
@@ -580,8 +582,8 @@ export const attributeIcon = async (
|
||||
}
|
||||
if (!icon) {
|
||||
const entityComponentIcons = await getComponentIcons(
|
||||
hass.connection,
|
||||
hass.config,
|
||||
hassConnection,
|
||||
hassConfig,
|
||||
domain
|
||||
);
|
||||
if (entityComponentIcons) {
|
||||
|
||||
@@ -11,7 +11,7 @@ export type LawnMowerEntityState =
|
||||
| "docked"
|
||||
| "error";
|
||||
|
||||
export const enum LawnMowerEntityFeature {
|
||||
export enum LawnMowerEntityFeature {
|
||||
START_MOWING = 1,
|
||||
PAUSE = 2,
|
||||
DOCK = 4,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import type {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { temperature2rgb } from "../common/color/convert-light-color";
|
||||
|
||||
export const enum LightEntityFeature {
|
||||
export enum LightEntityFeature {
|
||||
EFFECT = 4,
|
||||
FLASH = 8,
|
||||
TRANSITION = 32,
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity/entity_registry";
|
||||
|
||||
export const enum LockEntityFeature {
|
||||
export enum LockEntityFeature {
|
||||
OPEN = 1,
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export interface MediaPlayerEntity extends HassEntityBase {
|
||||
| "buffering";
|
||||
}
|
||||
|
||||
export const enum MediaPlayerEntityFeature {
|
||||
export enum MediaPlayerEntityFeature {
|
||||
PAUSE = 1,
|
||||
SEEK = 2,
|
||||
VOLUME_SET = 4,
|
||||
|
||||
@@ -82,6 +82,7 @@ export type Selector =
|
||||
| UiActionSelector
|
||||
| UiColorSelector
|
||||
| UiStateContentSelector
|
||||
| UiTimeFormatSelector
|
||||
| BackupLocationSelector;
|
||||
|
||||
export interface ActionSelector {
|
||||
@@ -601,6 +602,10 @@ export interface UiStateContentSelector {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface UiTimeFormatSelector {
|
||||
ui_time_format: {} | null;
|
||||
}
|
||||
|
||||
export interface EntityNameSelector {
|
||||
entity_name: {
|
||||
entity_id?: string;
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export interface TodoItem {
|
||||
completed?: string | null;
|
||||
}
|
||||
|
||||
export const enum TodoListEntityFeature {
|
||||
export enum TodoListEntityFeature {
|
||||
CREATE_TODO_ITEM = 1,
|
||||
DELETE_TODO_ITEM = 2,
|
||||
UPDATE_TODO_ITEM = 4,
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ export type VacuumEntityState =
|
||||
| "returning"
|
||||
| "error";
|
||||
|
||||
export const enum VacuumEntityFeature {
|
||||
export enum VacuumEntityFeature {
|
||||
TURN_ON = 1,
|
||||
TURN_OFF = 2,
|
||||
PAUSE = 4,
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { HomeAssistantFormatters } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
|
||||
export const enum ValveEntityFeature {
|
||||
export enum ValveEntityFeature {
|
||||
OPEN = 1,
|
||||
CLOSE = 2,
|
||||
SET_POSITION = 4,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
export const enum WaterHeaterEntityFeature {
|
||||
export enum WaterHeaterEntityFeature {
|
||||
TARGET_TEMPERATURE = 1,
|
||||
OPERATION_MODE = 2,
|
||||
AWAY_MODE = 4,
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ import { round } from "../common/number/round";
|
||||
import "../components/ha-svg-icon";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const enum WeatherEntityFeature {
|
||||
export enum WeatherEntityFeature {
|
||||
FORECAST_DAILY = 1,
|
||||
FORECAST_HOURLY = 2,
|
||||
FORECAST_TWICE_DAILY = 4,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import {
|
||||
mdiArrowOscillating,
|
||||
mdiFan,
|
||||
@@ -8,7 +10,9 @@ import {
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-attribute-icon";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
import "../../../components/ha-icon-button-group";
|
||||
@@ -22,10 +26,10 @@ import {
|
||||
climateHvacModeIcon,
|
||||
compareClimateHvacModes,
|
||||
} from "../../../data/climate";
|
||||
import { apiContext, formattersContext } from "../../../data/context";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import "../../../state-control/climate/ha-state-control-climate-humidity";
|
||||
import "../../../state-control/climate/ha-state-control-climate-temperature";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-control-select-container";
|
||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
@@ -34,15 +38,24 @@ type MainControl = "temperature" | "humidity";
|
||||
|
||||
@customElement("more-info-climate")
|
||||
class MoreInfoClimate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: ClimateEntity;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state() private _mainControl: MainControl = "temperature";
|
||||
|
||||
private _renderPresetModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="preset_mode"
|
||||
.attributeValue=${value}
|
||||
@@ -50,7 +63,6 @@ class MoreInfoClimate extends LitElement {
|
||||
|
||||
private _renderFanModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="fan_mode"
|
||||
.attributeValue=${value}
|
||||
@@ -58,7 +70,6 @@ class MoreInfoClimate extends LitElement {
|
||||
|
||||
private _renderSwingModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="swing_mode"
|
||||
.attributeValue=${value}
|
||||
@@ -66,7 +77,6 @@ class MoreInfoClimate extends LitElement {
|
||||
|
||||
private _renderSwingHorizontalModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="swing_horizontal_mode"
|
||||
.attributeValue=${value}
|
||||
@@ -120,13 +130,13 @@ class MoreInfoClimate extends LitElement {
|
||||
? html`
|
||||
<div>
|
||||
<p class="label">
|
||||
${this.hass.formatEntityAttributeName(
|
||||
${this._formatters.formatEntityAttributeName(
|
||||
this.stateObj,
|
||||
"current_temperature"
|
||||
)}
|
||||
</p>
|
||||
<p class="value">
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_temperature"
|
||||
)}
|
||||
@@ -138,13 +148,13 @@ class MoreInfoClimate extends LitElement {
|
||||
? html`
|
||||
<div>
|
||||
<p class="label">
|
||||
${this.hass.formatEntityAttributeName(
|
||||
${this._formatters.formatEntityAttributeName(
|
||||
this.stateObj,
|
||||
"current_humidity"
|
||||
)}
|
||||
</p>
|
||||
<p class="value">
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_humidity"
|
||||
)}
|
||||
@@ -157,7 +167,6 @@ class MoreInfoClimate extends LitElement {
|
||||
${this._mainControl === "temperature"
|
||||
? html`
|
||||
<ha-state-control-climate-temperature
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-state-control-climate-temperature>
|
||||
`
|
||||
@@ -165,7 +174,6 @@ class MoreInfoClimate extends LitElement {
|
||||
${this._mainControl === "humidity"
|
||||
? html`
|
||||
<ha-state-control-climate-humidity
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-state-control-climate-humidity>
|
||||
`
|
||||
@@ -176,7 +184,7 @@ class MoreInfoClimate extends LitElement {
|
||||
<ha-icon-button-toggle
|
||||
.selected=${this._mainControl === "temperature"}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
.label=${this.hass.localize(
|
||||
.label=${this._localize(
|
||||
"ui.dialogs.more_info_control.climate.temperature"
|
||||
)}
|
||||
.control=${"temperature"}
|
||||
@@ -187,7 +195,7 @@ class MoreInfoClimate extends LitElement {
|
||||
<ha-icon-button-toggle
|
||||
.selected=${this._mainControl === "humidity"}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
.label=${this.hass.localize(
|
||||
.label=${this._localize(
|
||||
"ui.dialogs.more_info_control.climate.humidity"
|
||||
)}
|
||||
.control=${"humidity"}
|
||||
@@ -201,8 +209,7 @@ class MoreInfoClimate extends LitElement {
|
||||
</div>
|
||||
<ha-more-info-control-select-container>
|
||||
<ha-control-select-menu
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize("ui.card.climate.mode")}
|
||||
.label=${this._localize("ui.card.climate.mode")}
|
||||
.value=${stateObj.state}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.options=${stateObj.attributes.hvac_modes
|
||||
@@ -211,7 +218,7 @@ class MoreInfoClimate extends LitElement {
|
||||
.map((mode) => ({
|
||||
value: mode,
|
||||
iconPath: climateHvacModeIcon(mode),
|
||||
label: this.hass.formatEntityState(stateObj, mode),
|
||||
label: this._formatters.formatEntityState(stateObj, mode),
|
||||
}))}
|
||||
@wa-select=${this._handleOperationModeChanged}
|
||||
>
|
||||
@@ -223,8 +230,7 @@ class MoreInfoClimate extends LitElement {
|
||||
${supportPresetMode && stateObj.attributes.preset_modes
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.formatEntityAttributeName(
|
||||
.label=${this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
"preset_mode"
|
||||
)}
|
||||
@@ -233,7 +239,7 @@ class MoreInfoClimate extends LitElement {
|
||||
@wa-select=${this._handlePresetmodeChanged}
|
||||
.options=${stateObj.attributes.preset_modes.map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.formatEntityAttributeValue(
|
||||
label: this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"preset_mode",
|
||||
mode
|
||||
@@ -248,8 +254,7 @@ class MoreInfoClimate extends LitElement {
|
||||
${supportFanMode && stateObj.attributes.fan_modes
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.formatEntityAttributeName(
|
||||
.label=${this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
"fan_mode"
|
||||
)}
|
||||
@@ -258,7 +263,7 @@ class MoreInfoClimate extends LitElement {
|
||||
@wa-select=${this._handleFanModeChanged}
|
||||
.options=${stateObj.attributes.fan_modes.map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.formatEntityAttributeValue(
|
||||
label: this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"fan_mode",
|
||||
mode
|
||||
@@ -273,8 +278,7 @@ class MoreInfoClimate extends LitElement {
|
||||
${supportSwingMode && stateObj.attributes.swing_modes
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.formatEntityAttributeName(
|
||||
.label=${this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
"swing_mode"
|
||||
)}
|
||||
@@ -283,7 +287,7 @@ class MoreInfoClimate extends LitElement {
|
||||
@wa-select=${this._handleSwingmodeChanged}
|
||||
.options=${stateObj.attributes.swing_modes.map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.formatEntityAttributeValue(
|
||||
label: this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"swing_mode",
|
||||
mode
|
||||
@@ -302,8 +306,7 @@ class MoreInfoClimate extends LitElement {
|
||||
stateObj.attributes.swing_horizontal_modes
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.formatEntityAttributeName(
|
||||
.label=${this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
"swing_horizontal_mode"
|
||||
)}
|
||||
@@ -313,7 +316,7 @@ class MoreInfoClimate extends LitElement {
|
||||
.options=${stateObj.attributes.swing_horizontal_modes.map(
|
||||
(mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.formatEntityAttributeValue(
|
||||
label: this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"swing_horizontal_mode",
|
||||
mode
|
||||
@@ -403,7 +406,7 @@ class MoreInfoClimate extends LitElement {
|
||||
data.entity_id = this.stateObj!.entity_id;
|
||||
const curState = this.stateObj;
|
||||
|
||||
await this.hass.callService("climate", service, data);
|
||||
await this._api.callService("climate", service, data);
|
||||
|
||||
// We reset stateObj to re-sync the inputs with the state. It will be out
|
||||
// of sync if our service call did not result in the entity to be turned
|
||||
|
||||
@@ -42,7 +42,6 @@ class MoreInfoFan extends LitElement {
|
||||
|
||||
private _renderPresetModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="preset_mode"
|
||||
.attributeValue=${value}
|
||||
@@ -50,7 +49,6 @@ class MoreInfoFan extends LitElement {
|
||||
|
||||
private _renderDirectionIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="direction"
|
||||
.attributeValue=${value}
|
||||
@@ -241,7 +239,6 @@ class MoreInfoFan extends LitElement {
|
||||
>
|
||||
<ha-attribute-icon
|
||||
slot="icon"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="direction"
|
||||
.attributeValue=${this.stateObj.attributes.direction}
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiPower, mdiTuneVariant } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-attribute-icon";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
import "../../../components/ha-list-item";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import { apiContext, formattersContext } from "../../../data/context";
|
||||
import type { HumidifierEntity } from "../../../data/humidifier";
|
||||
import { HumidifierEntityFeature } from "../../../data/humidifier";
|
||||
import "../../../state-control/humidifier/ha-state-control-humidifier-humidity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-control-select-container";
|
||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
|
||||
@customElement("more-info-humidifier")
|
||||
class MoreInfoHumidifier extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HumidifierEntity;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state() public _mode?: string;
|
||||
|
||||
private _renderModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="mode"
|
||||
.attributeValue=${value}
|
||||
@@ -43,7 +56,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const hass = this.hass;
|
||||
const stateObj = this.stateObj;
|
||||
|
||||
const supportModes = supportsFeature(
|
||||
@@ -57,13 +69,13 @@ class MoreInfoHumidifier extends LitElement {
|
||||
? html`
|
||||
<div>
|
||||
<p class="label">
|
||||
${this.hass.formatEntityAttributeName(
|
||||
${this._formatters.formatEntityAttributeName(
|
||||
this.stateObj,
|
||||
"current_humidity"
|
||||
)}
|
||||
</p>
|
||||
<p class="value">
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_humidity"
|
||||
)}
|
||||
@@ -75,22 +87,20 @@ class MoreInfoHumidifier extends LitElement {
|
||||
|
||||
<div class="controls">
|
||||
<ha-state-control-humidifier-humidity
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-state-control-humidifier-humidity>
|
||||
</div>
|
||||
|
||||
<ha-more-info-control-select-container>
|
||||
<ha-control-select-menu
|
||||
.hass=${hass}
|
||||
.label=${this.hass.localize("ui.card.humidifier.state")}
|
||||
.label=${this._localize("ui.card.humidifier.state")}
|
||||
.value=${this.stateObj.state}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@wa-select=${this._handleStateChanged}
|
||||
.options=${["off", "on"].map((fanState) => ({
|
||||
value: fanState,
|
||||
label: this.stateObj
|
||||
? this.hass.formatEntityState(this.stateObj, fanState)
|
||||
? this._formatters.formatEntityState(this.stateObj, fanState)
|
||||
: fanState,
|
||||
}))}
|
||||
>
|
||||
@@ -100,15 +110,14 @@ class MoreInfoHumidifier extends LitElement {
|
||||
${supportModes
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.hass=${hass}
|
||||
.label=${hass.localize("ui.card.humidifier.mode")}
|
||||
.label=${this._localize("ui.card.humidifier.mode")}
|
||||
.value=${stateObj.attributes.mode}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@wa-select=${this._handleModeChanged}
|
||||
.options=${stateObj.attributes.available_modes?.map((mode) => ({
|
||||
value: mode,
|
||||
label: stateObj
|
||||
? this.hass.formatEntityAttributeValue(
|
||||
? this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"mode",
|
||||
mode
|
||||
@@ -164,7 +173,7 @@ class MoreInfoHumidifier extends LitElement {
|
||||
data.entity_id = this.stateObj!.entity_id;
|
||||
const curState = this.stateObj;
|
||||
|
||||
await this.hass.callService("humidifier", service, data);
|
||||
await this._api.callService("humidifier", service, data);
|
||||
|
||||
// We reset stateObj to re-sync the inputs with the state. It will be out
|
||||
// of sync if our service call did not result in the entity to be turned
|
||||
|
||||
@@ -60,7 +60,6 @@ class MoreInfoLight extends LitElement {
|
||||
|
||||
private _renderEffectIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="effect"
|
||||
.attributeValue=${value}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiAccount, mdiAccountArrowRight, mdiWaterBoiler } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-attribute-icon";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
@@ -13,20 +17,29 @@ import {
|
||||
WaterHeaterEntityFeature,
|
||||
compareWaterHeaterOperationMode,
|
||||
} from "../../../data/water_heater";
|
||||
import { apiContext, formattersContext } from "../../../data/context";
|
||||
import "../../../state-control/water_heater/ha-state-control-water_heater-temperature";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-control-select-container";
|
||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
|
||||
@customElement("more-info-water_heater")
|
||||
class MoreInfoWaterHeater extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: WaterHeaterEntity;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
private _renderOperationModeIcon = (value: string) =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
attribute="operation_mode"
|
||||
.attributeValue=${value}
|
||||
@@ -57,13 +70,13 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
? html`
|
||||
<div>
|
||||
<p class="label">
|
||||
${this.hass.formatEntityAttributeName(
|
||||
${this._formatters.formatEntityAttributeName(
|
||||
this.stateObj,
|
||||
"current_temperature"
|
||||
)}
|
||||
</p>
|
||||
<p class="value">
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_temperature"
|
||||
)}
|
||||
@@ -74,7 +87,6 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
</div>
|
||||
<div class="controls">
|
||||
<ha-state-control-water_heater-temperature
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-state-control-water_heater-temperature>
|
||||
</div>
|
||||
@@ -82,8 +94,7 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
${supportOperationMode && stateObj.attributes.operation_list
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize("ui.card.water_heater.mode")}
|
||||
.label=${this._localize("ui.card.water_heater.mode")}
|
||||
.value=${stateObj.state}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
@wa-select=${this._handleOperationModeChanged}
|
||||
@@ -92,7 +103,7 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
.sort(compareWaterHeaterOperationMode)
|
||||
.map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.formatEntityState(stateObj, mode),
|
||||
label: this._formatters.formatEntityState(stateObj, mode),
|
||||
}))}
|
||||
.renderIcon=${this._renderOperationModeIcon}
|
||||
>
|
||||
@@ -103,7 +114,7 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
${supportAwayMode
|
||||
? html`
|
||||
<ha-control-select-menu
|
||||
.label=${this.hass.formatEntityAttributeName(
|
||||
.label=${this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
"away_mode"
|
||||
)}
|
||||
@@ -112,7 +123,7 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
@wa-select=${this._handleAwayModeChanged}
|
||||
.options=${["on", "off"].map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass.formatEntityAttributeValue(
|
||||
label: this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"away_mode",
|
||||
mode
|
||||
@@ -165,7 +176,7 @@ class MoreInfoWaterHeater extends LitElement {
|
||||
data.entity_id = this.stateObj!.entity_id;
|
||||
const curState = this.stateObj;
|
||||
|
||||
await this.hass.callService("water_heater", service, data);
|
||||
await this._api.callService("water_heater", service, data);
|
||||
|
||||
// We reset stateObj to re-sync the inputs with the state. It will be out
|
||||
// of sync if our service call did not result in the entity to be turned
|
||||
|
||||
@@ -13,6 +13,11 @@ import { computeShownAttributes } from "../../data/entity/entity_attributes";
|
||||
import type { ExtEntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-yaml-editor";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import type { FeatureEnum } from "../../common/entity/get_domain_features";
|
||||
import { getFeatures } from "../../common/entity/get_domain_features";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { titleCase } from "../../common/string/title-case";
|
||||
|
||||
interface DetailsViewParams {
|
||||
entityId: string;
|
||||
@@ -177,6 +182,12 @@ class HaMoreInfoDetails extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let featureEnum: FeatureEnum | undefined;
|
||||
if (this._stateObj?.attributes.supported_features !== undefined) {
|
||||
const domain = computeDomain(this.params!.entityId);
|
||||
featureEnum = getFeatures(domain);
|
||||
}
|
||||
|
||||
return attributes.map(
|
||||
(attribute) => html`
|
||||
<div class="data-entry">
|
||||
@@ -189,16 +200,37 @@ class HaMoreInfoDetails extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<div class="value">
|
||||
<ha-attribute-value
|
||||
.attribute=${attribute}
|
||||
.stateObj=${this._stateObj}
|
||||
></ha-attribute-value>
|
||||
${attribute === "supported_features" && featureEnum
|
||||
? this._renderFeatures(featureEnum, this._stateObj!)
|
||||
: html`
|
||||
<ha-attribute-value
|
||||
.attribute=${attribute}
|
||||
.stateObj=${this._stateObj}
|
||||
></ha-attribute-value>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
private _renderFeatures(
|
||||
featureEnum: FeatureEnum,
|
||||
stateObj: HassEntity
|
||||
): string {
|
||||
return (
|
||||
Object.entries(featureEnum)
|
||||
.filter(([_key, value]) => typeof value === "number")
|
||||
.map(([key, value]) =>
|
||||
supportsFeature(stateObj, value as number)
|
||||
? titleCase(key.replaceAll("_", "\u00A0").toLowerCase())
|
||||
: undefined
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join(", ") || this.hass.localize("ui.common.none")
|
||||
);
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: flex;
|
||||
|
||||
@@ -44,7 +44,7 @@ class DialogScheduleBlockInfo extends DirtyStateProviderMixin<ScheduleBlockInfo>
|
||||
selector: { time: { no_second: true } },
|
||||
},
|
||||
{
|
||||
name: "advanced_settings",
|
||||
name: "more_options",
|
||||
type: "expandable" as const,
|
||||
flatten: true,
|
||||
expanded: expand,
|
||||
@@ -157,9 +157,9 @@ class DialogScheduleBlockInfo extends DirtyStateProviderMixin<ScheduleBlockInfo>
|
||||
return this.hass!.localize("ui.dialogs.helper_settings.schedule.end");
|
||||
case "data":
|
||||
return this.hass!.localize("ui.dialogs.helper_settings.schedule.data");
|
||||
case "advanced_settings":
|
||||
case "more_options":
|
||||
return this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.advanced_settings"
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
);
|
||||
}
|
||||
return "";
|
||||
|
||||
@@ -125,7 +125,7 @@ class HaCounterForm extends LitElement {
|
||||
></ha-input>
|
||||
<ha-expansion-panel
|
||||
header=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.advanced_settings"
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
)}
|
||||
outlined
|
||||
>
|
||||
|
||||
@@ -93,7 +93,7 @@ class HaInputTextForm extends LitElement {
|
||||
></ha-icon-picker>
|
||||
<ha-expansion-panel
|
||||
header=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.advanced_settings"
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
)}
|
||||
outlined
|
||||
>
|
||||
|
||||
@@ -181,6 +181,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
.content=${this._config.state_content}
|
||||
.timeFormat=${this._config.time_format}
|
||||
.name=${name}
|
||||
>
|
||||
</state-display>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { LegacyStateFilter } from "../common/evaluate-filter";
|
||||
import type { Condition } from "../common/validate-condition";
|
||||
import type { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
import type { DisplayType } from "./hui-entity-badge";
|
||||
import type { TimestampRenderingFormat } from "../components/types";
|
||||
|
||||
export interface EntityFilterBadgeConfig extends LovelaceBadgeConfig {
|
||||
type: "entity-filter";
|
||||
@@ -53,6 +54,7 @@ export interface EntityBadgeConfig extends LovelaceBadgeConfig {
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
double_tap_action?: ActionConfig;
|
||||
time_format?: TimestampRenderingFormat;
|
||||
/**
|
||||
* @deprecated use `show_state`, `show_name`, `icon_type`
|
||||
*/
|
||||
|
||||
@@ -210,7 +210,6 @@ export abstract class HuiModeSelectCardFeatureBase<
|
||||
protected _renderOptionIcon(option: HuiModeSelectOption): TemplateResult<1> {
|
||||
return html`<ha-attribute-icon
|
||||
slot="graphic"
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${this._stateObj}
|
||||
.attribute=${this._attribute}
|
||||
.attributeValue=${option.value}
|
||||
@@ -219,7 +218,6 @@ export abstract class HuiModeSelectCardFeatureBase<
|
||||
|
||||
private _renderMenuIcon = (value: string): TemplateResult<1> =>
|
||||
html`<ha-attribute-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${this._stateObj}
|
||||
.attribute=${this._attribute}
|
||||
.attributeValue=${value}
|
||||
|
||||
@@ -151,7 +151,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
||||
prevent-interaction-on-scroll
|
||||
.showCurrentAsPrimary=${this._config.show_current_as_primary}
|
||||
show-secondary
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-control-humidifier-humidity>
|
||||
</div>
|
||||
|
||||
@@ -150,7 +150,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
})}
|
||||
prevent-interaction-on-scroll
|
||||
show-current
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-control-water_heater-temperature>`
|
||||
: html` <ha-state-control-climate-temperature
|
||||
@@ -160,7 +159,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
prevent-interaction-on-scroll
|
||||
.showCurrentAsPrimary=${this._config.show_current_as_primary}
|
||||
show-secondary
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-control-climate-temperature>`}
|
||||
</div>
|
||||
|
||||
@@ -264,6 +264,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
.content=${this._config.state_content}
|
||||
.timeFormat=${this._config.time_format}
|
||||
>
|
||||
</state-display>
|
||||
`;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ClimateEntity } from "../../../../../data/climate";
|
||||
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../../../../data/climate";
|
||||
import type { RenderBadgeFunction } from "./tile-badge";
|
||||
|
||||
export const renderClimateBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
export const renderClimateBadge: RenderBadgeFunction = (stateObj) => {
|
||||
const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action;
|
||||
|
||||
if (!hvacAction || hvacAction === "off") {
|
||||
@@ -23,11 +23,7 @@ export const renderClimateBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
),
|
||||
})}
|
||||
>
|
||||
<ha-attribute-icon
|
||||
.hass=${hass}
|
||||
.stateObj=${stateObj}
|
||||
attribute="hvac_action"
|
||||
>
|
||||
<ha-attribute-icon .stateObj=${stateObj} attribute="hvac_action">
|
||||
</ha-attribute-icon>
|
||||
</ha-tile-badge>
|
||||
`;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { HumidifierEntity } from "../../../../../data/humidifier";
|
||||
import { HUMIDIFIER_ACTION_MODE } from "../../../../../data/humidifier";
|
||||
import type { RenderBadgeFunction } from "./tile-badge";
|
||||
|
||||
export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
export const renderHumidifierBadge: RenderBadgeFunction = (stateObj) => {
|
||||
const action = (stateObj as HumidifierEntity).attributes.action;
|
||||
|
||||
if (!action || action === "off") {
|
||||
@@ -23,7 +23,7 @@ export const renderHumidifierBadge: RenderBadgeFunction = (stateObj, hass) => {
|
||||
),
|
||||
})}
|
||||
>
|
||||
<ha-attribute-icon .hass=${hass} .stateObj=${stateObj} attribute="action">
|
||||
<ha-attribute-icon .stateObj=${stateObj} attribute="action">
|
||||
</ha-attribute-icon>
|
||||
</ha-tile-badge>
|
||||
`;
|
||||
|
||||
@@ -666,6 +666,7 @@ export interface TileCardConfig extends LovelaceCardConfig {
|
||||
icon_double_tap_action?: ActionConfig;
|
||||
features?: LovelaceCardFeatureConfig[];
|
||||
features_position?: LovelaceCardFeaturePosition;
|
||||
time_format?: TimestampRenderingFormat;
|
||||
}
|
||||
|
||||
export interface HeadingCardConfig extends LovelaceCardConfig {
|
||||
|
||||
@@ -33,6 +33,8 @@ import { actionConfigStruct } from "../structs/action-struct";
|
||||
import { baseLovelaceBadgeConfig } from "../structs/base-badge-struct";
|
||||
import { entityNameStruct } from "../structs/entity-name-struct";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
|
||||
import { stateContentHasTimestamp } from "../../../../state-display/state-display";
|
||||
import "./hui-card-features-editor";
|
||||
|
||||
const badgeConfigStruct = assign(
|
||||
@@ -51,6 +53,7 @@ const badgeConfigStruct = assign(
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
time_format: optional(enums(TIMESTAMP_RENDERING_FORMATS)),
|
||||
image: optional(string()), // For old badge config support
|
||||
})
|
||||
);
|
||||
@@ -73,7 +76,7 @@ export class HuiEntityBadgeEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
(localize: LocalizeFunc, showTimeFormat: boolean) =>
|
||||
[
|
||||
{ name: "entity", selector: { entity: {} } },
|
||||
{
|
||||
@@ -157,6 +160,16 @@ export class HuiEntityBadgeEditor
|
||||
filter_entity: "entity",
|
||||
},
|
||||
},
|
||||
...(showTimeFormat
|
||||
? ([
|
||||
{
|
||||
name: "time_format",
|
||||
selector: {
|
||||
ui_time_format: {},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -214,7 +227,17 @@ export class HuiEntityBadgeEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema(this.hass!.localize);
|
||||
const entityId = this._config!.entity;
|
||||
|
||||
const showTimeFormat =
|
||||
!!entityId &&
|
||||
stateContentHasTimestamp(
|
||||
entityId,
|
||||
this.hass.states[entityId],
|
||||
this._config.state_content
|
||||
);
|
||||
|
||||
const schema = this._schema(this.hass!.localize, showTimeFormat);
|
||||
|
||||
const data = {
|
||||
...this._config,
|
||||
|
||||
@@ -40,6 +40,8 @@ import { entityNameStruct } from "../structs/entity-name-struct";
|
||||
import type { EditDetailElementEvent, EditSubElementEvent } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { getSupportedFeaturesType } from "./hui-card-features-editor";
|
||||
import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
|
||||
import { stateContentHasTimestamp } from "../../../../state-display/state-display";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@@ -60,6 +62,7 @@ const cardConfigStruct = assign(
|
||||
icon_double_tap_action: optional(actionConfigStruct),
|
||||
features: optional(array(any())),
|
||||
features_position: optional(enums(["bottom", "inline"])),
|
||||
time_format: optional(enums(TIMESTAMP_RENDERING_FORMATS)),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -89,7 +92,8 @@ export class HuiTileCardEditor
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
entityId: string | undefined,
|
||||
hideState: boolean
|
||||
hideState: boolean,
|
||||
showTimeFormat: boolean
|
||||
) =>
|
||||
[
|
||||
{ name: "entity", selector: { entity: {} } },
|
||||
@@ -155,6 +159,16 @@ export class HuiTileCardEditor
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
...(showTimeFormat
|
||||
? ([
|
||||
{
|
||||
name: "time_format",
|
||||
selector: {
|
||||
ui_time_format: {},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
{
|
||||
name: "content_layout",
|
||||
required: true,
|
||||
@@ -271,10 +285,19 @@ export class HuiTileCardEditor
|
||||
|
||||
const entityId = this._config!.entity;
|
||||
|
||||
const showTimeFormat =
|
||||
!this._config.hide_state &&
|
||||
stateContentHasTimestamp(
|
||||
entityId,
|
||||
this.hass.states[entityId],
|
||||
this._config.state_content
|
||||
);
|
||||
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
entityId,
|
||||
this._config.hide_state ?? false
|
||||
this._config.hide_state ?? false,
|
||||
showTimeFormat
|
||||
);
|
||||
|
||||
const vertical = this._config.vertical ?? false;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiMinus, mdiPlus, mdiWaterPercent } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
@@ -8,6 +10,8 @@ import { stateActive } from "../../common/entity/state_active";
|
||||
import { domainStateColorProperties } from "../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
import { consumeLocalize } from "../../common/decorators/consume-context-entry";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-big-number";
|
||||
import "../../components/ha-control-circular-slider";
|
||||
@@ -15,9 +19,9 @@ import "../../components/ha-outlined-icon-button";
|
||||
import "../../components/ha-svg-icon";
|
||||
import type { ClimateEntity } from "../../data/climate";
|
||||
import { ClimateEntityFeature } from "../../data/climate";
|
||||
import { apiContext, formattersContext } from "../../data/context";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { computeCssVariable } from "../../resources/css-variables";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
createStateControlCircularSliderController,
|
||||
stateControlCircularSliderStyle,
|
||||
@@ -25,10 +29,20 @@ import {
|
||||
|
||||
@customElement("ha-state-control-climate-humidity")
|
||||
export class HaStateControlClimateHumidity extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: ClimateEntity;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: "show-current", type: Boolean })
|
||||
public showCurrent = false;
|
||||
|
||||
@@ -74,7 +88,7 @@ export class HaStateControlClimateHumidity extends LitElement {
|
||||
private _debouncedCallService = debounce(() => this._callService(), 1000);
|
||||
|
||||
private _callService() {
|
||||
this.hass.callService("climate", "set_humidity", {
|
||||
this._api.callService("climate", "set_humidity", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
humidity: this._targetHumidity,
|
||||
});
|
||||
@@ -95,21 +109,21 @@ export class HaStateControlClimateHumidity extends LitElement {
|
||||
if (this.stateObj.state === UNAVAILABLE) {
|
||||
return html`
|
||||
<p class="label disabled">
|
||||
${this.hass.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
${this._formatters.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this._targetHumidity) {
|
||||
return html`
|
||||
<p class="label">${this.hass.formatEntityState(this.stateObj)}</p>
|
||||
<p class="label">
|
||||
${this._formatters.formatEntityState(this.stateObj)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<p class="label">
|
||||
${this.hass.localize("ui.card.climate.humidity_target")}
|
||||
</p>
|
||||
<p class="label">${this._localize("ui.card.climate.humidity_target")}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -142,7 +156,6 @@ export class HaStateControlClimateHumidity extends LitElement {
|
||||
.value=${humidity}
|
||||
unit="%"
|
||||
unit-position="bottom"
|
||||
.hass=${this.hass}
|
||||
.formatOptions=${formatOptions}
|
||||
></ha-big-number>
|
||||
`;
|
||||
@@ -157,7 +170,7 @@ export class HaStateControlClimateHumidity extends LitElement {
|
||||
<p class="label">
|
||||
<ha-svg-icon .path=${mdiWaterPercent}></ha-svg-icon>
|
||||
<span>
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_humidity",
|
||||
humidity
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiMinus, mdiPlus, mdiThermometer, mdiThermostat } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -23,8 +25,13 @@ import {
|
||||
CLIMATE_HVAC_ACTION_TO_MODE,
|
||||
ClimateEntityFeature,
|
||||
} from "../../data/climate";
|
||||
import {
|
||||
apiContext,
|
||||
configContext,
|
||||
formattersContext,
|
||||
internationalizationContext,
|
||||
} from "../../data/context";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
createStateControlCircularSliderController,
|
||||
stateControlCircularSliderStyle,
|
||||
@@ -44,10 +51,24 @@ const SLIDER_MODES: Record<HvacMode, ControlCircularSliderMode> = {
|
||||
|
||||
@customElement("ha-state-control-climate-temperature")
|
||||
export class HaStateControlClimateTemperature extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: ClimateEntity;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
private _config!: ContextType<typeof configContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@property({ attribute: "show-secondary", type: Boolean })
|
||||
public showSecondary = false;
|
||||
|
||||
@@ -77,7 +98,7 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
private get _step() {
|
||||
return (
|
||||
this.stateObj.attributes.target_temp_step ||
|
||||
(this.hass.config.unit_system.temperature === UNIT_F ? 1 : 0.5)
|
||||
(this._config.config.unit_system.temperature === UNIT_F ? 1 : 0.5)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,14 +140,14 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
|
||||
private _callService(type: string) {
|
||||
if (type === "high" || type === "low") {
|
||||
this.hass.callService("climate", "set_temperature", {
|
||||
this._api.callService("climate", "set_temperature", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
target_temp_low: this._targetTemperature.low,
|
||||
target_temp_high: this._targetTemperature.high,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.hass.callService("climate", "set_temperature", {
|
||||
this._api.callService("climate", "set_temperature", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
temperature: this._targetTemperature.value,
|
||||
});
|
||||
@@ -164,7 +185,7 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
if (this.stateObj.state === UNAVAILABLE) {
|
||||
return html`
|
||||
<p class="label disabled">
|
||||
${this.hass.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
${this._formatters.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
@@ -181,9 +202,12 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
return html`
|
||||
<p class="label">
|
||||
${action && action !== "off"
|
||||
? this.hass.formatEntityAttributeValue(this.stateObj, "hvac_action")
|
||||
? this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"hvac_action"
|
||||
)
|
||||
: isTemperatureDisplayed
|
||||
? this.hass.formatEntityState(this.stateObj)
|
||||
? this._formatters.formatEntityState(this.stateObj)
|
||||
: nothing}
|
||||
</p>
|
||||
`;
|
||||
@@ -237,14 +261,13 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
minimumFractionDigits: digits,
|
||||
};
|
||||
|
||||
const unit = hideUnit ? "" : this.hass.config.unit_system.temperature;
|
||||
const unit = hideUnit ? "" : this._config.config.unit_system.temperature;
|
||||
|
||||
if (style === "big") {
|
||||
return html`
|
||||
<ha-big-number
|
||||
.value=${temperature}
|
||||
.unit=${unit}
|
||||
.hass=${this.hass}
|
||||
.formatOptions=${formatOptions}
|
||||
></ha-big-number>
|
||||
`;
|
||||
@@ -252,10 +275,10 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
|
||||
const formatted = formatNumber(
|
||||
temperature,
|
||||
this.hass.locale,
|
||||
this._i18n.locale,
|
||||
formatOptions
|
||||
);
|
||||
return html`${formatted}${blankBeforeUnit(unit, this.hass.locale)}${unit}`;
|
||||
return html`${formatted}${blankBeforeUnit(unit, this._i18n.locale)}${unit}`;
|
||||
}
|
||||
|
||||
private _renderCurrent(temperature: number, style: "normal" | "big") {
|
||||
@@ -266,15 +289,14 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
return html`
|
||||
<ha-big-number
|
||||
.value=${temperature}
|
||||
.unit=${this.hass.config.unit_system.temperature}
|
||||
.hass=${this.hass}
|
||||
.unit=${this._config.config.unit_system.temperature}
|
||||
.formatOptions=${formatOptions}
|
||||
></ha-big-number>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_temperature",
|
||||
temperature
|
||||
@@ -321,7 +343,7 @@ export class HaStateControlClimateTemperature extends LitElement {
|
||||
if (this.stateObj.state !== UNAVAILABLE) {
|
||||
return html`
|
||||
<p class="primary-state">
|
||||
${this.hass.formatEntityState(this.stateObj)}
|
||||
${this._formatters.formatEntityState(this.stateObj)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiMinus, mdiPlus, mdiThermostat, mdiWaterPercent } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
@@ -14,12 +16,12 @@ import "../../components/ha-outlined-icon-button";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { DOMAIN_ATTRIBUTES_UNITS } from "../../data/entity/entity_attributes";
|
||||
import { apiContext, formattersContext } from "../../data/context";
|
||||
import type { HumidifierEntity } from "../../data/humidifier";
|
||||
import {
|
||||
HUMIDIFIER_ACTION_MODE,
|
||||
HumidifierEntityDeviceClass,
|
||||
} from "../../data/humidifier";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
createStateControlCircularSliderController,
|
||||
stateControlCircularSliderStyle,
|
||||
@@ -27,10 +29,16 @@ import {
|
||||
|
||||
@customElement("ha-state-control-humidifier-humidity")
|
||||
export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: HumidifierEntity;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@property({ attribute: "show-secondary", type: Boolean })
|
||||
public showSecondary = false;
|
||||
|
||||
@@ -79,7 +87,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
private _debouncedCallService = debounce(() => this._callService(), 1000);
|
||||
|
||||
private _callService() {
|
||||
this.hass.callService("humidifier", "set_humidity", {
|
||||
this._api.callService("humidifier", "set_humidity", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
humidity: this._targetHumidity,
|
||||
});
|
||||
@@ -100,7 +108,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
if (this.stateObj.state === UNAVAILABLE) {
|
||||
return html`
|
||||
<p class="label disabled">
|
||||
${this.hass.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
${this._formatters.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
@@ -115,9 +123,9 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
return html`
|
||||
<p class="label">
|
||||
${action && action !== "off"
|
||||
? this.hass.formatEntityAttributeValue(this.stateObj, "action")
|
||||
? this._formatters.formatEntityAttributeValue(this.stateObj, "action")
|
||||
: isHumidityDisplayed
|
||||
? this.hass.formatEntityState(this.stateObj)
|
||||
? this._formatters.formatEntityState(this.stateObj)
|
||||
: nothing}
|
||||
</p>
|
||||
`;
|
||||
@@ -156,7 +164,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
if (this.stateObj.state !== UNAVAILABLE) {
|
||||
return html`
|
||||
<p class="primary-state">
|
||||
${this.hass.formatEntityState(this.stateObj)}
|
||||
${this._formatters.formatEntityState(this.stateObj)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
@@ -201,7 +209,6 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
<ha-big-number
|
||||
.value=${humidity}
|
||||
.unit=${DOMAIN_ATTRIBUTES_UNITS.humidifier.current_humidity}
|
||||
.hass=${this.hass}
|
||||
.formatOptions=${formatOptions}
|
||||
unit-position="bottom"
|
||||
></ha-big-number>
|
||||
@@ -209,7 +216,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"humidity",
|
||||
humidity
|
||||
@@ -226,7 +233,6 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
<ha-big-number
|
||||
.value=${humidity}
|
||||
.unit=${DOMAIN_ATTRIBUTES_UNITS.humidifier.current_humidity}
|
||||
.hass=${this.hass}
|
||||
.formatOptions=${formatOptions}
|
||||
unit-position="bottom"
|
||||
></ha-big-number>
|
||||
@@ -234,7 +240,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_humidity",
|
||||
humidity
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiMinus, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
@@ -5,10 +7,12 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { UNIT_F } from "../../common/const";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { consumeLocalize } from "../../common/decorators/consume-context-entry";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-big-number";
|
||||
import "../../components/ha-control-circular-slider";
|
||||
@@ -17,7 +21,11 @@ import "../../components/ha-svg-icon";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { WaterHeaterEntity } from "../../data/water_heater";
|
||||
import { WaterHeaterEntityFeature } from "../../data/water_heater";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
apiContext,
|
||||
configContext,
|
||||
formattersContext,
|
||||
} from "../../data/context";
|
||||
import {
|
||||
createStateControlCircularSliderController,
|
||||
stateControlCircularSliderStyle,
|
||||
@@ -25,10 +33,24 @@ import {
|
||||
|
||||
@customElement("ha-state-control-water_heater-temperature")
|
||||
export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: WaterHeaterEntity;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
private _config!: ContextType<typeof configContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: "show-current", type: Boolean })
|
||||
public showCurrent = false;
|
||||
|
||||
@@ -49,7 +71,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
private get _step() {
|
||||
return (
|
||||
this.stateObj.attributes.target_temp_step ||
|
||||
(this.hass.config.unit_system.temperature === UNIT_F ? 1 : 0.5)
|
||||
(this._config.config.unit_system.temperature === UNIT_F ? 1 : 0.5)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +99,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
private _debouncedCallService = debounce(() => this._callService(), 1000);
|
||||
|
||||
private _callService() {
|
||||
this.hass.callService("water_heater", "set_temperature", {
|
||||
this._api.callService("water_heater", "set_temperature", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
temperature: this._targetTemperature,
|
||||
});
|
||||
@@ -98,7 +120,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
if (this.stateObj.state === UNAVAILABLE) {
|
||||
return html`
|
||||
<p class="label disabled">
|
||||
${this.hass.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
${this._formatters.formatEntityState(this.stateObj, UNAVAILABLE)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
@@ -111,12 +133,14 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
!this._targetTemperature
|
||||
) {
|
||||
return html`
|
||||
<p class="label">${this.hass.formatEntityState(this.stateObj)}</p>
|
||||
<p class="label">
|
||||
${this._formatters.formatEntityState(this.stateObj)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<p class="label">${this.hass.localize("ui.card.water_heater.target")}</p>
|
||||
<p class="label">${this._localize("ui.card.water_heater.target")}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -148,8 +172,7 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
return html`
|
||||
<ha-big-number
|
||||
.value=${temperature}
|
||||
.unit=${this.hass.config.unit_system.temperature}
|
||||
.hass=${this.hass}
|
||||
.unit=${this._config.config.unit_system.temperature}
|
||||
.formatOptions=${formatOptions}
|
||||
></ha-big-number>
|
||||
`;
|
||||
@@ -162,9 +185,9 @@ export class HaStateControlWaterHeaterTemperature extends LitElement {
|
||||
|
||||
return html`
|
||||
<p class="label">
|
||||
${this.hass.localize("ui.card.water_heater.currently")}
|
||||
${this._localize("ui.card.water_heater.currently")}
|
||||
<span>
|
||||
${this.hass.formatEntityAttributeValue(
|
||||
${this._formatters.formatEntityAttributeValue(
|
||||
this.stateObj,
|
||||
"current_temperature",
|
||||
temperature
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
@@ -5,7 +6,10 @@ import { customElement, property } from "lit/decorators";
|
||||
import { join } from "lit/directives/join";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { STRINGS_SEPARATOR_DOT } from "../common/const";
|
||||
import {
|
||||
STRINGS_SEPARATOR_DOT,
|
||||
TIMESTAMP_STATE_DOMAINS,
|
||||
} from "../common/const";
|
||||
import "../components/ha-relative-time";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../data/entity/entity";
|
||||
import {
|
||||
@@ -16,14 +20,7 @@ import type { UpdateEntity } from "../data/update";
|
||||
import { computeUpdateStateDisplay } from "../data/update";
|
||||
import "../panels/lovelace/components/hui-timestamp-display";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
const TIMESTAMP_STATE_DOMAINS = [
|
||||
"button",
|
||||
"infrared",
|
||||
"input_button",
|
||||
"radio_frequency",
|
||||
"scene",
|
||||
];
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
|
||||
export const STATE_DISPLAY_SPECIAL_CONTENT = [
|
||||
"remaining_time",
|
||||
@@ -60,6 +57,55 @@ export const DEFAULT_STATE_CONTENT_DOMAINS: Record<string, StateContent> = {
|
||||
valve: ["state", "current_position"],
|
||||
};
|
||||
|
||||
const TIMESTAMP_STATE_PROPS = ["last_updated", "last_changed"];
|
||||
|
||||
const TIMESTAMP_CONTENTS = [...TIMESTAMP_STATE_PROPS, "last_triggered"];
|
||||
|
||||
const TIMESTAMP_DOMAIN_CONTENTS = {
|
||||
calendar: ["start_time", "end_time"],
|
||||
input_datetime: ["timestamp"],
|
||||
sun: [
|
||||
"next_dawn",
|
||||
"next_dusk",
|
||||
"next_midnight",
|
||||
"next_noon",
|
||||
"next_rising",
|
||||
"next_setting",
|
||||
],
|
||||
};
|
||||
|
||||
export const stateContentHasTimestamp = (
|
||||
entityId?: string,
|
||||
stateObj?: HassEntity,
|
||||
content?: StateContent
|
||||
): boolean => {
|
||||
const contentArray = ensureArray(content);
|
||||
if (content && contentArray.some((c) => TIMESTAMP_CONTENTS.includes(c))) {
|
||||
return true;
|
||||
}
|
||||
if (!entityId) {
|
||||
return false;
|
||||
}
|
||||
const domain = computeDomain(entityId);
|
||||
if (!content || contentArray.includes("state")) {
|
||||
if (TIMESTAMP_STATE_DOMAINS.includes(domain)) {
|
||||
return true;
|
||||
}
|
||||
if (stateObj) {
|
||||
const sensorDeviceClass =
|
||||
domain === "sensor" ? stateObj.attributes.device_class : "";
|
||||
if (SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(sensorDeviceClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
TIMESTAMP_DOMAIN_CONTENTS[domain] &&
|
||||
content &&
|
||||
contentArray.some((c) => TIMESTAMP_DOMAIN_CONTENTS[domain].includes(c))
|
||||
);
|
||||
};
|
||||
|
||||
@customElement("state-display")
|
||||
class StateDisplay extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -70,6 +116,8 @@ class StateDisplay extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public name?: string;
|
||||
|
||||
@property({ attribute: false }) public timeFormat?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "dash-unavailable" })
|
||||
public dashUnavailable?: boolean;
|
||||
|
||||
@@ -77,9 +125,24 @@ class StateDisplay extends LitElement {
|
||||
return this;
|
||||
}
|
||||
|
||||
private _normalizeContent = memoizeOne(
|
||||
(content?: StateContent): StateContent | undefined =>
|
||||
content == null
|
||||
? undefined
|
||||
: ensureArray(content).map((s) => {
|
||||
if (s === "last-updated") return "last_updated";
|
||||
if (s === "last-changed") return "last_changed";
|
||||
return s;
|
||||
})
|
||||
);
|
||||
|
||||
private get _content(): StateContent {
|
||||
const domain = computeStateDomain(this.stateObj);
|
||||
return this.content ?? DEFAULT_STATE_CONTENT_DOMAINS[domain] ?? "state";
|
||||
return (
|
||||
this._normalizeContent(this.content) ??
|
||||
DEFAULT_STATE_CONTENT_DOMAINS[domain] ??
|
||||
"state"
|
||||
);
|
||||
}
|
||||
|
||||
private _computeContent(
|
||||
@@ -105,10 +168,11 @@ class StateDisplay extends LitElement {
|
||||
<hui-timestamp-display
|
||||
.hass=${this.hass}
|
||||
.ts=${new Date(stateObj.state)}
|
||||
.format=${this.stateObj.attributes.device_class ===
|
||||
.format=${this.timeFormat ||
|
||||
(this.stateObj.attributes.device_class ===
|
||||
SENSOR_DEVICE_CLASS_UPTIME
|
||||
? "total"
|
||||
: "relative"}
|
||||
: "relative")}
|
||||
capitalize
|
||||
></hui-timestamp-display>
|
||||
`;
|
||||
@@ -129,42 +193,26 @@ class StateDisplay extends LitElement {
|
||||
return this.hass.formatEntityName(stateObj, { type }) || undefined;
|
||||
}
|
||||
|
||||
let relativeDateTime: string | Date | undefined;
|
||||
let relativeDateTime: string | number | undefined;
|
||||
|
||||
// Check last-changed for backwards compatibility
|
||||
if (content === "last_changed" || content === "last-changed") {
|
||||
relativeDateTime = stateObj.last_changed;
|
||||
}
|
||||
// Check last_updated for backwards compatibility
|
||||
if (content === "last_updated" || content === "last-updated") {
|
||||
relativeDateTime = stateObj.last_updated;
|
||||
}
|
||||
if (domain === "input_datetime" && content === "timestamp") {
|
||||
relativeDateTime = new Date(stateObj.attributes.timestamp * 1000);
|
||||
}
|
||||
|
||||
if (
|
||||
content === "last_triggered" ||
|
||||
(domain === "calendar" &&
|
||||
(content === "start_time" || content === "end_time")) ||
|
||||
(domain === "sun" &&
|
||||
(content === "next_dawn" ||
|
||||
content === "next_dusk" ||
|
||||
content === "next_midnight" ||
|
||||
content === "next_noon" ||
|
||||
content === "next_rising" ||
|
||||
content === "next_setting"))
|
||||
if (TIMESTAMP_STATE_PROPS.includes(content)) {
|
||||
relativeDateTime = stateObj[content];
|
||||
} else if (domain === "input_datetime" && content === "timestamp") {
|
||||
relativeDateTime = stateObj.attributes.timestamp * 1000;
|
||||
} else if (
|
||||
TIMESTAMP_CONTENTS.includes(content) ||
|
||||
TIMESTAMP_DOMAIN_CONTENTS[domain]?.includes(content)
|
||||
) {
|
||||
relativeDateTime = stateObj.attributes[content];
|
||||
}
|
||||
|
||||
if (relativeDateTime) {
|
||||
return html`
|
||||
<ha-relative-time
|
||||
.datetime=${relativeDateTime}
|
||||
capitalize
|
||||
></ha-relative-time>
|
||||
`;
|
||||
if (relativeDateTime || relativeDateTime === 0) {
|
||||
return html`<hui-timestamp-display
|
||||
.hass=${this.hass}
|
||||
.ts=${new Date(relativeDateTime)}
|
||||
.format=${this.timeFormat}
|
||||
capitalize
|
||||
></hui-timestamp-display>`;
|
||||
}
|
||||
|
||||
const specialContent = (STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain] ??
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"save": "Save",
|
||||
"apply": "Apply",
|
||||
"add": "Add",
|
||||
"auto": "Auto",
|
||||
"create": "Create",
|
||||
"edit": "Edit",
|
||||
"edit_item": "Edit {name}",
|
||||
@@ -496,7 +497,8 @@
|
||||
"replace": "Replace",
|
||||
"append": "Append",
|
||||
"supports_markdown": "Supports {markdown_help_link}",
|
||||
"markdown": "Markdown"
|
||||
"markdown": "Markdown",
|
||||
"none": "None"
|
||||
},
|
||||
"components": {
|
||||
"input": {
|
||||
@@ -799,6 +801,15 @@
|
||||
"floor": "Floor"
|
||||
}
|
||||
},
|
||||
"time-format-picker": {
|
||||
"formats": {
|
||||
"relative": "Relative",
|
||||
"total": "Total",
|
||||
"datetime": "Date and time",
|
||||
"time": "Time",
|
||||
"date": "Date"
|
||||
}
|
||||
},
|
||||
"subpage-data-table": {
|
||||
"filters": "Filters",
|
||||
"show_results": "Show {number} results",
|
||||
@@ -2066,7 +2077,7 @@
|
||||
"generic": {
|
||||
"name": "Name",
|
||||
"icon": "Icon",
|
||||
"advanced_settings": "Advanced settings"
|
||||
"more_options": "[%key:ui::dialogs::restart::more_options%]"
|
||||
},
|
||||
"input_datetime": {
|
||||
"date": "Date",
|
||||
@@ -5194,7 +5205,7 @@
|
||||
"context_user_picked": "User firing event",
|
||||
"context_user_pick": "Select user",
|
||||
"description": {
|
||||
"picker": "Triggers when an event is being received (event is an advanced concept in Home Assistant).",
|
||||
"picker": "Triggers when an event is being received (events are a building block used across Home Assistant).",
|
||||
"full": "When {eventTypes} event is fired"
|
||||
}
|
||||
},
|
||||
@@ -5695,7 +5706,7 @@
|
||||
"event": "[%key:ui::panel::config::automation::editor::triggers::type::event::event_type%]",
|
||||
"event_data": "[%key:ui::panel::config::automation::editor::triggers::type::event::event_data%]",
|
||||
"description": {
|
||||
"picker": "Fires an event manually (event is an advanced concept in Home Assistant).",
|
||||
"picker": "Fires an event manually (events are a building block used across Home Assistant).",
|
||||
"full": "Manually fire event {name}",
|
||||
"template": "based on a template"
|
||||
}
|
||||
@@ -9663,6 +9674,7 @@
|
||||
"show_state": "Show state",
|
||||
"show_last_changed": "Show last changed",
|
||||
"tap_action": "Tap behavior",
|
||||
"time_format": "Time format",
|
||||
"interactions": "Interactions",
|
||||
"title": "Title",
|
||||
"theme": "Theme",
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
import { featureClassNames } from "../../../src/common/entity/feature_class_names";
|
||||
|
||||
describe("featureClassNames", () => {
|
||||
const classNames = {
|
||||
1: "has-feature_a",
|
||||
2: "has-feature_b",
|
||||
4: "has-feature_c",
|
||||
8: "has-feature_d",
|
||||
};
|
||||
|
||||
it("Skips null states", () => {
|
||||
const stateObj = null;
|
||||
assert.strictEqual(featureClassNames(stateObj!, classNames), "");
|
||||
});
|
||||
|
||||
it("Matches no features", () => {
|
||||
// eslint-disable-next-line
|
||||
const stateObj = <HassEntity>{
|
||||
attributes: {
|
||||
supported_features: 64,
|
||||
},
|
||||
};
|
||||
assert.strictEqual(featureClassNames(stateObj, classNames), "");
|
||||
});
|
||||
|
||||
it("Matches one feature", () => {
|
||||
// eslint-disable-next-line
|
||||
const stateObj = <HassEntity>{
|
||||
attributes: {
|
||||
supported_features: 72,
|
||||
},
|
||||
};
|
||||
assert.strictEqual(
|
||||
featureClassNames(stateObj, classNames),
|
||||
"has-feature_d"
|
||||
);
|
||||
});
|
||||
|
||||
it("Matches two features", () => {
|
||||
// eslint-disable-next-line
|
||||
const stateObj = <HassEntity>{
|
||||
attributes: {
|
||||
supported_features: 73,
|
||||
},
|
||||
};
|
||||
assert.strictEqual(
|
||||
featureClassNames(stateObj, classNames),
|
||||
"has-feature_a has-feature_d"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user