Skip to content

Commit

Permalink
feat: add chips on home area cards
Browse files Browse the repository at this point in the history
  • Loading branch information
Lebe1ge committed Dec 12, 2024
1 parent 97fc2d2 commit f57f61a
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 227 deletions.
258 changes: 151 additions & 107 deletions custom_components/linus_dashboard/www/linus-strategy.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

46 changes: 12 additions & 34 deletions src/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import StrategyDevice = generic.StrategyDevice;
import MagicAreaRegistryEntry = generic.MagicAreaRegistryEntry;
import { FloorRegistryEntry } from "./types/homeassistant/data/floor_registry";
import { DEVICE_CLASSES, MAGIC_AREAS_DOMAIN, MAGIC_AREAS_NAME, UNDISCLOSED } from "./variables";
import { getMAEntity, getMagicAreaSlug, groupEntitiesByDomain, slugify } from "./utils";
import { getEntityDomain, getGlobalEntitiesExceptUndisclosed, getMAEntity, getMagicAreaSlug, groupEntitiesByDomain, slugify } from "./utils";
import { EntityRegistryEntry } from "./types/homeassistant/data/entity_registry";
import { FrontendEntityComponentIconResources, IconResources } from "./types/homeassistant/data/frontend";
import { LinusDashboardConfig } from "./types/homeassistant/data/linus_dashboard";
Expand All @@ -33,7 +33,7 @@ class Helper {
/**
* An array of entities from Home Assistant's entity registry.
*
* @type {Record<string, StrategyEntity[]}
* @type {Record<string, StrategyEntity[]>}
* @private
*/
static #domains: Record<string, StrategyEntity[]> = {};
Expand Down Expand Up @@ -344,7 +344,7 @@ class Helper {
}


let domain = this.getEntityDomain(entity.entity_id)
let domain = getEntityDomain(entity.entity_id)
if (Object.keys(DEVICE_CLASSES).includes(domain)) {
const entityState = Helper.getEntityState(entity.entity_id);
if (entityState?.attributes?.device_class) domain = entityState.attributes.device_class
Expand Down Expand Up @@ -462,7 +462,7 @@ class Helper {
}),
);

// console.log('this.#areas', this.#areas, this.#magicAreasDevices)
// console.log('this.#areas', info, this.#areas, this.#magicAreasDevices)

this.#initialized = true;
}
Expand Down Expand Up @@ -550,9 +550,8 @@ class Helper {
const area_slugs = Array.isArray(area_slug) ? area_slug : [area_slug];

for (const slug of area_slugs) {
const newStates = slug === "global"
? this.domains[device_class]?.map((entity) => `states['${entity.entity_id}']`) ?? []
: this.#areas[slug]?.domains[device_class]?.map((entity_id) => `states['${entity_id}']`) ?? [];
const entities = area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(device_class) : this.#areas[slug]?.domains[device_class]
const newStates = entities?.map((entity_id) => `states['${entity_id}']`);
states.push(...newStates);
}

Expand Down Expand Up @@ -746,14 +745,6 @@ class Helper {
return this.#hassStates[entity_id]
}

/**
* Get entity domain.
*
* @return {string}
*/
static getEntityDomain(entityId: string): string {
return entityId.split(".")[0];
}

/**
* Get translation.
Expand Down Expand Up @@ -785,12 +776,8 @@ class Helper {
for (const slug of areaSlugs) {
if (slug) {
const magic_entity = getMAEntity(slug!, domain);
const newStates = domain === "all"
? this.#areas[slug]?.entities.map((entity_id) => `states['${entity_id}']`)
: magic_entity
? [`states['${magic_entity.entity_id}']`] : area_slug === "global"
? this.domains[domain]?.map((entity) => `states['${entity.entity_id}']`)
: this.#areas[slug]?.domains[domain]?.map((entity_id) => `states['${entity_id}']`);
const entities = magic_entity ? [magic_entity] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(domain) : this.#areas[slug]?.domains[domain]
const newStates = entities?.map((entity_id) => `states['${entity_id}']`);
if (newStates) {
states.push(...newStates);
}
Expand Down Expand Up @@ -848,12 +835,8 @@ class Helper {

for (const slug of areaSlugs) {
const magic_entity = getMAEntity(slug!, "binary_sensor", device_class);

const newStates = magic_entity
? [`states['${magic_entity.entity_id}']`]
: area_slug === "global"
? this.domains[device_class]?.map((entity) => `states['${entity.entity_id}']`)
: this.#areas[slug]?.domains[device_class]?.map((entity_id) => `states['${entity_id}']`);
const entities = magic_entity ? [magic_entity] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(device_class) : this.#areas[slug]?.domains[device_class]
const newStates = entities?.map((entity_id) => `states['${entity_id}']`);

if (newStates) states.push(...newStates);
}
Expand All @@ -875,13 +858,8 @@ class Helper {

for (const slug of areaSlugs) {
const magic_entity = getMAEntity(slug!, "binary_sensor", device_class);

const newStates = magic_entity
? [`states['${magic_entity.entity_id}']`]
: area_slug === "global"
? this.domains[device_class]?.map((entity) => `states['${entity.entity_id}']`)
: this.#areas[slug]?.domains[device_class]?.map((entity_id) => `states['${entity_id}']`);

const entities = magic_entity ? [magic_entity] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(device_class) : this.#areas[slug]?.domains[device_class]
const newStates = entities?.map((entity_id) => `states['${entity_id}']`);
if (newStates) states.push(...newStates);
}

Expand Down
123 changes: 80 additions & 43 deletions src/cards/HomeAreaCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ClimateChip } from "../chips/ClimateChip";
import { LightChip } from "../chips/LightChip";
import { ConditionalChip } from "../chips/ConditionalChip";
import { UNAVAILABLE, UNDISCLOSED } from "../variables";
import { EntityCardConfig } from "../types/lovelace-mushroom/cards/entity-card-config";

// Utility function to generate badge icon and color
const getBadgeIcon = (entityId: string) => `
Expand Down Expand Up @@ -51,32 +52,53 @@ const getBadgeColor = (entityId: string) => `
{% endif %}
`;

class HomeAreaCard extends AbstractCard {
constructor(options: cards.HomeAreaCardOptions) {
class HomeAreaCard {
/**
* Magic area device to create the card for.
*
* @type {MagicAreaRegistryEntry}
*/
magicDevice: MagicAreaRegistryEntry

/**
* Default configuration of the view.
*
* @type {StrategyArea}
* @private
*/
area: StrategyArea

const magicAreasEntity: EntityRegistryEntry = getMAEntity(options.area_slug, "area_state") as EntityRegistryEntry;
/**
* Configuration of the card.
*
* @type {EntityCardConfig}
*/
config: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:help-circle",
};

const area = Helper.areas[options.area_slug];
constructor(options: cards.HomeAreaCardOptions) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}

super(magicAreasEntity);
const defaultConfig = options?.area_slug === UNDISCLOSED ? this.getUndisclosedAreaConfig(area) : this.getDefaultConfig(area);
this.config = { ...this.config, ...defaultConfig, ...options };
this.magicDevice = Helper.magicAreasDevices[options.area_slug];
this.area = Helper.areas[options.area_slug];

this.config = { ...this.config, ...options };
}

getDefaultConfig(area: StrategyArea): TemplateCardConfig {

const device = Helper.magicAreasDevices[area.slug];

const { area_state, all_lights, aggregate_temperature, aggregate_battery, aggregate_health, aggregate_window, aggregate_door, aggregate_cover, aggregate_climate, light_control } = device?.entities || {};
const { all_lights } = this.magicDevice?.entities || {};
const icon = area.icon || "mdi:home-outline";

const cards = [
this.getMainCard(area, icon, aggregate_temperature, aggregate_battery, area_state),
this.getMainCard(),
];

if (device) {
cards.push(this.getChipsCard(area, device, area_state, aggregate_health, aggregate_window, aggregate_door, aggregate_cover, aggregate_climate, all_lights, light_control))
}
cards.push(this.getChipsCard());

if (all_lights) {
cards.push(this.getLightCard(all_lights));
Expand All @@ -101,57 +123,56 @@ class HomeAreaCard extends AbstractCard {
};
}

getMainCard(area: StrategyArea, icon: string, aggregate_temperature: EntityRegistryEntry, aggregate_battery: EntityRegistryEntry, area_state: EntityRegistryEntry): any {
getMainCard(): any {

const { area_state, aggregate_temperature, aggregate_battery } = this.magicDevice?.entities || {};
const icon = this.area.icon || "mdi:home-outline";

return {
type: "custom:mushroom-template-card",
primary: getAreaName(area),
primary: getAreaName(this.area),
secondary: aggregate_temperature && this.getTemperatureTemplate(aggregate_temperature),
icon: icon,
icon_color: this.getIconColorTemplate(area_state),
fill_container: true,
layout: "horizontal",
badge_icon: getBadgeIcon(aggregate_battery?.entity_id),
badge_color: getBadgeColor(aggregate_battery?.entity_id),
tap_action: { action: "navigate", navigation_path: slugify(area.name) },
tap_action: { action: "navigate", navigation_path: this.area.slug },
card_mod: { style: this.getCardModStyle() }
};
}

getChipsCard(area: StrategyArea, device: MagicAreaRegistryEntry, area_state: EntityRegistryEntry, aggregate_health: EntityRegistryEntry, aggregate_window: EntityRegistryEntry, aggregate_door: EntityRegistryEntry, aggregate_cover: EntityRegistryEntry, aggregate_climate: EntityRegistryEntry, all_lights: EntityRegistryEntry, light_control: EntityRegistryEntry): any {
getChipsCard(): any {

const { light_control, aggregate_health, aggregate_window, aggregate_door, aggregate_cover, aggregate_climate } = this.magicDevice?.entities || {};
const { motion, occupancy, presence, window, climate, door, cover, health, light } = this.area.domains;

return {
type: "custom:mushroom-chips-card",
alignment: "end",
chips: [
new ConditionalChip(
[{ entity: area_state?.entity_id, state_not: UNAVAILABLE }],
new AreaStateChip(device).getChip()
).getChip(),
new ConditionalChip(
[{ entity: aggregate_health?.entity_id, state_not: "on" }],
(motion || occupancy || presence) && new AreaStateChip({ area: this.area }).getChip(),
health && new ConditionalChip(
aggregate_health ? [{ entity: aggregate_health?.entity_id, state: "on" }] : health.map(entity => ({ entity, state: "on" })),
new AggregateChip({ device_class: "health" }).getChip()
).getChip(),
new ConditionalChip(
[{ entity: aggregate_window?.entity_id, state: "on" }],
new AggregateChip({ magic_device_id: area.slug, area_slug: area.slug, device_class: "window", show_content: false }).getChip()
window?.length && new ConditionalChip(
aggregate_window ? [{ entity: aggregate_window?.entity_id, state: "on" }] : window.map(entity => ({ entity, state: "on" })),
new AggregateChip({ magic_device_id: this.area.slug, area_slug: this.area.slug, device_class: "window", show_content: false }).getChip()
).getChip(),
new ConditionalChip(
[{ entity: aggregate_door?.entity_id, state: "on" }],
new AggregateChip({ magic_device_id: area.slug, area_slug: area.slug, device_class: "door", show_content: false }).getChip()
door && new ConditionalChip(
aggregate_door ? [{ entity: aggregate_door?.entity_id, state: "on" }] : door.map(entity => ({ entity, state: "on" })),
new AggregateChip({ magic_device_id: this.area.slug, area_slug: this.area.slug, device_class: "door", show_content: false }).getChip()
).getChip(),
new ConditionalChip(
[{ entity: aggregate_cover?.entity_id, state: "on" }],
new AggregateChip({ magic_device_id: area.slug, area_slug: area.slug, device_class: "cover", show_content: false }).getChip()
cover && new ConditionalChip(
aggregate_cover ? [{ entity: aggregate_cover?.entity_id, state: "on" }] : cover.map(entity => ({ entity, state: "on" })),
new AggregateChip({ magic_device_id: this.area.slug, area_slug: this.area.slug, device_class: "cover", show_content: false }).getChip()
).getChip(),
climate && new ClimateChip({ magic_device_id: this.area.slug, area_slug: this.area.slug }).getChip(),
light && new LightChip({ area_slug: this.area.slug, magic_device_id: this.area.slug, tap_action: { action: "toggle" } }).getChip(),
new ConditionalChip(
[{ entity: aggregate_climate?.entity_id, state_not: UNAVAILABLE }],
new ClimateChip({ magic_device_id: area.slug, area_slug: area.slug }).getChip()
).getChip(),
new ConditionalChip(
[{ entity: all_lights?.entity_id, state_not: UNAVAILABLE, }],
new LightChip({ area_slug: area.slug, magic_device_id: area.slug, tap_action: { action: "toggle" } }).getChip()
).getChip(),
new ConditionalChip(
[{ entity: all_lights?.entity_id, state_not: UNAVAILABLE }],
[{ entity: this.magicDevice?.entities?.all_lights?.entity_id, state_not: UNAVAILABLE }],
new ControlChip("light", light_control?.entity_id).getChip()
).getChip()
].filter(Boolean),
Expand Down Expand Up @@ -180,8 +201,9 @@ class HomeAreaCard extends AbstractCard {
}

getIconColorTemplate(area_state: EntityRegistryEntry): string {
const condition = area_state?.entity_id ? `"dark" in state_attr('${area_state?.entity_id}', 'states')` : `not is_state("sun.sun", "above_horizon")`;
return `
{{ "indigo" if "dark" in state_attr('${area_state?.entity_id}', 'states') else "amber" }}
{{ "indigo" if ${condition} else "amber" }}
`;
}

Expand Down Expand Up @@ -232,6 +254,21 @@ class HomeAreaCard extends AbstractCard {
}
`;
}

/**
* Get a card.
*
* @return {cards.AbstractCardConfig} A card object.
*/
getCard(): cards.AbstractCardConfig {
const defaultConfig = this.area.slug === UNDISCLOSED ? this.getUndisclosedAreaConfig(this.area) : this.getDefaultConfig(this.area);
const magicAreasEntity: EntityRegistryEntry = getMAEntity(this.magicDevice?.id, "area_state") as EntityRegistryEntry;

return {
...defaultConfig,
entity: magicAreasEntity ? magicAreasEntity.entity_id : undefined,
};
}
}

export { HomeAreaCard };
Loading

0 comments on commit f57f61a

Please sign in to comment.