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. */
import {
mdiAccount,
mdiAirFilter,
mdiAlert,
mdiAngleAcute,
@ -50,8 +51,10 @@ import {
mdiProgressClock,
mdiRayVertex,
mdiRemote,
mdiRobot,
mdiRobotMower,
mdiRobotVacuum,
mdiRoomService,
mdiScriptText,
mdiSineWave,
mdiSpeakerMessage,
@ -61,6 +64,7 @@ import {
mdiThermometerLines,
mdiThermostat,
mdiTimerOutline,
mdiToggleSwitch,
mdiTransmissionTower,
mdiWater,
mdiWaterPercent,
@ -69,6 +73,7 @@ import {
mdiWeatherRainy,
mdiWeatherWindy,
mdiWeight,
mdiWhiteBalanceSunny,
mdiWifi,
} 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.
// 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. */
export const DEFAULT_DOMAIN_ICON = mdiBookmark;
@ -85,20 +93,23 @@ export const DEFAULT_DOMAIN_ICON = mdiBookmark;
export const FIXED_DOMAIN_ICONS = {
air_quality: mdiAirFilter,
alert: mdiAlert,
automation: mdiRobot,
calendar: mdiCalendar,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
datetime: mdiCalendarClock,
date: mdiCalendar,
datetime: mdiCalendarClock,
demo: mdiHomeAssistant,
device_tracker: mdiAccount,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
homeassistant: mdiHomeAssistant,
homekit: mdiHomeAutomation,
image: mdiImage,
image_processing: mdiImageFilterFrames,
image: mdiImage,
input_boolean: mdiToggleSwitch,
input_button: mdiButtonPointer,
input_datetime: mdiCalendarClock,
input_number: mdiRayVertex,
@ -110,6 +121,7 @@ export const FIXED_DOMAIN_ICONS = {
notify: mdiCommentAlert,
number: mdiRayVertex,
persistent_notification: mdiBell,
person: mdiAccount,
plant: mdiFlower,
proximity: mdiAppleSafari,
remote: mdiRemote,
@ -121,10 +133,11 @@ export const FIXED_DOMAIN_ICONS = {
simple_alarm: mdiBell,
siren: mdiBullhorn,
stt: mdiMicrophoneMessage,
sun: mdiWhiteBalanceSunny,
text: mdiFormTextbox,
todo: mdiClipboardList,
time: mdiClock,
timer: mdiTimerOutline,
todo: mdiClipboardList,
tts: mdiSpeakerMessage,
vacuum: mdiRobotVacuum,
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";
const BATTERY_ICONS = {
10: mdiBattery10,
20: mdiBattery20,
30: mdiBattery30,
40: mdiBattery40,
50: mdiBattery50,
60: mdiBattery60,
70: mdiBattery70,
80: mdiBattery80,
90: mdiBattery90,
100: mdiBattery,
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: mdiBatteryCharging10,
20: mdiBatteryCharging20,
30: mdiBatteryCharging30,
40: mdiBatteryCharging40,
50: mdiBatteryCharging50,
60: mdiBatteryCharging60,
70: mdiBatteryCharging70,
80: mdiBatteryCharging80,
90: mdiBatteryCharging90,
100: mdiBatteryCharging,
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",
};
export const batteryStateIcon = (
batteryState: HassEntity,
batteryChargingState?: HassEntity
) => {
const battery = batteryState.state;
const batteryCharging =
batteryChargingState && batteryChargingState.state === "on";
return batteryIcon(battery, batteryCharging);
export const batteryIcon = (stateObj: HassEntity, state?: string) => {
const level = state ?? stateObj.state;
return batteryLevelIcon(level);
};
export const batteryIcon = (
batteryState: number | string,
batteryCharging?: boolean
) => {
const batteryValue = Number(batteryState);
export const batteryLevelIcon = (
batteryLevel: number | string,
isBatteryCharging?: boolean
): string => {
const batteryValue = Number(batteryLevel);
if (isNaN(batteryValue)) {
if (batteryState === "off") {
return mdiBattery;
if (batteryLevel === "off") {
return "mdi:battery";
}
if (batteryState === "on") {
return mdiBatteryAlert;
if (batteryLevel === "on") {
return "mdi:battery-alert";
}
return mdiBatteryUnknown;
return "mdi:battery-unknown";
}
const batteryRound = Math.round(batteryValue / 10) * 10;
if (batteryCharging && batteryValue >= 10) {
if (isBatteryCharging && batteryValue >= 10) {
return BATTERY_CHARGING_ICONS[batteryRound];
}
if (batteryCharging) {
return mdiBatteryChargingOutline;
if (isBatteryCharging) {
return "mdi:battery-charging-outline";
}
if (batteryValue <= 5) {
return mdiBatteryAlertVariantOutline;
return "mdi:battery-alert-variant-outline";
}
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. */
import {
mdiArrowUpBox,
mdiArrowDownBox,
mdiGarage,
mdiGarageOpen,
mdiGateArrowRight,
mdiGate,
mdiGateOpen,
mdiDoorOpen,
mdiDoorClosed,
mdiCircle,
mdiWindowShutter,
mdiWindowShutterOpen,
mdiBlindsHorizontal,
mdiBlindsHorizontalClosed,
mdiRollerShade,
mdiRollerShadeClosed,
mdiWindowClosed,
mdiWindowOpen,
mdiArrowExpandHorizontal,
mdiArrowUp,
mdiArrowCollapseHorizontal,
mdiArrowDown,
mdiCircleSlice8,
mdiArrowSplitVertical,
mdiCurtains,
mdiCurtainsClosed,
mdiArrowExpandHorizontal,
mdiArrowUp,
} from "@mdi/js";
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 => {
switch (stateObj.attributes.device_class) {
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;
}
.mdc-data-table__cell--icon:first-child ha-icon,
.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-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;
}
: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 { batteryStateIcon } from "../../common/entity/battery_icon";
import "../ha-svg-icon";
import { batteryLevelIcon } from "../../common/entity/battery_icon";
import "../ha-icon";
@customElement("ha-battery-icon")
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() {
if (!this.batteryStateObj) return nothing;
return html`
<ha-svg-icon
.path=${batteryStateIcon(
this.batteryStateObj,
this.batteryChargingStateObj
<ha-icon
.icon=${batteryLevelIcon(
this.batteryStateObj.state,
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 { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until";
import { DEFAULT_SERVICE_ICON, FIXED_DOMAIN_ICONS } from "../common/const";
import { computeDomain } from "../common/entity/compute_domain";
import { domainIconWithoutDefault } from "../common/entity/domain_icon";
import { serviceIcon } from "../data/icons";
import { HomeAssistant } from "../types";
import "./ha-icon";
@ -41,10 +40,11 @@ export class HaServiceIcon extends LitElement {
}
private _renderFallback() {
const domain = computeDomain(this.service!);
return html`
<ha-svg-icon
.path=${domainIconWithoutDefault(computeDomain(this.service!)) ||
mdiRoomService}
.path=${FIXED_DOMAIN_ICONS[domain] || DEFAULT_SERVICE_ICON}
></ha-svg-icon>
`;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import "@material/mwc-list/mwc-list";
import "@material/web/divider/divider";
import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js";
import Fuse, { IFuseOptions } from "fuse.js";
import {
@ -16,17 +17,21 @@ import { repeat } from "lit/directives/repeat";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
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 { LocalizeFunc } from "../../../common/translations/localize";
import { deepEqual } from "../../../common/util/deep-equal";
import "../../../components/ha-dialog";
import type { HaDialog } from "../../../components/ha-dialog";
import "../../../components/ha-dialog-header";
import "../../../components/ha-domain-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-button-prev";
import "../../../components/ha-icon-next";
import "../../../components/ha-list-new";
import "../../../components/ha-list-item-new";
import "../../../components/ha-list-new";
import "../../../components/ha-service-icon";
import "../../../components/search-input";
import {
ACTION_GROUPS,
ACTION_ICONS,
@ -36,6 +41,7 @@ import {
} from "../../../data/action";
import { AutomationElementGroup } from "../../../data/automation";
import { CONDITION_GROUPS, CONDITION_ICONS } from "../../../data/condition";
import { getServiceIcons } from "../../../data/icons";
import {
IntegrationManifest,
domainToName,
@ -45,16 +51,10 @@ import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger";
import { HassDialog } from "../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import {
AddAutomationElementDialogParams,
PASTE_VALUE,
} 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 = {
trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS },
@ -74,7 +74,6 @@ interface ListItem {
description: string;
iconPath?: string;
icon?: TemplateResult;
image?: string;
group: boolean;
}
@ -318,17 +317,15 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
(!domainUsed && manifest?.integration_type === "entity") ||
!["helper", "entity"].includes(manifest?.integration_type || "")))
) {
const icon = domainIconWithoutDefault(domain);
result.push({
group: true,
iconPath: icon,
image: !icon
? brandsUrl({
domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})
: undefined,
icon: html`
<ha-domain-icon
.hass=${this.hass}
.domain=${domain}
brandFallback
></ha-domain-icon>
`,
key: `${SERVICE_PREFIX}${domain}`,
name: domainToName(localize, domain, manifest),
description: "",
@ -364,10 +361,12 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
for (const service of services_keys) {
result.push({
group: false,
icon: html`<ha-service-icon
.hass=${this.hass}
.service=${`${dmn}.${service}`}
></ha-service-icon>`,
icon: html`
<ha-service-icon
.hass=${this.hass}
.service=${`${dmn}.${service}`}
></ha-service-icon>
`,
key: `${SERVICE_PREFIX}${dmn}.${service}`,
name: `${domain ? "" : `${domainToName(localize, dmn)}: `}${
this.hass.localize(`component.${dmn}.services.${service}.name`) ||
@ -578,13 +577,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
slot="start"
.path=${item.iconPath}
></ha-svg-icon>`
: html`<img
alt=""
slot="start"
src=${item.image!}
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>`}
: nothing}
${item.group
? html`<ha-icon-next slot="end"></ha-icon-next>`
: 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 { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/chips/ha-assist-chip";
import "../../../components/ha-card";
import "../../../components/ha-state-icon";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import {
@ -199,8 +199,11 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
@click=${this._handleMoreInfo}
.label=${stateLabel}
>
<ha-svg-icon slot="icon" .path=${alarmPanelIcon(stateObj.state)}>
</ha-svg-icon>
<ha-state-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
></ha-state-icon>
</ha-assist-chip>
</h1>
<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 { styleMap } from "lit/directives/style-map";
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 { binarySensorIcon } from "../../../common/entity/binary_sensor_icon";
import { computeDomain } from "../../../common/entity/compute_domain";
import { navigate } from "../../../common/navigate";
import {
@ -36,8 +35,9 @@ import { blankBeforeUnit } from "../../../common/translations/blank_before_unit"
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import { subscribeOne } from "../../../common/util/subscribe-one";
import "../../../components/ha-card";
import "../../../components/ha-domain-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-state-icon";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
@ -388,13 +388,16 @@ export class HuiAreaCard
(entity) => entity.attributes.device_class === deviceClass
)
) {
const icon = FIXED_DEVICE_CLASS_ICONS[deviceClass];
sensors.push(
html`<div class="sensor">
${icon ? html`<ha-svg-icon .path=${icon}></ha-svg-icon>` : ""}
sensors.push(html`
<div class="sensor">
<ha-domain-icon
.hass=${this.hass}
.domain=${domain}
.deviceClass=${deviceClass}
></ha-domain-icon>
${this._average(domain, deviceClass)}
</div> `
);
</div>
`);
}
});
});
@ -434,16 +437,18 @@ export class HuiAreaCard
<div class="alerts">
${ALERT_DOMAINS.map((domain) => {
if (!(domain in entitiesByDomain)) {
return "";
return nothing;
}
return this._deviceClasses[domain].map((deviceClass) => {
const entity = this._isOn(domain, deviceClass);
return entity
? html`<ha-svg-icon
class="alert"
.path=${DOMAIN_ICONS[domain][deviceClass] ||
binarySensorIcon(entity.state, entity)}
></ha-svg-icon>`
? html`
<ha-state-icon
class="alert"
.hass=${this.hass}
.stateObj=${entity}
></ha-state-icon>
`
: nothing;
});
})}
@ -574,7 +579,14 @@ export class HuiAreaCard
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);
color: var(--text-accent-color, var(--text-primary-color));
padding: 8px;

View File

@ -16,7 +16,7 @@ import {
import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
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 "../../../components/ha-card";
import "../../../components/ha-svg-icon";
@ -252,7 +252,7 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
private computeIcon(attr: string, batLvl: number): string {
if (attr === "battery") {
return batteryIcon(batLvl);
return batteryLevelIcon(batLvl);
}
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 type { LinearProgress } from "@material/mwc-linear-progress/mwc-linear-progress";
import "@material/mwc-list/mwc-list-item";
import {
mdiChevronDown,
mdiMonitor,
@ -12,11 +10,11 @@ import {
mdiVolumeHigh,
} from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
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 { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { domainIcon } from "../../common/entity/domain_icon";
import { supportsFeature } from "../../common/entity/supports-feature";
import { debounce } from "../../common/util/debounce";
import "../../components/ha-button";
import "../../components/ha-button-menu";
import "../../components/ha-circular-progress";
import "../../components/ha-domain-icon";
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 {
BROWSER_PLAYER,
cleanupMediaTitle,
computeMediaControls,
computeMediaDescription,
ControlButton,
formatMediaTime,
getCurrentProgress,
handleMediaControlClick,
MediaPlayerEntity,
MediaPlayerEntityFeature,
MediaPlayerItem,
cleanupMediaTitle,
computeMediaControls,
computeMediaDescription,
formatMediaTime,
getCurrentProgress,
handleMediaControlClick,
setMediaPlayerVolume,
} from "../../data/media-player";
import { ResolvedMediaSource } from "../../data/media_source";
@ -56,7 +58,6 @@ import {
BrowserMediaPlayer,
ERR_UNSUPPORTED_MEDIA,
} from "./browser-media-player";
import { debounce } from "../../common/util/debounce";
declare global {
interface HASSDomEvents {
@ -320,22 +321,19 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
: ""
}
<ha-button-menu >
<ha-button-menu>
${
this.narrow
? html`
<ha-icon-button
slot="trigger"
.path=${isBrowser
? mdiMonitor
: domainIcon(computeDomain(this.entityId), stateObj)}
></ha-icon-button>
<ha-icon-button slot="trigger">
${this._renderIcon(isBrowser, stateObj)}
</ha-icon-button>
`
: html`
<ha-button
slot="trigger"
.label=${this.narrow
? ""
? nothing
: `${
stateObj
? computeStateName(stateObj)
@ -343,12 +341,9 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
}
`}
>
<ha-svg-icon
slot="icon"
.path=${isBrowser
? mdiMonitor
: domainIcon(computeDomain(this.entityId), stateObj)}
></ha-svg-icon>
<span slot="icon">
${this._renderIcon(isBrowser, stateObj)}
</span>
<ha-svg-icon
slot="trailingIcon"
.path=${mdiChevronDown}
@ -356,23 +351,23 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
</ha-button>
`
}
<mwc-list-item
<ha-list-item
.player=${BROWSER_PLAYER}
?selected=${isBrowser}
@click=${this._selectPlayer}
>
${this.hass.localize("ui.components.media-browser.web-browser")}
</mwc-list-item>
</ha-list-item>
${this._mediaPlayerEntities.map(
(source) => html`
<mwc-list-item
<ha-list-item
?selected=${source.entity_id === this.entityId}
.disabled=${source.state === UNAVAILABLE}
.player=${source.entity_id}
@click=${this._selectPlayer}
>
${computeStateName(source)}
</mwc-list-item>
</ha-list-item>
`
)}
</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) {
super.willUpdate(changedProps);
if (changedProps.has("entityId")) {
@ -571,9 +583,10 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
--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-icon-size: 36px;
--mdc-icon-size: 16px;
}
.info {
@ -655,10 +668,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
margin: 16px 0 16px 16px;
}
ha-button-menu mwc-button {
line-height: 1;
}
:host([narrow]) {
height: 57px;
}
@ -705,10 +714,15 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
left: 0;
}
mwc-list-item[selected] {
ha-list-item[selected] {
font-weight: bold;
}
span[slot="icon"] {
display: flex;
align-items: center;
}
ha-svg-icon[slot="trailingIcon"] {
margin-inline-start: 8px !important;
margin-inline-end: 0px !important;