Clean domain icons (#19533)

* Use state icon inside alarm control panel card

* Add domain icon component and clean old files

* Clean code

* Migrate area card

* Remove some icon rules

* Update ha-bar-media-player.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update ha-domain-icon.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2024-01-30 14:49:39 +01:00 committed by GitHub
parent 6dcc70f6fc
commit 314499005d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 417 additions and 860 deletions

View File

@ -1,6 +1,7 @@
/** Constants to be used in the frontend. */ /** Constants to be used in the frontend. */
import { import {
mdiAccount,
mdiAirFilter, mdiAirFilter,
mdiAlert, mdiAlert,
mdiAngleAcute, mdiAngleAcute,
@ -50,8 +51,10 @@ import {
mdiProgressClock, mdiProgressClock,
mdiRayVertex, mdiRayVertex,
mdiRemote, mdiRemote,
mdiRobot,
mdiRobotMower, mdiRobotMower,
mdiRobotVacuum, mdiRobotVacuum,
mdiRoomService,
mdiScriptText, mdiScriptText,
mdiSineWave, mdiSineWave,
mdiSpeakerMessage, mdiSpeakerMessage,
@ -61,6 +64,7 @@ import {
mdiThermometerLines, mdiThermometerLines,
mdiThermostat, mdiThermostat,
mdiTimerOutline, mdiTimerOutline,
mdiToggleSwitch,
mdiTransmissionTower, mdiTransmissionTower,
mdiWater, mdiWater,
mdiWaterPercent, mdiWaterPercent,
@ -69,6 +73,7 @@ import {
mdiWeatherRainy, mdiWeatherRainy,
mdiWeatherWindy, mdiWeatherWindy,
mdiWeight, mdiWeight,
mdiWhiteBalanceSunny,
mdiWifi, mdiWifi,
} from "@mdi/js"; } from "@mdi/js";
@ -78,6 +83,9 @@ import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
// Arrays with values should be alphabetically sorted if order doesn't matter. // Arrays with values should be alphabetically sorted if order doesn't matter.
// Each constant should have a description what it is supposed to be used for. // Each constant should have a description what it is supposed to be used for.
/** Icon to use when no icon specified for service. */
export const DEFAULT_SERVICE_ICON = mdiRoomService;
/** Icon to use when no icon specified for domain. */ /** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = mdiBookmark; export const DEFAULT_DOMAIN_ICON = mdiBookmark;
@ -85,20 +93,23 @@ export const DEFAULT_DOMAIN_ICON = mdiBookmark;
export const FIXED_DOMAIN_ICONS = { export const FIXED_DOMAIN_ICONS = {
air_quality: mdiAirFilter, air_quality: mdiAirFilter,
alert: mdiAlert, alert: mdiAlert,
automation: mdiRobot,
calendar: mdiCalendar, calendar: mdiCalendar,
climate: mdiThermostat, climate: mdiThermostat,
configurator: mdiCog, configurator: mdiCog,
conversation: mdiMicrophoneMessage, conversation: mdiMicrophoneMessage,
counter: mdiCounter, counter: mdiCounter,
datetime: mdiCalendarClock,
date: mdiCalendar, date: mdiCalendar,
datetime: mdiCalendarClock,
demo: mdiHomeAssistant, demo: mdiHomeAssistant,
device_tracker: mdiAccount,
google_assistant: mdiGoogleAssistant, google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities, group: mdiGoogleCirclesCommunities,
homeassistant: mdiHomeAssistant, homeassistant: mdiHomeAssistant,
homekit: mdiHomeAutomation, homekit: mdiHomeAutomation,
image: mdiImage,
image_processing: mdiImageFilterFrames, image_processing: mdiImageFilterFrames,
image: mdiImage,
input_boolean: mdiToggleSwitch,
input_button: mdiButtonPointer, input_button: mdiButtonPointer,
input_datetime: mdiCalendarClock, input_datetime: mdiCalendarClock,
input_number: mdiRayVertex, input_number: mdiRayVertex,
@ -110,6 +121,7 @@ export const FIXED_DOMAIN_ICONS = {
notify: mdiCommentAlert, notify: mdiCommentAlert,
number: mdiRayVertex, number: mdiRayVertex,
persistent_notification: mdiBell, persistent_notification: mdiBell,
person: mdiAccount,
plant: mdiFlower, plant: mdiFlower,
proximity: mdiAppleSafari, proximity: mdiAppleSafari,
remote: mdiRemote, remote: mdiRemote,
@ -121,10 +133,11 @@ export const FIXED_DOMAIN_ICONS = {
simple_alarm: mdiBell, simple_alarm: mdiBell,
siren: mdiBullhorn, siren: mdiBullhorn,
stt: mdiMicrophoneMessage, stt: mdiMicrophoneMessage,
sun: mdiWhiteBalanceSunny,
text: mdiFormTextbox, text: mdiFormTextbox,
todo: mdiClipboardList,
time: mdiClock, time: mdiClock,
timer: mdiTimerOutline, timer: mdiTimerOutline,
todo: mdiClipboardList,
tts: mdiSpeakerMessage, tts: mdiSpeakerMessage,
vacuum: mdiRobotVacuum, vacuum: mdiRobotVacuum,
wake_word: mdiChatSleep, wake_word: mdiChatSleep,

View File

@ -1,36 +0,0 @@
/** Return an icon representing a alarm panel state. */
import {
mdiShieldLock,
mdiShieldAirplane,
mdiShieldHome,
mdiShieldMoon,
mdiSecurity,
mdiShieldOutline,
mdiBellRing,
mdiShieldOff,
mdiShield,
} from "@mdi/js";
export const alarmPanelIcon = (state?: string) => {
switch (state) {
case "armed_away":
return mdiShieldLock;
case "armed_vacation":
return mdiShieldAirplane;
case "armed_home":
return mdiShieldHome;
case "armed_night":
return mdiShieldMoon;
case "armed_custom_bypass":
return mdiSecurity;
case "pending":
return mdiShieldOutline;
case "triggered":
return mdiBellRing;
case "disarmed":
return mdiShieldOff;
default:
return mdiShield;
}
};

View File

@ -1,92 +1,59 @@
/** Return an icon representing a battery state. */
import {
mdiBattery,
mdiBattery10,
mdiBattery20,
mdiBattery30,
mdiBattery40,
mdiBattery50,
mdiBattery60,
mdiBattery70,
mdiBattery80,
mdiBattery90,
mdiBatteryAlert,
mdiBatteryAlertVariantOutline,
mdiBatteryCharging,
mdiBatteryCharging10,
mdiBatteryCharging20,
mdiBatteryCharging30,
mdiBatteryCharging40,
mdiBatteryCharging50,
mdiBatteryCharging60,
mdiBatteryCharging70,
mdiBatteryCharging80,
mdiBatteryCharging90,
mdiBatteryChargingOutline,
mdiBatteryUnknown,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
const BATTERY_ICONS = { const BATTERY_ICONS = {
10: mdiBattery10, 10: "mdi:battery-10",
20: mdiBattery20, 20: "mdi:battery-20",
30: mdiBattery30, 30: "mdi:battery-30",
40: mdiBattery40, 40: "mdi:battery-40",
50: mdiBattery50, 50: "mdi:battery-50",
60: mdiBattery60, 60: "mdi:battery-60",
70: mdiBattery70, 70: "mdi:battery-70",
80: mdiBattery80, 80: "mdi:battery-80",
90: mdiBattery90, 90: "mdi:battery-90",
100: mdiBattery, 100: "mdi:battery",
}; };
const BATTERY_CHARGING_ICONS = { const BATTERY_CHARGING_ICONS = {
10: mdiBatteryCharging10, 10: "mdi:battery-charging-10",
20: mdiBatteryCharging20, 20: "mdi:battery-charging-20",
30: mdiBatteryCharging30, 30: "mdi:battery-charging-30",
40: mdiBatteryCharging40, 40: "mdi:battery-charging-40",
50: mdiBatteryCharging50, 50: "mdi:battery-charging-50",
60: mdiBatteryCharging60, 60: "mdi:battery-charging-60",
70: mdiBatteryCharging70, 70: "mdi:battery-charging-70",
80: mdiBatteryCharging80, 80: "mdi:battery-charging-80",
90: mdiBatteryCharging90, 90: "mdi:battery-charging-90",
100: mdiBatteryCharging, 100: "mdi:battery-charging",
}; };
export const batteryStateIcon = ( export const batteryIcon = (stateObj: HassEntity, state?: string) => {
batteryState: HassEntity, const level = state ?? stateObj.state;
batteryChargingState?: HassEntity return batteryLevelIcon(level);
) => {
const battery = batteryState.state;
const batteryCharging =
batteryChargingState && batteryChargingState.state === "on";
return batteryIcon(battery, batteryCharging);
}; };
export const batteryIcon = ( export const batteryLevelIcon = (
batteryState: number | string, batteryLevel: number | string,
batteryCharging?: boolean isBatteryCharging?: boolean
) => { ): string => {
const batteryValue = Number(batteryState); const batteryValue = Number(batteryLevel);
if (isNaN(batteryValue)) { if (isNaN(batteryValue)) {
if (batteryState === "off") { if (batteryLevel === "off") {
return mdiBattery; return "mdi:battery";
} }
if (batteryState === "on") { if (batteryLevel === "on") {
return mdiBatteryAlert; return "mdi:battery-alert";
} }
return mdiBatteryUnknown; return "mdi:battery-unknown";
} }
const batteryRound = Math.round(batteryValue / 10) * 10; const batteryRound = Math.round(batteryValue / 10) * 10;
if (batteryCharging && batteryValue >= 10) { if (isBatteryCharging && batteryValue >= 10) {
return BATTERY_CHARGING_ICONS[batteryRound]; return BATTERY_CHARGING_ICONS[batteryRound];
} }
if (batteryCharging) { if (isBatteryCharging) {
return mdiBatteryChargingOutline; return "mdi:battery-charging-outline";
} }
if (batteryValue <= 5) { if (batteryValue <= 5) {
return mdiBatteryAlertVariantOutline; return "mdi:battery-alert-variant-outline";
} }
return BATTERY_ICONS[batteryRound]; return BATTERY_ICONS[batteryRound];
}; };

View File

@ -1,108 +0,0 @@
import {
mdiAlertCircle,
mdiBattery,
mdiBatteryCharging,
mdiBatteryOutline,
mdiBrightness5,
mdiBrightness7,
mdiCheckboxMarkedCircle,
mdiCheckNetworkOutline,
mdiCloseNetworkOutline,
mdiCheckCircle,
mdiCropPortrait,
mdiDoorClosed,
mdiDoorOpen,
mdiFire,
mdiGarage,
mdiGarageOpen,
mdiHome,
mdiHomeOutline,
mdiLock,
mdiLockOpen,
mdiMusicNote,
mdiMusicNoteOff,
mdiMotionSensor,
mdiMotionSensorOff,
mdiPackage,
mdiPackageUp,
mdiPlay,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRadioboxBlank,
mdiSnowflake,
mdiSmokeDetector,
mdiSmokeDetectorAlert,
mdiSmokeDetectorVariant,
mdiSmokeDetectorVariantAlert,
mdiSquare,
mdiSquareOutline,
mdiStop,
mdiThermometer,
mdiVibrate,
mdiWater,
mdiWaterOff,
mdiWindowClosed,
mdiWindowOpen,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
/** Return an icon representing a binary sensor state. */
export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
const is_off = state === "off";
switch (stateObj?.attributes.device_class) {
case "battery":
return is_off ? mdiBattery : mdiBatteryOutline;
case "battery_charging":
return is_off ? mdiBattery : mdiBatteryCharging;
case "carbon_monoxide":
return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
case "cold":
return is_off ? mdiThermometer : mdiSnowflake;
case "connectivity":
return is_off ? mdiCloseNetworkOutline : mdiCheckNetworkOutline;
case "door":
return is_off ? mdiDoorClosed : mdiDoorOpen;
case "garage_door":
return is_off ? mdiGarage : mdiGarageOpen;
case "power":
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
case "gas":
case "problem":
case "safety":
case "tamper":
return is_off ? mdiCheckCircle : mdiAlertCircle;
case "smoke":
return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
case "heat":
return is_off ? mdiThermometer : mdiFire;
case "light":
return is_off ? mdiBrightness5 : mdiBrightness7;
case "lock":
return is_off ? mdiLock : mdiLockOpen;
case "moisture":
return is_off ? mdiWaterOff : mdiWater;
case "motion":
return is_off ? mdiMotionSensorOff : mdiMotionSensor;
case "occupancy":
return is_off ? mdiHomeOutline : mdiHome;
case "opening":
return is_off ? mdiSquare : mdiSquareOutline;
case "plug":
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
case "presence":
return is_off ? mdiHomeOutline : mdiHome;
case "running":
return is_off ? mdiStop : mdiPlay;
case "sound":
return is_off ? mdiMusicNoteOff : mdiMusicNote;
case "update":
return is_off ? mdiPackage : mdiPackageUp;
case "vibration":
return is_off ? mdiCropPortrait : mdiVibrate;
case "window":
return is_off ? mdiWindowClosed : mdiWindowOpen;
default:
return is_off ? mdiRadioboxBlank : mdiCheckboxMarkedCircle;
}
};

View File

@ -1,132 +1,12 @@
/** Return an icon representing a cover state. */ /** Return an icon representing a cover state. */
import { import {
mdiArrowUpBox,
mdiArrowDownBox,
mdiGarage,
mdiGarageOpen,
mdiGateArrowRight,
mdiGate,
mdiGateOpen,
mdiDoorOpen,
mdiDoorClosed,
mdiCircle,
mdiWindowShutter,
mdiWindowShutterOpen,
mdiBlindsHorizontal,
mdiBlindsHorizontalClosed,
mdiRollerShade,
mdiRollerShadeClosed,
mdiWindowClosed,
mdiWindowOpen,
mdiArrowExpandHorizontal,
mdiArrowUp,
mdiArrowCollapseHorizontal, mdiArrowCollapseHorizontal,
mdiArrowDown, mdiArrowDown,
mdiCircleSlice8, mdiArrowExpandHorizontal,
mdiArrowSplitVertical, mdiArrowUp,
mdiCurtains,
mdiCurtainsClosed,
} from "@mdi/js"; } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
const open = state !== "closed";
switch (stateObj?.attributes.device_class) {
case "garage":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiGarage;
default:
return mdiGarageOpen;
}
case "gate":
switch (state) {
case "opening":
case "closing":
return mdiGateArrowRight;
case "closed":
return mdiGate;
default:
return mdiGateOpen;
}
case "door":
return open ? mdiDoorOpen : mdiDoorClosed;
case "damper":
return open ? mdiCircle : mdiCircleSlice8;
case "shutter":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiWindowShutter;
default:
return mdiWindowShutterOpen;
}
case "curtain":
switch (state) {
case "opening":
return mdiArrowSplitVertical;
case "closing":
return mdiArrowCollapseHorizontal;
case "closed":
return mdiCurtainsClosed;
default:
return mdiCurtains;
}
case "blind":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiBlindsHorizontalClosed;
default:
return mdiBlindsHorizontal;
}
case "shade":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiRollerShadeClosed;
default:
return mdiRollerShade;
}
case "window":
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiWindowClosed;
default:
return mdiWindowOpen;
}
}
switch (state) {
case "opening":
return mdiArrowUpBox;
case "closing":
return mdiArrowDownBox;
case "closed":
return mdiWindowClosed;
default:
return mdiWindowOpen;
}
};
export const computeOpenIcon = (stateObj: HassEntity): string => { export const computeOpenIcon = (stateObj: HassEntity): string => {
switch (stateObj.attributes.device_class) { switch (stateObj.attributes.device_class) {
case "awning": case "awning":

View File

@ -0,0 +1,16 @@
import { HassEntity } from "home-assistant-js-websocket";
export const deviceTrackerIcon = (stateObj: HassEntity, state?: string) => {
const compareState = state ?? stateObj.state;
if (stateObj?.attributes.source_type === "router") {
return compareState === "home" ? "mdi:lan-connect" : "mdi:lan-disconnect";
}
if (
["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
) {
return compareState === "home" ? "mdi:bluetooth-connect" : "mdi:bluetooth";
}
return compareState === "not_home"
? "mdi:account-arrow-right"
: "mdi:account";
};

View File

@ -1,301 +0,0 @@
import {
mdiAccount,
mdiAccountArrowRight,
mdiAirHumidifier,
mdiAirHumidifierOff,
mdiAudioVideo,
mdiAudioVideoOff,
mdiBluetooth,
mdiBluetoothConnect,
mdiButtonPointer,
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
mdiCloseCircleOutline,
mdiCrosshairsQuestion,
mdiDoorbell,
mdiEyeCheck,
mdiFan,
mdiFanOff,
mdiGestureTapButton,
mdiLanConnect,
mdiLanDisconnect,
mdiLock,
mdiLockAlert,
mdiLockClock,
mdiLockOpen,
mdiMeterGas,
mdiMotionSensor,
mdiPackage,
mdiPackageDown,
mdiPackageUp,
mdiPipeValve,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiRobot,
mdiRobotConfused,
mdiRobotOff,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiVideo,
mdiVideoOff,
mdiWaterBoiler,
mdiWaterBoilerOff,
mdiWeatherNight,
mdiWhiteBalanceSunny,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { UpdateEntity, updateIsInstalling } from "../../data/update";
import { weatherIcon } from "../../data/weather";
/**
* Return the icon to be used for a domain.
*
* Optionally pass in a state to influence the domain icon.
*/
import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
import { alarmPanelIcon } from "./alarm_panel_icon";
import { binarySensorIcon } from "./binary_sensor_icon";
import { coverIcon } from "./cover_icon";
import { numberIcon } from "./number_icon";
import { sensorIcon } from "./sensor_icon";
export const domainIcon = (
domain: string,
stateObj?: HassEntity,
state?: string
): string => {
const icon = domainIconWithoutDefault(domain, stateObj, state);
if (icon) {
return icon;
}
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};
export const domainIconWithoutDefault = (
domain: string,
stateObj?: HassEntity,
state?: string
): string | undefined => {
const compareState = state !== undefined ? state : stateObj?.state;
switch (domain) {
case "alarm_control_panel":
return alarmPanelIcon(compareState);
case "automation":
return compareState === "unavailable"
? mdiRobotConfused
: compareState === "off"
? mdiRobotOff
: mdiRobot;
case "binary_sensor":
return binarySensorIcon(compareState, stateObj);
case "button":
switch (stateObj?.attributes.device_class) {
case "identify":
return mdiCrosshairsQuestion;
case "restart":
return mdiRestart;
case "update":
return mdiPackageUp;
default:
return mdiButtonPointer;
}
case "camera":
return compareState === "off" ? mdiVideoOff : mdiVideo;
case "cover":
return coverIcon(compareState, stateObj);
case "device_tracker":
if (stateObj?.attributes.source_type === "router") {
return compareState === "home" ? mdiLanConnect : mdiLanDisconnect;
}
if (
["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
) {
return compareState === "home" ? mdiBluetoothConnect : mdiBluetooth;
}
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "event":
switch (stateObj?.attributes.device_class) {
case "doorbell":
return mdiDoorbell;
case "button":
return mdiGestureTapButton;
case "motion":
return mdiMotionSensor;
default:
return mdiEyeCheck;
}
case "fan":
return compareState === "off" ? mdiFanOff : mdiFan;
case "humidifier":
return compareState === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
case "input_boolean":
return compareState === "on"
? mdiCheckCircleOutline
: mdiCloseCircleOutline;
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "lock":
switch (compareState) {
case "unlocked":
return mdiLockOpen;
case "jammed":
return mdiLockAlert;
case "locking":
case "unlocking":
return mdiLockClock;
default:
return mdiLock;
}
case "media_player":
switch (stateObj?.attributes.device_class) {
case "speaker":
switch (compareState) {
case "playing":
return mdiSpeakerPlay;
case "paused":
return mdiSpeakerPause;
case "off":
return mdiSpeakerOff;
default:
return mdiSpeaker;
}
case "tv":
switch (compareState) {
case "playing":
return mdiTelevisionPlay;
case "paused":
return mdiTelevisionPause;
case "off":
return mdiTelevisionOff;
default:
return mdiTelevision;
}
case "receiver":
switch (compareState) {
case "off":
return mdiAudioVideoOff;
default:
return mdiAudioVideo;
}
default:
switch (compareState) {
case "playing":
case "paused":
return mdiCastConnected;
case "off":
return mdiCastOff;
default:
return mdiCast;
}
}
case "number": {
const icon = numberIcon(stateObj);
if (icon) {
return icon;
}
break;
}
case "person":
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "switch":
switch (stateObj?.attributes.device_class) {
case "outlet":
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
case "switch":
return compareState === "on"
? mdiToggleSwitchVariant
: mdiToggleSwitchVariantOff;
default:
return mdiToggleSwitchVariant;
}
case "sensor": {
const icon = sensorIcon(stateObj);
if (icon) {
return icon;
}
break;
}
case "sun":
return stateObj?.state === "above_horizon"
? mdiWhiteBalanceSunny
: mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update":
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? mdiPackageDown
: mdiPackageUp
: mdiPackage;
case "valve":
switch (stateObj?.attributes.device_class) {
case "water":
return mdiPipeValve;
case "gas":
return mdiMeterGas;
default:
return mdiPipeValve;
}
case "water_heater":
return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
case "weather":
return weatherIcon(stateObj?.state);
}
if (domain in FIXED_DOMAIN_ICONS) {
return FIXED_DOMAIN_ICONS[domain];
}
return undefined;
};

View File

@ -1,13 +0,0 @@
/** Return an icon representing a number state. */
import { HassEntity } from "home-assistant-js-websocket";
import { FIXED_DEVICE_CLASS_ICONS } from "../const";
export const numberIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
return undefined;
};

View File

@ -1,25 +0,0 @@
/** Return an icon representing a sensor state. */
import { mdiBattery, mdiThermometer } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
import { batteryStateIcon } from "./battery_icon";
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
return stateObj ? batteryStateIcon(stateObj) : mdiBattery;
}
const unit = stateObj?.attributes.unit_of_measurement;
if (unit === UNIT_C || unit === UNIT_F) {
return mdiThermometer;
}
return undefined;
};

View File

@ -0,0 +1,42 @@
import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { updateIcon } from "./update_icon";
import { deviceTrackerIcon } from "./device_tracker_icon";
import { batteryIcon } from "./battery_icon";
export const stateIcon = (
stateObj: HassEntity,
state?: string
): string | undefined => {
const domain = computeStateDomain(stateObj);
const compareState = state ?? stateObj.state;
const dc = stateObj.attributes.device_class;
switch (domain) {
case "update":
return updateIcon(stateObj, compareState);
case "sensor":
if (dc === "battery") {
return batteryIcon(stateObj, compareState);
}
break;
case "device_tracker":
return deviceTrackerIcon(stateObj, compareState);
case "sun":
return compareState === "above_horizon"
? "mdi:white-balance-sunny"
: "mdi:weather-night";
case "input_datetime":
if (!stateObj.attributes.has_date) {
return "mdi:clock";
}
if (!stateObj.attributes.has_time) {
return "mdi:calendar";
}
break;
}
return undefined;
};

View File

@ -1,12 +0,0 @@
/** Return an icon representing a state. */
import { HassEntity } from "home-assistant-js-websocket";
import { DEFAULT_DOMAIN_ICON } from "../const";
import { computeDomain } from "./compute_domain";
import { domainIcon } from "./domain_icon";
export const stateIconPath = (state: HassEntity | undefined) => {
if (!state) {
return DEFAULT_DOMAIN_ICON;
}
return domainIcon(computeDomain(state.entity_id), state);
};

View File

@ -0,0 +1,11 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UpdateEntity, updateIsInstalling } from "../../data/update";
export const updateIcon = (stateObj: HassEntity, state?: string) => {
const compareState = state ?? stateObj.state;
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? "mdi:package-down"
: "mdi:package-up"
: "mdi:package";
};

View File

@ -764,10 +764,12 @@ export class HaDataTable extends LitElement {
text-align: right; text-align: right;
} }
.mdc-data-table__cell--icon:first-child ha-icon,
.mdc-data-table__cell--icon:first-child img, .mdc-data-table__cell--icon:first-child img,
.mdc-data-table__cell--icon:first-child ha-icon,
.mdc-data-table__cell--icon:first-child ha-svg-icon,
.mdc-data-table__cell--icon:first-child ha-state-icon, .mdc-data-table__cell--icon:first-child ha-state-icon,
.mdc-data-table__cell--icon:first-child ha-svg-icon { .mdc-data-table__cell--icon:first-child ha-domain-icon,
.mdc-data-table__cell--icon:first-child ha-service-icon {
margin-left: 8px; margin-left: 8px;
} }
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon, :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon,

View File

@ -1,22 +1,25 @@
import { html, LitElement } from "lit"; import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { batteryStateIcon } from "../../common/entity/battery_icon"; import { batteryLevelIcon } from "../../common/entity/battery_icon";
import "../ha-svg-icon"; import "../ha-icon";
@customElement("ha-battery-icon") @customElement("ha-battery-icon")
export class HaBatteryIcon extends LitElement { export class HaBatteryIcon extends LitElement {
@property() public batteryStateObj; @property({ attribute: false }) public batteryStateObj?: HassEntity;
@property() public batteryChargingStateObj; @property({ attribute: false }) public batteryChargingStateObj?: HassEntity;
protected render() { protected render() {
if (!this.batteryStateObj) return nothing;
return html` return html`
<ha-svg-icon <ha-icon
.path=${batteryStateIcon( .icon=${batteryLevelIcon(
this.batteryStateObj, this.batteryStateObj.state,
this.batteryChargingStateObj this.batteryChargingStateObj?.state === "on"
)} )}
></ha-svg-icon> ></ha-icon>
`; `;
} }
} }

View File

@ -0,0 +1,84 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until";
import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../common/const";
import { domainIcon } from "../data/icons";
import { HomeAssistant } from "../types";
import { brandsUrl } from "../util/brands-url";
import "./ha-icon";
@customElement("ha-domain-icon")
export class HaDomainIcon extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public domain?: string;
@property() public deviceClass?: string;
@property() public icon?: string;
@property({ type: Boolean }) public brandFallback?: boolean;
protected render() {
if (this.icon) {
return html`<ha-icon .icon=${this.icon}></ha-icon>`;
}
if (!this.domain) {
return nothing;
}
if (!this.hass) {
return this._renderFallback();
}
const icon = domainIcon(this.hass, this.domain, this.deviceClass).then(
(icn) => {
if (icn) {
return html`<ha-icon .icon=${icn}></ha-icon>`;
}
return this._renderFallback();
}
);
return html`${until(icon)}`;
}
private _renderFallback() {
if (this.domain! in FIXED_DOMAIN_ICONS) {
return html`
<ha-svg-icon .path=${FIXED_DOMAIN_ICONS[this.domain!]}></ha-svg-icon>
`;
}
if (this.brandFallback) {
const image = brandsUrl({
domain: this.domain!,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
return html`
<img
alt=""
src=${image}
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
`;
}
return html`<ha-svg-icon .path=${DEFAULT_DOMAIN_ICON}></ha-svg-icon>`;
}
static get styles(): CSSResultGroup {
return css`
img {
width: var(--mdc-icon-size, 24px);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-domain-icon": HaDomainIcon;
}
}

View File

@ -1,9 +1,8 @@
import { mdiRoomService } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until"; import { until } from "lit/directives/until";
import { DEFAULT_SERVICE_ICON, FIXED_DOMAIN_ICONS } from "../common/const";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
import { domainIconWithoutDefault } from "../common/entity/domain_icon";
import { serviceIcon } from "../data/icons"; import { serviceIcon } from "../data/icons";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-icon"; import "./ha-icon";
@ -41,10 +40,11 @@ export class HaServiceIcon extends LitElement {
} }
private _renderFallback() { private _renderFallback() {
const domain = computeDomain(this.service!);
return html` return html`
<ha-svg-icon <ha-svg-icon
.path=${domainIconWithoutDefault(computeDomain(this.service!)) || .path=${FIXED_DOMAIN_ICONS[domain] || DEFAULT_SERVICE_ICON}
mdiRoomService}
></ha-svg-icon> ></ha-svg-icon>
`; `;
} }

View File

@ -2,7 +2,8 @@ import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until"; import { until } from "lit/directives/until";
import { stateIconPath } from "../common/entity/state_icon_path"; import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../common/const";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import { entityIcon } from "../data/icons"; import { entityIcon } from "../data/icons";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-icon"; import "./ha-icon";
@ -44,9 +45,13 @@ export class HaStateIcon extends LitElement {
} }
private _renderFallback() { private _renderFallback() {
return html`<ha-svg-icon const domain = computeStateDomain(this.stateObj!);
.path=${stateIconPath(this.stateObj)}
></ha-svg-icon>`; return html`
<ha-svg-icon
.path=${FIXED_DOMAIN_ICONS[domain] || DEFAULT_DOMAIN_ICON}
></ha-svg-icon>
`;
} }
} }

View File

@ -17,10 +17,11 @@ import {
mdiRoomService, mdiRoomService,
mdiShuffleDisabled, mdiShuffleDisabled,
} from "@mdi/js"; } from "@mdi/js";
import { css, html, LitElement, PropertyValues } from "lit"; import { LitElement, PropertyValues, css, html } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { ensureArray } from "../../common/array/ensure-array"; import { ensureArray } from "../../common/array/ensure-array";
import { fireEvent } from "../../common/dom/fire_event";
import { ACTION_ICONS } from "../../data/action";
import { Condition, Trigger } from "../../data/automation"; import { Condition, Trigger } from "../../data/automation";
import { import {
Action, Action,
@ -45,7 +46,6 @@ import "./hat-graph-branch";
import { BRANCH_HEIGHT, NODE_SIZE, SPACING } from "./hat-graph-const"; import { BRANCH_HEIGHT, NODE_SIZE, SPACING } from "./hat-graph-const";
import "./hat-graph-node"; import "./hat-graph-node";
import "./hat-graph-spacer"; import "./hat-graph-spacer";
import { ACTION_ICONS } from "../../data/action";
export interface NodeInfo { export interface NodeInfo {
path: string; path: string;

View File

@ -2,6 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
import { computeObjectId } from "../common/entity/compute_object_id"; import { computeObjectId } from "../common/entity/compute_object_id";
import { computeStateDomain } from "../common/entity/compute_state_domain"; import { computeStateDomain } from "../common/entity/compute_state_domain";
import { stateIcon } from "../common/entity/state_icon";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { import {
EntityRegistryDisplayEntry, EntityRegistryDisplayEntry,
@ -49,7 +50,7 @@ interface ComponentIcons {
} }
interface ServiceIcons { interface ServiceIcons {
[domain: string]: Record<string, string>; [service: string]: string;
} }
export type IconCategory = "entity" | "entity_component" | "services"; export type IconCategory = "entity" | "entity_component" | "services";
@ -127,26 +128,18 @@ export const getServiceIcons = async (
export const entityIcon = async ( export const entityIcon = async (
hass: HomeAssistant, hass: HomeAssistant,
state: HassEntity, stateObj: HassEntity,
stateValue?: string state?: string
) => { ) => {
const entity = hass.entities?.[state.entity_id] as const entry = hass.entities?.[stateObj.entity_id] as
| EntityRegistryDisplayEntry | EntityRegistryDisplayEntry
| undefined; | undefined;
if (entity?.icon) { if (entry?.icon) {
return entity.icon; return entry.icon;
} }
const domain = computeStateDomain(state); const domain = computeStateDomain(stateObj);
const deviceClass = state.attributes.device_class;
return getEntityIcon( return getEntityIcon(hass, domain, stateObj, state, entry);
hass,
domain,
deviceClass,
stateValue ?? state.state,
entity?.platform,
entity?.translation_key
);
}; };
export const entryIcon = async ( export const entryIcon = async (
@ -157,39 +150,41 @@ export const entryIcon = async (
return entry.icon; return entry.icon;
} }
const domain = computeDomain(entry.entity_id); const domain = computeDomain(entry.entity_id);
return getEntityIcon( return getEntityIcon(hass, domain, undefined, undefined, entry);
hass,
domain,
undefined,
undefined,
entry.platform,
entry.translation_key
);
}; };
const getEntityIcon = async ( const getEntityIcon = async (
hass: HomeAssistant, hass: HomeAssistant,
domain: string, domain: string,
deviceClass?: string, stateObj?: HassEntity,
value?: string, stateValue?: string,
platform?: string, entry?: EntityRegistryEntry | EntityRegistryDisplayEntry
translation_key?: string
) => { ) => {
const platform = entry?.platform;
const translation_key = entry?.translation_key;
const device_class = stateObj?.attributes.device_class;
const state = stateValue ?? stateObj?.state;
let icon: string | undefined; let icon: string | undefined;
if (translation_key && platform) { if (translation_key && platform) {
const platformIcons = await getPlatformIcons(hass, platform); const platformIcons = await getPlatformIcons(hass, platform);
if (platformIcons) { if (platformIcons) {
const translations = platformIcons[domain]?.[translation_key]; const translations = platformIcons[domain]?.[translation_key];
icon = (value && translations?.state?.[value]) || translations?.default; icon = (state && translations?.state?.[state]) || translations?.default;
} }
} }
if (!icon && stateObj) {
icon = stateIcon(stateObj, state);
}
if (!icon) { if (!icon) {
const entityComponentIcons = await getComponentIcons(hass, domain); const entityComponentIcons = await getComponentIcons(hass, domain);
if (entityComponentIcons) { if (entityComponentIcons) {
const translations = const translations =
(deviceClass && entityComponentIcons[deviceClass]) || (device_class && entityComponentIcons[device_class]) ||
entityComponentIcons._; entityComponentIcons._;
icon = (value && translations?.state?.[value]) || translations?.default; icon = (state && translations?.state?.[state]) || translations?.default;
} }
} }
return icon; return icon;
@ -234,12 +229,34 @@ export const attributeIcon = async (
return icon; return icon;
}; };
export const serviceIcon = async (hass: HomeAssistant, service: string) => { export const serviceIcon = async (
hass: HomeAssistant,
service: string
): Promise<string | undefined> => {
let icon: string | undefined;
const domain = computeDomain(service); const domain = computeDomain(service);
const serviceName = computeObjectId(service); const serviceName = computeObjectId(service);
const serviceIcons = await getServiceIcons(hass, domain); const serviceIcons = await getServiceIcons(hass, domain);
if (serviceIcons) { if (serviceIcons) {
return serviceIcons[serviceName]; icon = serviceIcons[serviceName];
}
if (!icon) {
icon = await domainIcon(hass, domain);
}
return icon;
};
export const domainIcon = async (
hass: HomeAssistant,
domain: string,
deviceClass?: string
): Promise<string | undefined> => {
const entityComponentIcons = await getComponentIcons(hass, domain);
if (entityComponentIcons) {
const translations =
(deviceClass && entityComponentIcons[deviceClass]) ||
entityComponentIcons._;
return translations?.default;
} }
return undefined; return undefined;
}; };

View File

@ -8,7 +8,6 @@ import {
mdiWeatherLightning, mdiWeatherLightning,
mdiWeatherLightningRainy, mdiWeatherLightningRainy,
mdiWeatherNight, mdiWeatherNight,
mdiWeatherNightPartlyCloudy,
mdiWeatherPartlyCloudy, mdiWeatherPartlyCloudy,
mdiWeatherPouring, mdiWeatherPouring,
mdiWeatherRainy, mdiWeatherRainy,
@ -520,13 +519,6 @@ export const getWeatherStateIcon = (
return undefined; return undefined;
}; };
export const weatherIcon = (state?: string, nightTime?: boolean): string =>
!state
? undefined
: nightTime && state === "partlycloudy"
? mdiWeatherNightPartlyCloudy
: weatherIcons[state];
const EIGHT_HOURS = 28800000; const EIGHT_HOURS = 28800000;
const DAY_IN_MILLISECONDS = 86400000; const DAY_IN_MILLISECONDS = 86400000;

View File

@ -8,7 +8,7 @@ import {
mdiReload, mdiReload,
mdiServerNetwork, mdiServerNetwork,
} from "@mdi/js"; } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, TemplateResult, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
@ -17,9 +17,7 @@ import { canShowPage } from "../../common/config/can_show_page";
import { componentsWithService } from "../../common/config/components_with_service"; import { componentsWithService } from "../../common/config/components_with_service";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
import { domainIcon } from "../../common/entity/domain_icon";
import { navigate } from "../../common/navigate"; import { navigate } from "../../common/navigate";
import { caseInsensitiveStringCompare } from "../../common/string/compare"; import { caseInsensitiveStringCompare } from "../../common/string/compare";
import { import {
@ -27,9 +25,9 @@ import {
fuzzyFilterSort, fuzzyFilterSort,
} from "../../common/string/filter/sequence-matching"; } from "../../common/string/filter/sequence-matching";
import { debounce } from "../../common/util/debounce"; import { debounce } from "../../common/util/debounce";
import "../../components/ha-label";
import "../../components/ha-circular-progress"; import "../../components/ha-circular-progress";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-label";
import "../../components/ha-list-item"; import "../../components/ha-list-item";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import { fetchHassioAddonsInfo } from "../../data/hassio/addon"; import { fetchHassioAddonsInfo } from "../../data/hassio/addon";
@ -56,7 +54,7 @@ interface CommandItem extends QuickBarItem {
interface EntityItem extends QuickBarItem { interface EntityItem extends QuickBarItem {
altText: string; altText: string;
icon?: string; icon?: TemplateResult;
} }
const isCommandItem = (item: QuickBarItem): item is CommandItem => const isCommandItem = (item: QuickBarItem): item is CommandItem =>
@ -296,16 +294,14 @@ export class QuickBar extends LitElement {
graphic="icon" graphic="icon"
> >
${item.iconPath ${item.iconPath
? html`<ha-svg-icon ? html`
<ha-svg-icon
.path=${item.iconPath} .path=${item.iconPath}
class="entity" class="entity"
slot="graphic" slot="graphic"
></ha-svg-icon>` ></ha-svg-icon>
: html`<ha-icon `
.icon=${item.icon} : html`<span slot="graphic">${item.icon}</span>`}
class="entity"
slot="graphic"
></ha-icon>`}
<span>${item.primaryText}</span> <span>${item.primaryText}</span>
${item.altText ${item.altText
? html` ? html`
@ -476,10 +472,12 @@ export class QuickBar extends LitElement {
const entityItem = { const entityItem = {
primaryText: computeStateName(entityState), primaryText: computeStateName(entityState),
altText: entityId, altText: entityId,
icon: entityState.attributes.icon, icon: html`
iconPath: entityState.attributes.icon <ha-state-icon
? undefined .hass=${this.hass}
: domainIcon(computeDomain(entityId), entityState), .stateObj=${entityState}
></ha-state-icon>
`,
action: () => fireEvent(this, "hass-more-info", { entityId }), action: () => fireEvent(this, "hass-more-info", { entityId }),
}; };

View File

@ -1,4 +1,5 @@
import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list";
import "@material/web/divider/divider";
import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js"; import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js";
import Fuse, { IFuseOptions } from "fuse.js"; import Fuse, { IFuseOptions } from "fuse.js";
import { import {
@ -16,17 +17,21 @@ import { repeat } from "lit/directives/repeat";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { domainIconWithoutDefault } from "../../../common/entity/domain_icon"; import { computeDomain } from "../../../common/entity/compute_domain";
import { stringCompare } from "../../../common/string/compare"; import { stringCompare } from "../../../common/string/compare";
import { LocalizeFunc } from "../../../common/translations/localize"; import { LocalizeFunc } from "../../../common/translations/localize";
import { deepEqual } from "../../../common/util/deep-equal";
import "../../../components/ha-dialog"; import "../../../components/ha-dialog";
import type { HaDialog } from "../../../components/ha-dialog"; import type { HaDialog } from "../../../components/ha-dialog";
import "../../../components/ha-dialog-header"; import "../../../components/ha-dialog-header";
import "../../../components/ha-domain-icon";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-icon-button-prev"; import "../../../components/ha-icon-button-prev";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import "../../../components/ha-list-new";
import "../../../components/ha-list-item-new"; import "../../../components/ha-list-item-new";
import "../../../components/ha-list-new";
import "../../../components/ha-service-icon";
import "../../../components/search-input";
import { import {
ACTION_GROUPS, ACTION_GROUPS,
ACTION_ICONS, ACTION_ICONS,
@ -36,6 +41,7 @@ import {
} from "../../../data/action"; } from "../../../data/action";
import { AutomationElementGroup } from "../../../data/automation"; import { AutomationElementGroup } from "../../../data/automation";
import { CONDITION_GROUPS, CONDITION_ICONS } from "../../../data/condition"; import { CONDITION_GROUPS, CONDITION_ICONS } from "../../../data/condition";
import { getServiceIcons } from "../../../data/icons";
import { import {
IntegrationManifest, IntegrationManifest,
domainToName, domainToName,
@ -45,16 +51,10 @@ import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger";
import { HassDialog } from "../../../dialogs/make-dialog-manager"; import { HassDialog } from "../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../resources/styles"; import { haStyle, haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { import {
AddAutomationElementDialogParams, AddAutomationElementDialogParams,
PASTE_VALUE, PASTE_VALUE,
} from "./show-add-automation-element-dialog"; } from "./show-add-automation-element-dialog";
import { computeDomain } from "../../../common/entity/compute_domain";
import { deepEqual } from "../../../common/util/deep-equal";
import "../../../components/search-input";
import "@material/web/divider/divider";
import { getServiceIcons } from "../../../data/icons";
const TYPES = { const TYPES = {
trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS }, trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS },
@ -74,7 +74,6 @@ interface ListItem {
description: string; description: string;
iconPath?: string; iconPath?: string;
icon?: TemplateResult; icon?: TemplateResult;
image?: string;
group: boolean; group: boolean;
} }
@ -318,17 +317,15 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
(!domainUsed && manifest?.integration_type === "entity") || (!domainUsed && manifest?.integration_type === "entity") ||
!["helper", "entity"].includes(manifest?.integration_type || ""))) !["helper", "entity"].includes(manifest?.integration_type || "")))
) { ) {
const icon = domainIconWithoutDefault(domain);
result.push({ result.push({
group: true, group: true,
iconPath: icon, icon: html`
image: !icon <ha-domain-icon
? brandsUrl({ .hass=${this.hass}
domain, .domain=${domain}
type: "icon", brandFallback
darkOptimized: this.hass.themes?.darkMode, ></ha-domain-icon>
}) `,
: undefined,
key: `${SERVICE_PREFIX}${domain}`, key: `${SERVICE_PREFIX}${domain}`,
name: domainToName(localize, domain, manifest), name: domainToName(localize, domain, manifest),
description: "", description: "",
@ -364,10 +361,12 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
for (const service of services_keys) { for (const service of services_keys) {
result.push({ result.push({
group: false, group: false,
icon: html`<ha-service-icon icon: html`
<ha-service-icon
.hass=${this.hass} .hass=${this.hass}
.service=${`${dmn}.${service}`} .service=${`${dmn}.${service}`}
></ha-service-icon>`, ></ha-service-icon>
`,
key: `${SERVICE_PREFIX}${dmn}.${service}`, key: `${SERVICE_PREFIX}${dmn}.${service}`,
name: `${domain ? "" : `${domainToName(localize, dmn)}: `}${ name: `${domain ? "" : `${domainToName(localize, dmn)}: `}${
this.hass.localize(`component.${dmn}.services.${service}.name`) || this.hass.localize(`component.${dmn}.services.${service}.name`) ||
@ -578,13 +577,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
slot="start" slot="start"
.path=${item.iconPath} .path=${item.iconPath}
></ha-svg-icon>` ></ha-svg-icon>`
: html`<img : nothing}
alt=""
slot="start"
src=${item.image!}
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>`}
${item.group ${item.group
? html`<ha-icon-next slot="end"></ha-icon-next>` ? html`<ha-icon-next slot="end"></ha-icon-next>`
: html`<ha-svg-icon : html`<ha-svg-icon

View File

@ -12,11 +12,11 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/chips/ha-assist-chip"; import "../../../components/chips/ha-assist-chip";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-state-icon";
import "../../../components/ha-textfield"; import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield"; import type { HaTextField } from "../../../components/ha-textfield";
import { import {
@ -199,8 +199,11 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
@click=${this._handleMoreInfo} @click=${this._handleMoreInfo}
.label=${stateLabel} .label=${stateLabel}
> >
<ha-svg-icon slot="icon" .path=${alarmPanelIcon(stateObj.state)}> <ha-state-icon
</ha-svg-icon> slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
></ha-state-icon>
</ha-assist-chip> </ha-assist-chip>
</h1> </h1>
<div id="armActions" class="actions"> <div id="armActions" class="actions">

View File

@ -23,9 +23,8 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { FIXED_DEVICE_CLASS_ICONS, STATES_OFF } from "../../../common/const"; import { STATES_OFF } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { binarySensorIcon } from "../../../common/entity/binary_sensor_icon";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { import {
@ -36,8 +35,9 @@ import { blankBeforeUnit } from "../../../common/translations/blank_before_unit"
import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import { subscribeOne } from "../../../common/util/subscribe-one"; import { subscribeOne } from "../../../common/util/subscribe-one";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-domain-icon";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon"; import "../../../components/ha-state-icon";
import { import {
AreaRegistryEntry, AreaRegistryEntry,
subscribeAreaRegistry, subscribeAreaRegistry,
@ -388,13 +388,16 @@ export class HuiAreaCard
(entity) => entity.attributes.device_class === deviceClass (entity) => entity.attributes.device_class === deviceClass
) )
) { ) {
const icon = FIXED_DEVICE_CLASS_ICONS[deviceClass]; sensors.push(html`
sensors.push( <div class="sensor">
html`<div class="sensor"> <ha-domain-icon
${icon ? html`<ha-svg-icon .path=${icon}></ha-svg-icon>` : ""} .hass=${this.hass}
.domain=${domain}
.deviceClass=${deviceClass}
></ha-domain-icon>
${this._average(domain, deviceClass)} ${this._average(domain, deviceClass)}
</div> ` </div>
); `);
} }
}); });
}); });
@ -434,16 +437,18 @@ export class HuiAreaCard
<div class="alerts"> <div class="alerts">
${ALERT_DOMAINS.map((domain) => { ${ALERT_DOMAINS.map((domain) => {
if (!(domain in entitiesByDomain)) { if (!(domain in entitiesByDomain)) {
return ""; return nothing;
} }
return this._deviceClasses[domain].map((deviceClass) => { return this._deviceClasses[domain].map((deviceClass) => {
const entity = this._isOn(domain, deviceClass); const entity = this._isOn(domain, deviceClass);
return entity return entity
? html`<ha-svg-icon ? html`
<ha-state-icon
class="alert" class="alert"
.path=${DOMAIN_ICONS[domain][deviceClass] || .hass=${this.hass}
binarySensorIcon(entity.state, entity)} .stateObj=${entity}
></ha-svg-icon>` ></ha-state-icon>
`
: nothing; : nothing;
}); });
})} })}
@ -574,7 +579,14 @@ export class HuiAreaCard
padding: 16px; padding: 16px;
} }
.alerts ha-svg-icon { ha-state-icon {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
}
.alerts ha-state-icon {
background: var(--accent-color); background: var(--accent-color);
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-accent-color, var(--text-primary-color));
padding: 8px; padding: 8px;

View File

@ -16,7 +16,7 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { batteryIcon } from "../../../common/entity/battery_icon"; import { batteryLevelIcon } from "../../../common/entity/battery_icon";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -252,7 +252,7 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
private computeIcon(attr: string, batLvl: number): string { private computeIcon(attr: string, batLvl: number): string {
if (attr === "battery") { if (attr === "battery") {
return batteryIcon(batLvl); return batteryLevelIcon(batLvl);
} }
return SENSOR_ICONS[attr]; return SENSOR_ICONS[attr];
} }

View File

@ -1,7 +1,5 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-linear-progress/mwc-linear-progress"; import "@material/mwc-linear-progress/mwc-linear-progress";
import type { LinearProgress } from "@material/mwc-linear-progress/mwc-linear-progress"; import type { LinearProgress } from "@material/mwc-linear-progress/mwc-linear-progress";
import "@material/mwc-list/mwc-list-item";
import { import {
mdiChevronDown, mdiChevronDown,
mdiMonitor, mdiMonitor,
@ -12,11 +10,11 @@ import {
mdiVolumeHigh, mdiVolumeHigh,
} from "@mdi/js"; } from "@mdi/js";
import { import {
css,
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
PropertyValues, PropertyValues,
css,
html,
nothing, nothing,
} from "lit"; } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
@ -26,25 +24,29 @@ import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain"; import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
import { domainIcon } from "../../common/entity/domain_icon";
import { supportsFeature } from "../../common/entity/supports-feature"; import { supportsFeature } from "../../common/entity/supports-feature";
import { debounce } from "../../common/util/debounce";
import "../../components/ha-button"; import "../../components/ha-button";
import "../../components/ha-button-menu"; import "../../components/ha-button-menu";
import "../../components/ha-circular-progress"; import "../../components/ha-circular-progress";
import "../../components/ha-domain-icon";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-list-item";
import "../../components/ha-state-icon";
import "../../components/ha-svg-icon";
import { UNAVAILABLE } from "../../data/entity"; import { UNAVAILABLE } from "../../data/entity";
import { import {
BROWSER_PLAYER, BROWSER_PLAYER,
cleanupMediaTitle,
computeMediaControls,
computeMediaDescription,
ControlButton, ControlButton,
formatMediaTime,
getCurrentProgress,
handleMediaControlClick,
MediaPlayerEntity, MediaPlayerEntity,
MediaPlayerEntityFeature, MediaPlayerEntityFeature,
MediaPlayerItem, MediaPlayerItem,
cleanupMediaTitle,
computeMediaControls,
computeMediaDescription,
formatMediaTime,
getCurrentProgress,
handleMediaControlClick,
setMediaPlayerVolume, setMediaPlayerVolume,
} from "../../data/media-player"; } from "../../data/media-player";
import { ResolvedMediaSource } from "../../data/media_source"; import { ResolvedMediaSource } from "../../data/media_source";
@ -56,7 +58,6 @@ import {
BrowserMediaPlayer, BrowserMediaPlayer,
ERR_UNSUPPORTED_MEDIA, ERR_UNSUPPORTED_MEDIA,
} from "./browser-media-player"; } from "./browser-media-player";
import { debounce } from "../../common/util/debounce";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -324,18 +325,15 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
${ ${
this.narrow this.narrow
? html` ? html`
<ha-icon-button <ha-icon-button slot="trigger">
slot="trigger" ${this._renderIcon(isBrowser, stateObj)}
.path=${isBrowser </ha-icon-button>
? mdiMonitor
: domainIcon(computeDomain(this.entityId), stateObj)}
></ha-icon-button>
` `
: html` : html`
<ha-button <ha-button
slot="trigger" slot="trigger"
.label=${this.narrow .label=${this.narrow
? "" ? nothing
: `${ : `${
stateObj stateObj
? computeStateName(stateObj) ? computeStateName(stateObj)
@ -343,12 +341,9 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
} }
`} `}
> >
<ha-svg-icon <span slot="icon">
slot="icon" ${this._renderIcon(isBrowser, stateObj)}
.path=${isBrowser </span>
? mdiMonitor
: domainIcon(computeDomain(this.entityId), stateObj)}
></ha-svg-icon>
<ha-svg-icon <ha-svg-icon
slot="trailingIcon" slot="trailingIcon"
.path=${mdiChevronDown} .path=${mdiChevronDown}
@ -356,23 +351,23 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
</ha-button> </ha-button>
` `
} }
<mwc-list-item <ha-list-item
.player=${BROWSER_PLAYER} .player=${BROWSER_PLAYER}
?selected=${isBrowser} ?selected=${isBrowser}
@click=${this._selectPlayer} @click=${this._selectPlayer}
> >
${this.hass.localize("ui.components.media-browser.web-browser")} ${this.hass.localize("ui.components.media-browser.web-browser")}
</mwc-list-item> </ha-list-item>
${this._mediaPlayerEntities.map( ${this._mediaPlayerEntities.map(
(source) => html` (source) => html`
<mwc-list-item <ha-list-item
?selected=${source.entity_id === this.entityId} ?selected=${source.entity_id === this.entityId}
.disabled=${source.state === UNAVAILABLE} .disabled=${source.state === UNAVAILABLE}
.player=${source.entity_id} .player=${source.entity_id}
@click=${this._selectPlayer} @click=${this._selectPlayer}
> >
${computeStateName(source)} ${computeStateName(source)}
</mwc-list-item> </ha-list-item>
` `
)} )}
</ha-button-menu> </ha-button-menu>
@ -382,6 +377,23 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
`; `;
} }
private _renderIcon(isBrowser: boolean, stateObj?: MediaPlayerEntity) {
if (isBrowser) {
return html`<ha-svg-icon .path=${mdiMonitor}></ha-svg-icon>`;
}
if (stateObj) {
return html`
<ha-state-icon .hass=${this.hass} .stateObj=${stateObj}></ha-state-icon>
`;
}
return html`
<ha-domain-icon
.hass=${this.hass}
.domain=${computeDomain(this.entityId)}
></ha-domain-icon>
`;
}
public willUpdate(changedProps: PropertyValues) { public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps); super.willUpdate(changedProps);
if (changedProps.has("entityId")) { if (changedProps.has("entityId")) {
@ -571,9 +583,10 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
--mdc-theme-primary: var(--secondary-text-color); --mdc-theme-primary: var(--secondary-text-color);
} }
mwc-button[slot="trigger"] { ha-button-menu ha-button[slot="trigger"] {
line-height: 1;
--mdc-theme-primary: var(--primary-text-color); --mdc-theme-primary: var(--primary-text-color);
--mdc-icon-size: 36px; --mdc-icon-size: 16px;
} }
.info { .info {
@ -655,10 +668,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
margin: 16px 0 16px 16px; margin: 16px 0 16px 16px;
} }
ha-button-menu mwc-button {
line-height: 1;
}
:host([narrow]) { :host([narrow]) {
height: 57px; height: 57px;
} }
@ -705,10 +714,15 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
left: 0; left: 0;
} }
mwc-list-item[selected] { ha-list-item[selected] {
font-weight: bold; font-weight: bold;
} }
span[slot="icon"] {
display: flex;
align-items: center;
}
ha-svg-icon[slot="trailingIcon"] { ha-svg-icon[slot="trailingIcon"] {
margin-inline-start: 8px !important; margin-inline-start: 8px !important;
margin-inline-end: 0px !important; margin-inline-end: 0px !important;