diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index a4e45e57ed..d199c4f7b6 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -14,9 +14,9 @@ import { import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; +import { formatAttributeName } from "../../data/entity_attributes"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; -import { formatAttributeName } from "../../util/hass-attributes-util"; import "../ha-icon-button"; import "../ha-svg-icon"; import "./state-badge"; diff --git a/src/components/ha-attributes.ts b/src/components/ha-attributes.ts index 5e47290df7..0c137a983b 100644 --- a/src/components/ha-attributes.ts +++ b/src/components/ha-attributes.ts @@ -1,12 +1,14 @@ import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { haStyle } from "../resources/styles"; -import { HomeAssistant } from "../types"; -import hassAttributeUtil, { +import { formatAttributeName, formatAttributeValue, -} from "../util/hass-attributes-util"; + STATE_ATTRIBUTES, +} from "../data/entity_attributes"; +import { haStyle } from "../resources/styles"; +import { HomeAssistant } from "../types"; + import "./ha-expansion-panel"; @customElement("ha-attributes") @@ -25,7 +27,7 @@ class HaAttributes extends LitElement { } const attributes = this.computeDisplayAttributes( - Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat( + STATE_ATTRIBUTES.concat( this.extraFilters ? this.extraFilters.split(",") : [] ) ); diff --git a/src/data/entity_attributes.ts b/src/data/entity_attributes.ts new file mode 100644 index 0000000000..faa42e8fab --- /dev/null +++ b/src/data/entity_attributes.ts @@ -0,0 +1,105 @@ +import { html, TemplateResult } from "lit"; +import { until } from "lit/directives/until"; +import checkValidDate from "../common/datetime/check_valid_date"; +import { formatDate } from "../common/datetime/format_date"; +import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time"; +import { formatNumber } from "../common/number/format_number"; +import { capitalizeFirstLetter } from "../common/string/capitalize-first-letter"; +import { isDate } from "../common/string/is_date"; +import { isTimestamp } from "../common/string/is_timestamp"; +import { HomeAssistant } from "../types"; + +let jsYamlPromise: Promise; + +export const STATE_ATTRIBUTES = [ + "assumed_state", + "attribution", + "custom_ui_more_info", + "custom_ui_state_card", + "device_class", + "editable", + "emulated_hue_name", + "emulated_hue", + "entity_picture", + "friendly_name", + "haaska_hidden", + "haaska_name", + "icon", + "initial_state", + "last_reset", + "restored", + "state_class", + "supported_features", + "unit_of_measurement", +]; + +// Convert from internal snake_case format to user-friendly format +export function formatAttributeName(value: string): string { + value = value + .replace(/_/g, " ") + .replace(/\bid\b/g, "ID") + .replace(/\bip\b/g, "IP") + .replace(/\bmac\b/g, "MAC") + .replace(/\bgps\b/g, "GPS"); + return capitalizeFirstLetter(value); +} + +export function formatAttributeValue( + hass: HomeAssistant, + value: any +): string | TemplateResult { + if (value === null) { + return "—"; + } + + // YAML handling + if ( + (Array.isArray(value) && value.some((val) => val instanceof Object)) || + (!Array.isArray(value) && value instanceof Object) + ) { + if (!jsYamlPromise) { + jsYamlPromise = import("../resources/js-yaml-dump"); + } + const yaml = jsYamlPromise.then((jsYaml) => jsYaml.dump(value)); + return html`
${until(yaml, "")}
`; + } + + if (typeof value === "number") { + return formatNumber(value, hass.locale); + } + + if (typeof value === "string") { + // URL handling + if (value.startsWith("http")) { + try { + // If invalid URL, exception will be raised + const url = new URL(value); + if (url.protocol === "http:" || url.protocol === "https:") + return html`${value}`; + } catch (_) { + // Nothing to do here + } + } + + // Date handling + if (isDate(value, true)) { + // Timestamp handling + if (isTimestamp(value)) { + const date = new Date(value); + if (checkValidDate(date)) { + return formatDateTimeWithSeconds(date, hass.locale); + } + } + + // Value was not a timestamp, so only do date formatting + const date = new Date(value); + if (checkValidDate(date)) { + return formatDate(date, hass.locale); + } + } + } + + return Array.isArray(value) ? value.join(", ") : value; +} diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index 890f95dec2..95c1916bff 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -23,8 +23,8 @@ import { iconColorCSS } from "../../../common/style/icon_color_css"; import "../../../components/ha-card"; import "../../../components/ha-icon"; import { UNAVAILABLE_STATES } from "../../../data/entity"; +import { formatAttributeValue } from "../../../data/entity_attributes"; import { HomeAssistant } from "../../../types"; -import { formatAttributeValue } from "../../../util/hass-attributes-util"; import { computeCardSize } from "../common/compute-card-size"; import { findEntities } from "../common/find-entities"; import { hasConfigOrEntityChanged } from "../common/has-changed"; diff --git a/src/panels/lovelace/special-rows/hui-attribute-row.ts b/src/panels/lovelace/special-rows/hui-attribute-row.ts index 3b7a7a62f2..fdae9a50c1 100644 --- a/src/panels/lovelace/special-rows/hui-attribute-row.ts +++ b/src/panels/lovelace/special-rows/hui-attribute-row.ts @@ -9,8 +9,8 @@ import { import { customElement, property, state } from "lit/decorators"; import checkValidDate from "../../../common/datetime/check_valid_date"; import { formatNumber } from "../../../common/number/format_number"; +import { formatAttributeValue } from "../../../data/entity_attributes"; import { HomeAssistant } from "../../../types"; -import { formatAttributeValue } from "../../../util/hass-attributes-util"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import "../components/hui-timestamp-display"; diff --git a/src/util/hass-attributes-util.ts b/src/util/hass-attributes-util.ts deleted file mode 100644 index 94523dc639..0000000000 --- a/src/util/hass-attributes-util.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { html, TemplateResult } from "lit"; -import { until } from "lit/directives/until"; -import checkValidDate from "../common/datetime/check_valid_date"; -import { formatDate } from "../common/datetime/format_date"; -import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time"; -import { formatNumber } from "../common/number/format_number"; -import { capitalizeFirstLetter } from "../common/string/capitalize-first-letter"; -import { isDate } from "../common/string/is_date"; -import { isTimestamp } from "../common/string/is_timestamp"; -import { HomeAssistant } from "../types"; - -let jsYamlPromise: Promise; - -const hassAttributeUtil = { - DOMAIN_DEVICE_CLASS: { - binary_sensor: [ - "battery", - "carbon_monoxide", - "cold", - "connectivity", - "door", - "garage_door", - "gas", - "heat", - "light", - "lock", - "moisture", - "motion", - "moving", - "occupancy", - "opening", - "plug", - "power", - "presence", - "problem", - "running", - "safety", - "smoke", - "sound", - "tamper", - "vibration", - "window", - ], - button: ["restart", "update"], - cover: [ - "awning", - "blind", - "curtain", - "damper", - "door", - "garage", - "gate", - "shade", - "shutter", - "window", - ], - humidifier: ["dehumidifier", "humidifier"], - sensor: [ - "apparent_power", - "aqi", - "battery", - "carbon_dioxide", - "carbon_monoxide", - "current", - "date", - "energy", - "gas", - "humidity", - "illuminance", - "monetary", - "nitrogen_dioxide", - "nitrogen_monoxide", - "nitrous_oxide", - "ozone", - "pm1", - "pm10", - "pm25", - "power", - "power_factor", - "pressure", - "reactive_power", - "signal_strength", - "sulphur_dioxide", - "temperature", - "timestamp", - "volatile_organic_compounds", - "voltage", - ], - switch: ["switch", "outlet"], - }, - UNKNOWN_TYPE: "json", - ADD_TYPE: "key-value", - TYPE_TO_TAG: { - string: "ha-customize-string", - json: "ha-customize-string", - icon: "ha-customize-icon", - boolean: "ha-customize-boolean", - array: "ha-customize-array", - "key-value": "ha-customize-key-value", - }, - LOGIC_STATE_ATTRIBUTES: {}, -}; - -// Attributes here serve dual purpose: -// 1) Any key of this object won't be shown in more-info window. -// 2) Any key which has value other than undefined will appear in customization -// config according to its value. -hassAttributeUtil.LOGIC_STATE_ATTRIBUTES = { - entity_picture: undefined, - friendly_name: { type: "string", description: "Name" }, - icon: { type: "icon" }, - emulated_hue: { - type: "boolean", - domains: ["emulated_hue"], - }, - emulated_hue_name: { - type: "string", - domains: ["emulated_hue"], - }, - haaska_hidden: undefined, - haaska_name: undefined, - supported_features: undefined, - attribution: undefined, - restored: undefined, - editable: undefined, - custom_ui_more_info: { type: "string" }, - custom_ui_state_card: { type: "string" }, - device_class: { - type: "array", - options: hassAttributeUtil.DOMAIN_DEVICE_CLASS, - description: "Device class", - domains: ["binary_sensor", "cover", "humidifier", "sensor", "switch"], - }, - state_class: { - type: "array", - options: { sensor: ["measurement", "total", "total_increasing"] }, - description: "State class", - domains: ["sensor"], - }, - last_reset: undefined, - assumed_state: { - type: "boolean", - domains: [ - "switch", - "light", - "cover", - "climate", - "fan", - "humidifier", - "group", - "water_heater", - ], - }, - initial_state: { - type: "string", - domains: ["automation"], - }, - unit_of_measurement: { type: "string" }, -}; - -export default hassAttributeUtil; - -// Convert from internal snake_case format to user-friendly format -export function formatAttributeName(value: string): string { - value = value - .replace(/_/g, " ") - .replace(/\bid\b/g, "ID") - .replace(/\bip\b/g, "IP") - .replace(/\bmac\b/g, "MAC") - .replace(/\bgps\b/g, "GPS"); - return capitalizeFirstLetter(value); -} - -export function formatAttributeValue( - hass: HomeAssistant, - value: any -): string | TemplateResult { - if (value === null) { - return "—"; - } - - // YAML handling - if ( - (Array.isArray(value) && value.some((val) => val instanceof Object)) || - (!Array.isArray(value) && value instanceof Object) - ) { - if (!jsYamlPromise) { - jsYamlPromise = import("../resources/js-yaml-dump"); - } - const yaml = jsYamlPromise.then((jsYaml) => jsYaml.dump(value)); - return html`
${until(yaml, "")}
`; - } - - if (typeof value === "number") { - return formatNumber(value, hass.locale); - } - - if (typeof value === "string") { - // URL handling - if (value.startsWith("http")) { - try { - // If invalid URL, exception will be raised - const url = new URL(value); - if (url.protocol === "http:" || url.protocol === "https:") - return html`${value}`; - } catch (_) { - // Nothing to do here - } - } - - // Date handling - if (isDate(value, true)) { - // Timestamp handling - if (isTimestamp(value)) { - const date = new Date(value); - if (checkValidDate(date)) { - return formatDateTimeWithSeconds(date, hass.locale); - } - } - - // Value was not a timestamp, so only do date formatting - const date = new Date(value); - if (checkValidDate(date)) { - return formatDate(date, hass.locale); - } - } - } - - return Array.isArray(value) ? value.join(", ") : value; -}