diff --git a/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts b/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts index 3a96be359..130203e77 100644 --- a/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts +++ b/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts @@ -10,13 +10,13 @@ import { assert } from "superstruct"; import setupCustomlocalize from "../../localize"; import "../../shared/editor/layout-picker"; import { configElementStyle } from "../../utils/editor-styles"; +import { stateIcon } from "../../utils/icons/state-icon"; import { EditorTarget } from "../../utils/lovelace/editor/types"; import { alarmControlPanelCardCardConfigStruct, AlarmControlPanelCardConfig, } from "./alarm-control-panel-card-config"; import { ALARM_CONTROl_PANEL_CARD_EDITOR_NAME, ALARM_CONTROl_PANEL_ENTITY_DOMAINS } from "./const"; -import { getStateIcon } from "./utils"; const DOMAINS = [...ALARM_CONTROl_PANEL_ENTITY_DOMAINS]; @@ -46,8 +46,8 @@ export class SwitchCardEditor extends LitElement implements LovelaceCardEditor { } const dir = computeRTLDirection(this.hass); - const entityState = this._config.entity ? this.hass.states[this._config.entity] : undefined; - const entityIcon = entityState ? getStateIcon(entityState.state) : undefined; + const entity = this._config.entity ? this.hass.states[this._config.entity] : undefined; + const entityIcon = entity ? stateIcon(entity) : undefined; const states = [ "armed_home", diff --git a/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts b/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts index 684841d19..846041c85 100644 --- a/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts +++ b/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts @@ -20,6 +20,8 @@ import "../../shared/state-item"; import { cardStyle } from "../../utils/card-styles"; import { registerCustomCard } from "../../utils/custom-cards"; import { actionHandler } from "../../utils/directives/action-handler-directive"; +import { alarmPanelIconAction } from "../../utils/icons/alarm-panel-icon"; +import { stateIcon } from "../../utils/icons/state-icon"; import { getLayoutFromConfig } from "../../utils/layout"; import { AlarmControlPanelCardConfig } from "./alarm-control-panel-card-config"; import "./alarm-control-panel-card-editor"; @@ -30,7 +32,6 @@ import { } from "./const"; import { getStateColor, - getStateIcon, getStateService, isActionsAvailable, isDisarmed, @@ -142,7 +143,7 @@ export class AlarmControlPanelCard extends LitElement implements LovelaceCard { const entity = this.hass.states[entity_id]; const name = this._config.name ?? entity.attributes.friendly_name; - const icon = this._config.icon ?? getStateIcon(entity.state); + const icon = this._config.icon ?? stateIcon(entity); const color = getStateColor(entity.state); const shapePulse = shouldPulse(entity.state); const layout = getLayoutFromConfig(this._config); @@ -209,7 +210,7 @@ export class AlarmControlPanelCard extends LitElement implements LovelaceCard { ${actions.map( (action) => html` this._onTap(e, action.state)} .disabled=${!isActionEnabled} > diff --git a/src/cards/alarm-control-panel-card/const.ts b/src/cards/alarm-control-panel-card/const.ts index 07c747fda..264edf6dc 100644 --- a/src/cards/alarm-control-panel-card/const.ts +++ b/src/cards/alarm-control-panel-card/const.ts @@ -4,20 +4,6 @@ export const ALARM_CONTROl_PANEL_CARD_NAME = `${PREFIX_NAME}-alarm-control-panel export const ALARM_CONTROl_PANEL_CARD_EDITOR_NAME = `${ALARM_CONTROl_PANEL_CARD_NAME}-editor`; export const ALARM_CONTROl_PANEL_ENTITY_DOMAINS = ["alarm_control_panel"]; -export const ALARM_CONTROL_PANEL_CARD_STATE_ICON = { - disarmed: "mdi:shield-off-outline", - arming: "mdi:shield-sync-outline", - armed_away: "mdi:shield-lock-outline", - armed_home: "mdi:shield-home-outline", - armed_night: "mdi:shield-moon-outline", - armed_vacation: "mdi:shield-airplane-outline", - armed_custom_bypass: "mdi:shield-half-full", - pending: "mdi:shield-sync", - triggered: "mdi:bell-ring-outline", - unavailable: "mdi:bell-remove-outline", -}; -export const ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_ICON = "mdi:shield-lock-outline"; - export const ALARM_CONTROL_PANEL_CARD_STATE_COLOR = { disarmed: "var(--rgb-state-alarm-disarmed)", armed: "var(--rgb-state-alarm-armed)", @@ -35,7 +21,3 @@ export const ALARM_CONTROL_PANEL_CARD_STATE_SERVICE = { armed_vacation: "alarm_arm_vacation", armed_custom_bypass: "alarm_arm_custom_bypass", }; - -/* -armed_away, armed_home, armed_night, arming, disarmed, pending, triggered and unavailable -*/ diff --git a/src/cards/alarm-control-panel-card/utils.ts b/src/cards/alarm-control-panel-card/utils.ts index 1f4e7cf48..f182d3c03 100644 --- a/src/cards/alarm-control-panel-card/utils.ts +++ b/src/cards/alarm-control-panel-card/utils.ts @@ -1,9 +1,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_COLOR, - ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_ICON, ALARM_CONTROL_PANEL_CARD_STATE_COLOR, - ALARM_CONTROL_PANEL_CARD_STATE_ICON, ALARM_CONTROL_PANEL_CARD_STATE_SERVICE, } from "./const"; @@ -14,18 +12,12 @@ export function getStateColor(state: string): string { ); } -export function getStateIcon(state: string): string { - return ( - ALARM_CONTROL_PANEL_CARD_STATE_ICON[state] || ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_ICON - ); -} - export function getStateService(state: string): string | undefined { return ALARM_CONTROL_PANEL_CARD_STATE_SERVICE[state]; } -export function shouldPulse(state:string): boolean { - return ["arming", "triggered", "pending", "unavailable"].indexOf(state) >= 0 +export function shouldPulse(state: string): boolean { + return ["arming", "triggered", "pending", "unavailable"].indexOf(state) >= 0; } export function isActionsAvailable(entity: HassEntity) { diff --git a/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts b/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts index bfa4bfb76..285bca2e5 100644 --- a/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts +++ b/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts @@ -1,10 +1,11 @@ -import { fireEvent, HomeAssistant, stateIcon } from "custom-card-helpers"; +import { fireEvent, HomeAssistant } from "custom-card-helpers"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import setupCustomlocalize from "../../../localize"; import "../../../shared/editor/color-picker"; import "../../../shared/editor/info-picker"; import { configElementStyle } from "../../../utils/editor-styles"; +import { stateIcon } from "../../../utils/icons/state-icon"; import { computeChipEditorComponentName } from "../../../utils/lovelace/chip/chip-element"; import { AlarmControlPanelChipConfig } from "../../../utils/lovelace/chip/types"; import { EditorTarget } from "../../../utils/lovelace/editor/types"; @@ -66,6 +67,17 @@ export class AlarmControlPanelChipEditor extends LitElement implements LovelaceC > +
+ +
diff --git a/src/cards/cover-card/cover-card-editor.ts b/src/cards/cover-card/cover-card-editor.ts index 502cce70b..a48a37374 100644 --- a/src/cards/cover-card/cover-card-editor.ts +++ b/src/cards/cover-card/cover-card-editor.ts @@ -3,7 +3,6 @@ import { fireEvent, HomeAssistant, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -11,6 +10,7 @@ import { assert } from "superstruct"; import setupCustomlocalize from "../../localize"; import "../../shared/editor/layout-picker"; import { configElementStyle } from "../../utils/editor-styles"; +import { stateIcon } from "../../utils/icons/state-icon"; import { EditorTarget } from "../../utils/lovelace/editor/types"; import { COVER_CARD_EDITOR_NAME, COVER_ENTITY_DOMAINS } from "./const"; import { CoverCardConfig, coverCardConfigStruct } from "./cover-card-config"; diff --git a/src/cards/cover-card/cover-card.ts b/src/cards/cover-card/cover-card.ts index 0f6977b1a..2f451858e 100644 --- a/src/cards/cover-card/cover-card.ts +++ b/src/cards/cover-card/cover-card.ts @@ -6,7 +6,6 @@ import { HomeAssistant, LovelaceCard, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; @@ -21,6 +20,7 @@ import { cardStyle } from "../../utils/card-styles"; import { registerCustomCard } from "../../utils/custom-cards"; import { actionHandler } from "../../utils/directives/action-handler-directive"; import { isActive } from "../../utils/entity"; +import { stateIcon } from "../../utils/icons/state-icon"; import { getLayoutFromConfig, Layout } from "../../utils/layout"; import { COVER_CARD_EDITOR_NAME, COVER_CARD_NAME, COVER_ENTITY_DOMAINS } from "./const"; import "./controls/cover-buttons-control"; diff --git a/src/cards/entity-card/entity-card-editor.ts b/src/cards/entity-card/entity-card-editor.ts index 1f45c1d40..636d02d02 100644 --- a/src/cards/entity-card/entity-card-editor.ts +++ b/src/cards/entity-card/entity-card-editor.ts @@ -3,7 +3,6 @@ import { fireEvent, HomeAssistant, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -13,6 +12,7 @@ import "../../shared/editor/color-picker"; import "../../shared/editor/info-picker"; import "../../shared/editor/layout-picker"; import { configElementStyle } from "../../utils/editor-styles"; +import { stateIcon } from "../../utils/icons/state-icon"; import { EditorTarget } from "../../utils/lovelace/editor/types"; import { ENTITY_CARD_EDITOR_NAME } from "./const"; import { EntityCardConfig, entityCardConfigStruct } from "./entity-card-config"; diff --git a/src/cards/entity-card/entity-card.ts b/src/cards/entity-card/entity-card.ts index f353a488b..1127993b8 100644 --- a/src/cards/entity-card/entity-card.ts +++ b/src/cards/entity-card/entity-card.ts @@ -6,7 +6,6 @@ import { HomeAssistant, LovelaceCard, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -21,6 +20,7 @@ import { computeRgbColor } from "../../utils/colors"; import { registerCustomCard } from "../../utils/custom-cards"; import { actionHandler } from "../../utils/directives/action-handler-directive"; import { isActive, isAvailable } from "../../utils/entity"; +import { stateIcon } from "../../utils/icons/state-icon"; import { getInfo } from "../../utils/info"; import { getLayoutFromConfig } from "../../utils/layout"; import { ENTITY_CARD_EDITOR_NAME, ENTITY_CARD_NAME } from "./const"; diff --git a/src/cards/fan-card/fan-card-editor.ts b/src/cards/fan-card/fan-card-editor.ts index 24bab0ecd..1f27c8ba4 100644 --- a/src/cards/fan-card/fan-card-editor.ts +++ b/src/cards/fan-card/fan-card-editor.ts @@ -3,7 +3,6 @@ import { fireEvent, HomeAssistant, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -11,6 +10,7 @@ import { assert } from "superstruct"; import setupCustomlocalize from "../../localize"; import "../../shared/editor/layout-picker"; import { configElementStyle } from "../../utils/editor-styles"; +import { stateIcon } from "../../utils/icons/state-icon"; import { EditorTarget } from "../../utils/lovelace/editor/types"; import { FAN_CARD_EDITOR_NAME, FAN_ENTITY_DOMAINS } from "./const"; import { FanCardConfig, fanCardConfigStruct } from "./fan-card-config"; diff --git a/src/cards/fan-card/fan-card.ts b/src/cards/fan-card/fan-card.ts index 2a13be770..317a780e1 100644 --- a/src/cards/fan-card/fan-card.ts +++ b/src/cards/fan-card/fan-card.ts @@ -6,7 +6,6 @@ import { HomeAssistant, LovelaceCard, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -22,6 +21,7 @@ import { cardStyle } from "../../utils/card-styles"; import { registerCustomCard } from "../../utils/custom-cards"; import { actionHandler } from "../../utils/directives/action-handler-directive"; import { isActive, isAvailable } from "../../utils/entity"; +import { stateIcon } from "../../utils/icons/state-icon"; import { getLayoutFromConfig } from "../../utils/layout"; import { FAN_CARD_EDITOR_NAME, FAN_CARD_NAME, FAN_ENTITY_DOMAINS } from "./const"; import "./controls/fan-oscillate-control"; diff --git a/src/cards/light-card/light-card-editor.ts b/src/cards/light-card/light-card-editor.ts index 9eb3bbf69..54de376cf 100644 --- a/src/cards/light-card/light-card-editor.ts +++ b/src/cards/light-card/light-card-editor.ts @@ -3,7 +3,6 @@ import { fireEvent, HomeAssistant, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -11,6 +10,7 @@ import { assert } from "superstruct"; import setupCustomlocalize from "../../localize"; import "../../shared/editor/layout-picker"; import { configElementStyle } from "../../utils/editor-styles"; +import { stateIcon } from "../../utils/icons/state-icon"; import { EditorTarget } from "../../utils/lovelace/editor/types"; import { LIGHT_CARD_EDITOR_NAME, LIGHT_ENTITY_DOMAINS } from "./const"; import { LightCardConfig, lightCardConfigStruct } from "./light-card-config"; diff --git a/src/cards/light-card/light-card.ts b/src/cards/light-card/light-card.ts index 93cd7de43..589b8312b 100644 --- a/src/cards/light-card/light-card.ts +++ b/src/cards/light-card/light-card.ts @@ -6,7 +6,6 @@ import { HomeAssistant, LovelaceCard, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit"; @@ -22,6 +21,7 @@ import { cardStyle } from "../../utils/card-styles"; import { registerCustomCard } from "../../utils/custom-cards"; import { actionHandler } from "../../utils/directives/action-handler-directive"; import { isActive } from "../../utils/entity"; +import { stateIcon } from "../../utils/icons/state-icon"; import { getLayoutFromConfig } from "../../utils/layout"; import { LIGHT_CARD_EDITOR_NAME, LIGHT_CARD_NAME, LIGHT_ENTITY_DOMAINS } from "./const"; import "./controls/light-brightness-control"; diff --git a/src/cards/person-card/person-card-editor.ts b/src/cards/person-card/person-card-editor.ts index 3a77f35ba..75cf28414 100644 --- a/src/cards/person-card/person-card-editor.ts +++ b/src/cards/person-card/person-card-editor.ts @@ -3,7 +3,6 @@ import { fireEvent, HomeAssistant, LovelaceCardEditor, - stateIcon, } from "custom-card-helpers"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -11,6 +10,7 @@ import { assert } from "superstruct"; import setupCustomlocalize from "../../localize"; import "../../shared/editor/layout-picker"; import { configElementStyle } from "../../utils/editor-styles"; +import { stateIcon } from "../../utils/icons/state-icon"; import { EditorTarget } from "../../utils/lovelace/editor/types"; import { PERSON_CARD_EDITOR_NAME, PERSON_ENTITY_DOMAINS } from "./const"; import { PersonCardConfig, personCardConfigStruct } from "./person-card-config"; diff --git a/src/cards/person-card/person-card.ts b/src/cards/person-card/person-card.ts index 8dbf74efd..c179c82c2 100644 --- a/src/cards/person-card/person-card.ts +++ b/src/cards/person-card/person-card.ts @@ -6,7 +6,6 @@ import { HomeAssistant, LovelaceCard, LovelaceCardEditor, - stateIcon as stateIconHelper, } from "custom-card-helpers"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -19,6 +18,7 @@ import { cardStyle } from "../../utils/card-styles"; import { registerCustomCard } from "../../utils/custom-cards"; import { actionHandler } from "../../utils/directives/action-handler-directive"; import { isActive } from "../../utils/entity"; +import { stateIcon as stateIconHelper } from "../../utils/icons/state-icon"; import { getLayoutFromConfig } from "../../utils/layout"; import { PERSON_CARD_EDITOR_NAME, PERSON_CARD_NAME, PERSON_ENTITY_DOMAINS } from "./const"; import { PersonCardConfig } from "./person-card-config"; diff --git a/src/utils/icons/alarm-panel-icon.ts b/src/utils/icons/alarm-panel-icon.ts new file mode 100644 index 000000000..8f85affa0 --- /dev/null +++ b/src/utils/icons/alarm-panel-icon.ts @@ -0,0 +1,42 @@ +export const alarmPanelIcon = (state?: string) => { + switch (state) { + case "armed_away": + return "mdi:shield-lock"; + case "armed_vacation": + return "mdi:shield-airplane"; + case "armed_home": + return "mdi:shield-home"; + case "armed_night": + return "mdi:shield-moon"; + case "armed_custom_bypass": + return "mdi:security"; + case "pending": + case "arming": + return "mdi:shield-sync"; + case "triggered": + return "mdi:bell-ring"; + case "disarmed": + return "mdi:shield-off"; + default: + return "mdi:shield"; + } +}; + +export const alarmPanelIconAction = (state?: string) => { + switch (state) { + case "armed_away": + return "mdi:shield-lock-outline"; + case "armed_vacation": + return "mdi:shield-airplane-outline"; + case "armed_home": + return "mdi:shield-home-outline"; + case "armed_night": + return "mdi:shield-moon-outline"; + case "armed_custom_bypass": + return "mdi:shield-half-full"; + case "disarmed": + return "mdi:shield-off-outline"; + default: + return "mdi:shield-outline"; + } +}; diff --git a/src/utils/icons/binary-sensor-icon.ts b/src/utils/icons/binary-sensor-icon.ts new file mode 100644 index 000000000..c97c198ef --- /dev/null +++ b/src/utils/icons/binary-sensor-icon.ts @@ -0,0 +1,58 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export const binarySensorIcon = (state?: string, entity?: HassEntity) => { + const isOff = state === "off"; + switch (entity?.attributes.device_class) { + case "battery": + return isOff ? "mdi:battery" : "mdi:battery-outline"; + case "battery_charging": + return isOff ? "mdi:battery" : "mdi:battery-charging"; + case "cold": + return isOff ? "mdi:thermometer" : "mdi:snowflake"; + case "connectivity": + return isOff ? "mdi:close-network-outline" : "mdi:check-network-outline"; + case "door": + return isOff ? "mdi:door-closed" : "mdi:door-open"; + case "garage_door": + return isOff ? "mdi:garage" : "mdi:garage-open"; + case "power": + return isOff ? "mdi:power-plug-off" : "mdi:power-plug"; + case "gas": + case "problem": + case "safety": + case "tamper": + return isOff ? "mdi:check-circle" : "mdi:alert-circle"; + case "smoke": + return isOff ? "mdi:check-circle" : "mdi:smoke"; + case "heat": + return isOff ? "mdi:thermometer" : "mdi:fire"; + case "light": + return isOff ? "mdi:brightness5" : "mdi:brightness-7"; + case "lock": + return isOff ? "mdi:lock" : "mdi:lock-open"; + case "moisture": + return isOff ? "mdi:water-off" : "mdi:water"; + case "motion": + return isOff ? "mdi:motion-sensor-off" : "mdi:motion-sensor"; + case "occupancy": + return isOff ? "mdi:home-outline" : "mdi:home"; + case "opening": + return isOff ? "mdi:square" : "mdi:square-outline"; + case "plug": + return isOff ? "mdi:power-plug-off" : "mdi:power-plug"; + case "presence": + return isOff ? "mdi:home-outline" : "mdi:home"; + case "running": + return isOff ? "mdi:stop" : "mdi:play"; + case "sound": + return isOff ? "mdi:music-note-off" : "mdi:music-note"; + case "update": + return isOff ? "mdi:package" : "mdi:package-up"; + case "vibration": + return isOff ? "mdi:crop-portrait" : "mdi:vibrate"; + case "window": + return isOff ? "mdi:window-closed" : "mdi:window-open"; + default: + return isOff ? "mdi:radiobox-blank" : "mdi:checkbox-marked-circle"; + } +}; diff --git a/src/utils/icons/cover-icon.ts b/src/utils/icons/cover-icon.ts new file mode 100644 index 000000000..f08fa3ce3 --- /dev/null +++ b/src/utils/icons/cover-icon.ts @@ -0,0 +1,111 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export const coverIcon = (state?: string, entity?: HassEntity): string => { + const open = state !== "closed"; + + switch (entity?.attributes.device_class) { + case "garage": + switch (state) { + case "opening": + return "mdi:arrow-up-box"; + case "closing": + return "mdi:arrow-down-box"; + case "closed": + return "mdi:garage"; + default: + return "mdi:garage-open"; + } + case "gate": + switch (state) { + case "opening": + case "closing": + return "mdi:gate-arrow-right"; + case "closed": + return "mdi:gate"; + default: + return "mdi:gate-open"; + } + case "door": + return open ? "mdi:door-open" : "mdi:door-closed"; + case "damper": + return open ? "md:circle" : "mdi:circle-slice-8"; + case "shutter": + switch (state) { + case "opening": + return "mdi:arrow-up-box"; + case "closing": + return "mdi:arrow-down-box"; + case "closed": + return "mdi:window-shutter"; + default: + return "mdi:window-shutter-open"; + } + case "curtain": + switch (state) { + case "opening": + return "mdi:arrow-split-vertical"; + case "closing": + return "mdi:arrow-collapse-horizontal"; + case "closed": + return "mdi:curtains-closed"; + default: + return "mdi:curtains"; + } + case "blind": + case "shade": + switch (state) { + case "opening": + return "mdi:arrow-up-box"; + case "closing": + return "mdi:arrow-down-box"; + case "closed": + return "mdi:blinds"; + default: + return "mdi:blinds-open"; + } + case "window": + switch (state) { + case "opening": + return "mdi:arrow-up-box"; + case "closing": + return "mdi:arrow-down-box"; + case "closed": + return "mdi:window-closed"; + default: + return "mdi:window-open"; + } + } + + switch (state) { + case "opening": + return "mdi:arrow-up-box"; + case "closing": + return "mdi:arrow-down-box"; + case "closed": + return "mdi:window-closed"; + default: + return "mdi:window-open"; + } +}; + +export const computeOpenIcon = (stateObj: HassEntity): string => { + switch (stateObj.attributes.device_class) { + case "awning": + case "door": + case "gate": + return "mdi:arrow-expand-horizontal"; + default: + return "mdi:arrow-up"; + } +}; + +export const computeCloseIcon = (stateObj: HassEntity): string => { + switch (stateObj.attributes.device_class) { + case "awning": + case "door": + case "gate": + return "mdi:arrow-collapse-horizontal"; + default: + return "mdi:arrow-down"; + } +}; diff --git a/src/utils/icons/domain-icon.ts b/src/utils/icons/domain-icon.ts new file mode 100644 index 000000000..fbc8c1d67 --- /dev/null +++ b/src/utils/icons/domain-icon.ts @@ -0,0 +1,159 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { alarmPanelIcon } from "./alarm-panel-icon"; +import { binarySensorIcon } from "./binary-sensor-icon"; +import { coverIcon } from "./cover-icon"; +import { sensorIcon } from "./sensor-icon"; + +const DEFAULT_DOMAIN_ICON = "mdi:bookmark"; + +const FIXED_DOMAIN_ICONS = { + alert: "mdi:alert", + air_quality: "mdi:air-filter", + automation: "mdi:robot", + calendar: "mdi:calendar", + camera: "mdi:video", + climate: "mdi:thermostat", + configurator: "mdi:cog", + conversation: "mdi:text-to-speech", + counter: "mdi:counter", + fan: "mdi:fan", + google_assistant: "mdi:google-assistant", + group: "mdi:google-circles-communities", + homeassistant: "mdi:home-assistant", + homekit: "mdi:home-automation", + image_processing: "mdi:image-filter-frames", + input_button: "mdi:gesture-tap-button", + input_datetime: "mdi:calendar-clock", + input_number: "mdi:ray-vertex", + input_select: "mdi:format-list-bulleted", + input_text: "mdi:form-textbox", + light: "mdi:lightbulb", + mailbox: "mdi:mailbox", + notify: "mdi:comment-alert", + number: "mdi:ray-vertex", + persistent_notification: "mdi:bell", + person: "mdi:account", + plant: "mdi:flower", + proximity: "mdi:apple-safari", + remote: "mdi:remote", + scene: "mdi:palette", + script: "mdi:script-text", + select: "mdi:format-list-bulleted", + sensor: "mdi:eye", + siren: "mdi:bullhorn", + simple_alarm: "mdi:bell", + sun: "mdi:white-balance-sunny", + timer: "mdi:timer-outline", + updater: "mdi:cloud-upload", + vacuum: "mdi:robot-vacuum", + water_heater: "mdi:thermometer", + weather: "mdi:weather-cloudy", + zone: "mdi:map-marker-radius", +}; + +export function domainIcon(domain: string, entity?: HassEntity, state?: string): string { + switch (domain) { + case "alarm_control_panel": + return alarmPanelIcon(state); + + case "binary_sensor": + return binarySensorIcon(state, entity); + + case "button": + switch (entity?.attributes.device_class) { + case "restart": + return "mdi:restart"; + case "update": + return "mdi:package-up"; + default: + return "mdi:gesture-tap-button"; + } + + case "cover": + return coverIcon(state, entity); + + case "device_tracker": + if (entity?.attributes.source_type === "router") { + return state === "home" ? "mdi:lan-connect" : "mdi:lan-disconnect"; + } + if (["bluetooth", "bluetooth_le"].includes(entity?.attributes.source_type)) { + return state === "home" ? "mdi:bluetooth-connect" : "mdi:bluetooth"; + } + return state === "not_home" ? "mdi:account-arrow-right" : "mdi:account"; + + case "humidifier": + return state && state === "off" ? "mdi:air-humidifier-off" : "mdi:air-humidifier"; + + case "input_boolean": + return state === "on" ? "mdi:check-circle-outline" : "mdi:close-circle-outline"; + + case "lock": + switch (state) { + case "unlocked": + return "mdi:lock-open"; + case "jammed": + return "mdi:lock-alert"; + case "locking": + case "unlocking": + return "mdi:lock-clock"; + default: + return "mdi:lock"; + } + + case "media_player": + return state === "playing" ? "mdi:cast-connected" : "mdi:cast"; + + case "switch": + switch (entity?.attributes.device_class) { + case "outlet": + return state === "on" ? "mdi:power-plug" : "mdi:power-plug-off"; + case "switch": + return state === "on" ? "mdi:toggle-switch" : "mdi:toggle-switch-off"; + default: + return "mdi:flash"; + } + + case "zwave": + switch (state) { + case "dead": + return "mdi:emoticon-dead"; + case "sleeping": + return "mdi:sleep"; + case "initializing": + return "mdi:timer-sand"; + default: + return "mdi:z-wave"; + } + + case "sensor": { + const icon = sensorIcon(entity); + if (icon) { + return icon; + } + + break; + } + + case "input_datetime": + if (!entity?.attributes.has_date) { + return "mdi:clock"; + } + if (!entity.attributes.has_time) { + return "mdi:calendar"; + } + break; + + case "sun": + return entity?.state === "above_horizon" + ? FIXED_DOMAIN_ICONS[domain] + : "mdi:weather-night"; + } + + if (domain in FIXED_DOMAIN_ICONS) { + return FIXED_DOMAIN_ICONS[domain]; + } + + // eslint-disable-next-line + console.warn(`Unable to find icon for domain ${domain}`); + return DEFAULT_DOMAIN_ICON; +} diff --git a/src/utils/icons/sensor-icon.ts b/src/utils/icons/sensor-icon.ts new file mode 100644 index 000000000..b86735227 --- /dev/null +++ b/src/utils/icons/sensor-icon.ts @@ -0,0 +1,112 @@ +import { UNIT_C, UNIT_F } from "custom-card-helpers"; +import { HassEntity } from "home-assistant-js-websocket"; + +const FIXED_DEVICE_CLASS_ICONS = { + apparent_power: "mdi:flash", + aqi: "mdi:air-filter", + carbon_dioxide: "mdi:molecule-co2", + carbon_monoxide: "mdi:molecule-co", + current: "mdi:current-ac", + date: "mdi:calendar", + energy: "mdi:lightning-bolt", + frequency: "mdi:sine-wave", + gas: "mdi:gas-cylinder", + humidity: "mdi:water-percent", + illuminance: "mdi:brightness-5", + monetary: "mdi:cash", + nitrogen_dioxide: "mdi:molecule", + nitrogen_monoxide: "mdi:molecule", + nitrous_oxide: "mdi:molecule", + ozone: "mdi:molecule", + pm1: "mdi:molecule", + pm10: "mdi:molecule", + pm25: "mdi:molecule", + power: "mdi:flash", + power_factor: "mdi:angle-acute", + pressure: "mdi:gauge", + reactive_power: "mdi:flash", + signal_strength: "mdi:wifi", + sulphur_dioxide: "mdi:molecule", + temperature: "mdi:thermometer", + timestamp: "mdi:clock", + volatile_organic_compounds: "mdi:molecule", + voltage: "mdi:sine-wave", +}; + +const SENSOR_DEVICE_CLASS_BATTERY = "battery"; + +const BATTERY_ICONS = { + 10: "mdi:battery-10", + 20: "mdi:battery-20", + 30: "mdi:battery-30", + 40: "mdi:battery-40", + 50: "mdi:battery-50", + 60: "mdi:battery-60", + 70: "mdi:battery-70", + 80: "mdi:battery-80", + 90: "mdi:battery-90", + 100: "mdi:battery", +}; +const BATTERY_CHARGING_ICONS = { + 10: "mdi:battery-charging-10", + 20: "mdi:battery-charging-20", + 30: "mdi:battery-charging-30", + 40: "mdi:battery-charging-40", + 50: "mdi:battery-charging-50", + 60: "mdi:battery-charging-60", + 70: "mdi:battery-charging-70", + 80: "mdi:battery-charging-80", + 90: "mdi:battery-charging-90", + 100: "mdi:battery-charging", +}; + +const batteryStateIcon = (batteryEntity: HassEntity, batteryChargingEntity?: HassEntity) => { + const battery = batteryEntity.state; + const batteryCharging = batteryChargingEntity?.state === "on"; + + return batteryIcon(battery, batteryCharging); +}; + +export const batteryIcon = (batteryState: number | string, batteryCharging?: boolean) => { + const batteryValue = Number(batteryState); + if (isNaN(batteryValue)) { + if (batteryState === "off") { + return "mdi:battery"; + } + if (batteryState === "on") { + return "mdi:battery-alert"; + } + return "mdi:battery-unknown"; + } + + const batteryRound = Math.round(batteryValue / 10) * 10; + if (batteryCharging && batteryValue >= 10) { + return BATTERY_CHARGING_ICONS[batteryRound]; + } + if (batteryCharging) { + return "mdi:battery-charging-outline"; + } + if (batteryValue <= 5) { + return "mdi:battery-alert-variant-outline"; + } + return BATTERY_ICONS[batteryRound]; +}; + +export const sensorIcon = (entity?: HassEntity): string | undefined => { + const dclass = entity?.attributes.device_class; + + if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) { + return FIXED_DEVICE_CLASS_ICONS[dclass]; + } + + if (dclass === SENSOR_DEVICE_CLASS_BATTERY) { + return entity ? batteryStateIcon(entity) : "mdi:battery"; + } + + const unit = entity?.attributes.unit_of_measurement; + if (unit === UNIT_C || unit === UNIT_F) { + return "mdi:thermometer"; + } + + return undefined; +}; diff --git a/src/utils/icons/state-icon.ts b/src/utils/icons/state-icon.ts new file mode 100644 index 000000000..461e3a7c7 --- /dev/null +++ b/src/utils/icons/state-icon.ts @@ -0,0 +1,11 @@ +import { computeDomain } from "custom-card-helpers"; +import { HassEntity } from "home-assistant-js-websocket"; +import { domainIcon } from "./domain-icon"; + +export function stateIcon(entity: HassEntity): string { + const domain = computeDomain(entity.entity_id); + + const state = entity.state; + + return domainIcon(domain, entity, state); +}