Skip to content

Commit

Permalink
Home Assistant Icons (#2161)
Browse files Browse the repository at this point in the history
  • Loading branch information
timmo001 committed May 21, 2023
1 parent 0bb32e3 commit 621d6df
Show file tree
Hide file tree
Showing 5 changed files with 1,013 additions and 16 deletions.
34 changes: 34 additions & 0 deletions scripts/pullHomeAssistantFrontendHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { join } = require("path");
const { writeFile } = require("fs");

const homeAssistantConstantsUrl =
"https://raw.githubusercontent.com/home-assistant/frontend/dev/src/common/const.ts";

console.log("Pulling Home Assistant Frontend Constants from GitHub...");

fetch(homeAssistantConstantsUrl, { method: "GET" })
.then((response) => {
response.text().then((text) => {
writeFile(
join(__dirname, "../src/utils/homeAssistant/const.ts"),
`// Sourced from ${homeAssistantConstantsUrl}\n\n${text}`,
(error) => {
if (error) {
console.error(
"Error writing Home Assistant Frontend Constants to file: " +
error
);
} else {
console.log(
"Home Assistant Frontend Constants successfully pulled from GitHub!"
);
}
}
);
});
})
.catch((error) => {
console.error(
"Error pulling Home Assistant Frontend Constants from GitHub: " + error
);
});
35 changes: 19 additions & 16 deletions src/components/dashboard/views/widgets/HomeAssistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { HassEntity } from "home-assistant-js-websocket";
import { Icon } from "@mdi/react";
import { IconButton, Typography } from "@mui/material";

import { DEFAULT_DOMAIN_ICON } from "@/utils/homeAssistant/const";
import { domainIcon } from "@/utils/homeAssistant/icons";
import { useHomeAssistant } from "@/providers/HomeAssistantProvider";
import { WidgetAction } from "@/types/widget.type";
import { WidgetImage } from "@/components/dashboard/views/widgets/Image";
Expand Down Expand Up @@ -34,25 +36,26 @@ export function WidgetHomeAssistant({
return true;
}, [entity, homeAssistant.services]);

const entityIcon = useMemo<string>(() => {
if (!entity?.attributes.icon) return "mdi:help";
if (entity.attributes.icon.startsWith("mdi:")) {
return entity.attributes.icon;
}
return `mdi:${entity.attributes.icon}`;
}, [entity?.attributes.icon]);

const mdiIcon = useMemo<string>(() => {
try {
const iconPath = entityIcon.replace(/[:|-](\w)/g, (_, match: string) =>
match.toUpperCase()
if (!entity) return DEFAULT_DOMAIN_ICON;
const domain = entity.entity_id.split(".")[0];
let icon = domainIcon(domain, entity, entity.state);

if (entity.attributes?.icon) {
const iconPath = entity.attributes.icon.replace(
/[:|-](\w)/g,
(_, match: string) => match.toUpperCase()
);
return require(`materialdesign-js/icons/${iconPath}`).default;
} catch (e) {
console.warn(`Could not load icon ${entityIcon}:`, e);
return require(`materialdesign-js/icons/mdiHelp`).default;

try {
icon = require(`materialdesign-js/icons/${iconPath}`).default;
} catch (e) {
console.warn(`Could not load icon ${iconPath}:`, e);
if (!icon) icon = require(`materialdesign-js/icons/mdiHelp`).default;
}
}
}, [entityIcon]);
return icon;
}, [entity]);

const icon = (
<Icon
Expand Down
263 changes: 263 additions & 0 deletions src/utils/homeAssistant/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// Sourced from https://raw.githubusercontent.com/home-assistant/frontend/dev/src/common/const.ts

/** Constants to be used in the frontend. */

import {
mdiAirFilter,
mdiAlert,
mdiAngleAcute,
mdiAppleSafari,
mdiArrowLeftRight,
mdiBell,
mdiBookmark,
mdiBrightness5,
mdiBullhorn,
mdiCalendar,
mdiCalendarClock,
mdiCarCoolantLevel,
mdiCash,
mdiClock,
mdiCloudUpload,
mdiCog,
mdiCommentAlert,
mdiCounter,
mdiCurrentAc,
mdiDatabase,
mdiEarHearing,
mdiEye,
mdiFlash,
mdiFlower,
mdiFormatListBulleted,
mdiFormTextbox,
mdiGauge,
mdiGestureTapButton,
mdiGoogleAssistant,
mdiGoogleCirclesCommunities,
mdiHomeAssistant,
mdiHomeAutomation,
mdiImageFilterFrames,
mdiLightbulb,
mdiLightningBolt,
mdiMailbox,
mdiMapMarkerRadius,
mdiMeterGas,
mdiMicrophoneMessage,
mdiMolecule,
mdiMoleculeCo,
mdiMoleculeCo2,
mdiPalette,
mdiProgressClock,
mdiRayVertex,
mdiRemote,
mdiRobotVacuum,
mdiScriptText,
mdiSineWave,
mdiSpeakerMessage,
mdiSpeedometer,
mdiSunWireless,
mdiThermometer,
mdiThermometerLines,
mdiThermostat,
mdiTimerOutline,
mdiTransmissionTower,
mdiWater,
mdiWaterPercent,
mdiWeatherPouring,
mdiWeatherRainy,
mdiWeatherWindy,
mdiWeight,
mdiWifi,
} from "@mdi/js";

// Constants should be alphabetically sorted by name.
// 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 domain. */
export const DEFAULT_DOMAIN_ICON = mdiBookmark;

/** Icons for each domain */
export const FIXED_DOMAIN_ICONS = {
air_quality: mdiAirFilter,
alert: mdiAlert,
calendar: mdiCalendar,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiMicrophoneMessage,
counter: mdiCounter,
date: mdiCalendar,
demo: mdiHomeAssistant,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
homeassistant: mdiHomeAssistant,
homekit: mdiHomeAutomation,
image_processing: mdiImageFilterFrames,
input_button: mdiGestureTapButton,
input_datetime: mdiCalendarClock,
input_number: mdiRayVertex,
input_select: mdiFormatListBulleted,
input_text: mdiFormTextbox,
light: mdiLightbulb,
mailbox: mdiMailbox,
notify: mdiCommentAlert,
number: mdiRayVertex,
persistent_notification: mdiBell,
plant: mdiFlower,
proximity: mdiAppleSafari,
remote: mdiRemote,
scene: mdiPalette,
schedule: mdiCalendarClock,
script: mdiScriptText,
select: mdiFormatListBulleted,
sensor: mdiEye,
simple_alarm: mdiBell,
siren: mdiBullhorn,
stt: mdiMicrophoneMessage,
text: mdiFormTextbox,
timer: mdiTimerOutline,
tts: mdiSpeakerMessage,
updater: mdiCloudUpload,
vacuum: mdiRobotVacuum,
zone: mdiMapMarkerRadius,
};

export const FIXED_DEVICE_CLASS_ICONS = {
apparent_power: mdiFlash,
aqi: mdiAirFilter,
atmospheric_pressure: mdiThermometerLines,
// battery: mdiBattery, => not included by design since `sensorIcon()` will dynamically determine the icon
carbon_dioxide: mdiMoleculeCo2,
carbon_monoxide: mdiMoleculeCo,
current: mdiCurrentAc,
data_rate: mdiTransmissionTower,
data_size: mdiDatabase,
date: mdiCalendar,
distance: mdiArrowLeftRight,
duration: mdiProgressClock,
energy: mdiLightningBolt,
frequency: mdiSineWave,
gas: mdiMeterGas,
humidity: mdiWaterPercent,
illuminance: mdiBrightness5,
irradiance: mdiSunWireless,
moisture: mdiWaterPercent,
monetary: mdiCash,
nitrogen_dioxide: mdiMolecule,
nitrogen_monoxide: mdiMolecule,
nitrous_oxide: mdiMolecule,
ozone: mdiMolecule,
pm1: mdiMolecule,
pm10: mdiMolecule,
pm25: mdiMolecule,
power: mdiFlash,
power_factor: mdiAngleAcute,
precipitation: mdiWeatherRainy,
precipitation_intensity: mdiWeatherPouring,
pressure: mdiGauge,
reactive_power: mdiFlash,
signal_strength: mdiWifi,
sound_pressure: mdiEarHearing,
speed: mdiSpeedometer,
sulphur_dioxide: mdiMolecule,
temperature: mdiThermometer,
timestamp: mdiClock,
volatile_organic_compounds: mdiMolecule,
voltage: mdiSineWave,
volume: mdiCarCoolantLevel,
water: mdiWater,
weight: mdiWeight,
wind_speed: mdiWeatherWindy,
};

/** Domains that have a state card. */
export const DOMAINS_WITH_CARD = [
"button",
"climate",
"cover",
"configurator",
"input_button",
"input_select",
"input_number",
"input_text",
"lock",
"media_player",
"number",
"scene",
"script",
"select",
"timer",
"text",
"vacuum",
"water_heater",
];

export const SENSOR_ENTITIES = [
"sensor",
"binary_sensor",
"calendar",
"camera",
"device_tracker",
"weather",
];

/** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
* should not act as a click target to open the more info dialog (the row name and state icon
* still do of course) as the click should instead e.g. activate the input field or toggle
* the button that this row shows.
*/
export const DOMAINS_INPUT_ROW = [
"automation",
"button",
"cover",
"date",
"fan",
"group",
"humidifier",
"input_boolean",
"input_button",
"input_datetime",
"input_number",
"input_select",
"input_text",
"light",
"lock",
"media_player",
"number",
"scene",
"script",
"select",
"switch",
"text",
"time",
"vacuum",
];

/** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"];

/** Binary States */
export const BINARY_STATE_ON = "on";
export const BINARY_STATE_OFF = "off";

/** Domains where we allow toggle in Lovelace. */
export const DOMAINS_TOGGLE = new Set([
"fan",
"input_boolean",
"light",
"switch",
"group",
"automation",
"humidifier",
]);

/** Domains that have a dynamic entity image / picture. */
export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set(["camera", "media_player"]);

/** Temperature units. */
export const UNIT_C = "°C";
export const UNIT_F = "°F";

/** Entity ID of the default view. */
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
Loading

0 comments on commit 621d6df

Please sign in to comment.