Skip to content

Commit

Permalink
feat: display light group, fix aggregate tap_action
Browse files Browse the repository at this point in the history
  • Loading branch information
Lebe1ge committed Dec 15, 2024
1 parent 725adf5 commit 5aca06f
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 337 deletions.
290 changes: 117 additions & 173 deletions custom_components/linus_dashboard/www/linus-strategy.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

44 changes: 15 additions & 29 deletions src/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,19 +504,15 @@ class Helper {
const newStates = domain === "all"
? this.#areas[slug]?.entities.map((entity_id) => `states['${entity_id}']`)
: this.#areas[slug]?.domains[domain]?.map((entity_id) => `states['${entity_id}']`);
if (newStates) {
states.push(...newStates);
}
if (newStates) states.push(...newStates);
} else {
for (const area of Object.values(this.#areas)) {
if (area.area_id === UNDISCLOSED) continue;

const newStates = domain === "all"
? area.entities.map((entity_id) => `states['${entity_id}']`)
: area.domains[domain]?.map((entity_id) => `states['${entity_id}']`);
if (newStates) {
states.push(...newStates);
}
if (newStates) states.push(...newStates);
}
}
}
Expand Down Expand Up @@ -552,7 +548,7 @@ class Helper {
for (const slug of area_slugs) {
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);
if (newStates) states.push(...newStates);
}

const formattedValue = Array.isArray(value) ? JSON.stringify(value).replace(/"/g, "'") : `'${value}'`;
Expand Down Expand Up @@ -590,12 +586,9 @@ class Helper {

for (const slug of areaSlugs) {
const magic_entity = getMAEntity(slug!, "sensor", device_class);
const newStates = magic_entity
? [`states['${magic_entity.entity_id}']`]
: slug
? this.#areas[slug]?.domains[device_class]?.map((entity_id) => `states['${entity_id}']`) || []
: [];
states.push(...newStates);
const entities = magic_entity ? [magic_entity.entity_id] : 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);
}

return `{% set entities = [${states}] %}{{ (entities | selectattr('attributes.device_class', 'defined') | selectattr('attributes.device_class', 'eq', '${device_class}') | map(attribute='state') | map('float') | sum / entities | length) | round(1) }} {{ ${states[0]}.attributes.unit_of_measurement }}`;
Expand Down Expand Up @@ -776,11 +769,9 @@ class Helper {
for (const slug of areaSlugs) {
if (slug) {
const magic_entity = getMAEntity(slug!, domain);
const entities = magic_entity ? [magic_entity] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(domain) : this.#areas[slug]?.domains[domain]
const entities = magic_entity ? [magic_entity.entity_id] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(domain) : this.#areas[slug]?.domains[domain]
const newStates = entities?.map((entity_id) => `states['${entity_id}']`);
if (newStates) {
states.push(...newStates);
}
if (newStates) states.push(...newStates);
} else {
// Get the ID of the devices which are linked to the given area.
for (const area of Object.values(this.#areas)) {
Expand All @@ -789,9 +780,7 @@ class Helper {
const newStates = domain === "all"
? this.#areas[area.slug]?.entities.map((entity_id) => `states['${entity_id}']`)
: this.#areas[area.slug]?.domains[domain]?.map((entity_id) => `states['${entity_id}']`);
if (newStates) {
states.push(...newStates);
}
if (newStates) states.push(...newStates);
}
}
}
Expand Down Expand Up @@ -835,7 +824,7 @@ class Helper {

for (const slug of areaSlugs) {
const magic_entity = getMAEntity(slug!, "binary_sensor", device_class);
const entities = magic_entity ? [magic_entity] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(device_class) : this.#areas[slug]?.domains[device_class]
const entities = magic_entity ? [magic_entity.entity_id] : 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 @@ -857,8 +846,8 @@ class Helper {


for (const slug of areaSlugs) {
const magic_entity = getMAEntity(slug!, "binary_sensor", device_class);
const entities = magic_entity ? [magic_entity] : area_slug === "global" ? getGlobalEntitiesExceptUndisclosed(device_class) : this.#areas[slug]?.domains[device_class]
const magic_entity = getMAEntity(slug!, "sensor", device_class);
const entities = magic_entity ? [magic_entity.entity_id] : 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 Expand Up @@ -923,12 +912,9 @@ class Helper {

for (const slug of areaSlugs) {
const magic_entity = getMAEntity(slug!, "sensor", device_class);
const newStates = magic_entity
? [`states['${magic_entity.entity_id}']`]
: slug
? this.#areas[slug]?.domains[device_class]?.map((entity_id) => `states['${entity_id}']`) || []
: [];
states.push(...newStates);
const entities = magic_entity ? [magic_entity.entity_id] : 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);
}

if (device_class === "battery") {
Expand Down
17 changes: 5 additions & 12 deletions src/cards/ControllerCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { cards } from "../types/strategy/cards";
import { LovelaceBadgeConfig, LovelaceCardConfig } from "../types/homeassistant/data/lovelace";
import { Helper } from "../Helper";
import { getMAEntity, navigateTo } from "../utils";
import { HassServiceTarget } from "home-assistant-js-websocket";

/**
* Controller Card class.
Expand All @@ -12,11 +11,6 @@ import { HassServiceTarget } from "home-assistant-js-websocket";
* @class
*/
class ControllerCard {
/**
* @type {ExtendedHassServiceTarget} The target to control the entities of.
* @private
*/
readonly #target: HassServiceTarget;

/**
* @type {string} The target to control the entities of.
Expand Down Expand Up @@ -48,11 +42,9 @@ class ControllerCard {
/**
* Class constructor.
*
* @param {HassServiceTarget} target The target to control the entities of.
* @param {cards.ControllerCardOptions} options Controller Card options.
*/
constructor(target: HassServiceTarget, options: cards.ControllerCardOptions = {}, domain: string, magic_device_id: string = "global") {
this.#target = target;
constructor(options: cards.ControllerCardOptions = {}, domain: string, magic_device_id: string = "global") {
this.#domain = domain;
this.#magic_device_id = magic_device_id;
this.#defaultConfig = {
Expand Down Expand Up @@ -98,7 +90,7 @@ class ControllerCard {
grid_rows: 1
},
...(this.#defaultConfig.subtitleNavigate && {
tap_action: navigateTo(this.#defaultConfig.subtitleNavigate),
tap_action: this.#defaultConfig.tap_action ?? navigateTo(this.#defaultConfig.subtitleNavigate),
})
})
}
Expand All @@ -122,10 +114,11 @@ class ControllerCard {
badges.push({
type: "custom:mushroom-chips-card",
chips: [chip],
card_mod: {
alignment: "end",
card_mod: this.#domain === "sensor" && {
style: `
ha-card {
min-width: 80px;
min-width: 100px;
}
`,
}
Expand Down
3 changes: 1 addition & 2 deletions src/chips/AggregateChip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { chips } from "../types/strategy/chips";
import { DEVICE_CLASSES } from "../variables";
import { AbstractChip } from "./AbstractChip";
import { Helper } from "../Helper";
import { navigateTo } from "../utils";

// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
Expand Down Expand Up @@ -62,7 +61,7 @@ class AggregateChip extends AbstractChip {
icon_color,
icon,
content: content,
tap_action: magicEntity?.entity_id ? tap_action : navigateTo(device_class),
tap_action: tap_action ?? { action: "none" }
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/configurationDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ export const configurationDefaults: StrategyDefaults = {
order: 11,
hidden: false,
},
sensor: {
hidden: false,
},
binary_sensor: {
hidden: false,
},
securityDetails: {
hidden: false,
},
Expand Down
11 changes: 9 additions & 2 deletions src/linus-strategy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Helper } from "./Helper";
import { generic } from "./types/strategy/generic";
import { LovelaceConfig, LovelaceViewConfig } from "./types/homeassistant/data/lovelace";
import { CUSTOM_VIEWS, DEVICE_CLASSES, DOMAINS_VIEWS, VIEWS_ICONS } from "./variables";
import { AGGREGATE_DOMAINS, CUSTOM_VIEWS, DEVICE_CLASSES, DOMAINS_VIEWS, VIEWS_ICONS } from "./variables";
import { AreaView } from "./views/AreaView";
import { getAreaName, getDomainTranslationKey, getFloorName } from "./utils";
import { FloorView } from "./views/FloorView";
Expand Down Expand Up @@ -180,10 +180,17 @@ class LinusStrategy extends HTMLTemplateElement {
const viewModule = await import("./views/UnavailableView");
view = await new viewModule.UnavailableView().getView();

} else if (AGGREGATE_DOMAINS.includes(viewId)) {

const viewModule = await import("./views/AggregateView");
view = await new viewModule.AggregateView({ domain: viewId }).getView();

} else if ([...DEVICE_CLASSES.binary_sensor, ...DEVICE_CLASSES.sensor].includes(viewId)) {

const domain = DEVICE_CLASSES.binary_sensor.includes(viewId) ? "binary_sensor" : "sensor";

const viewModule = await import("./views/AggregateView");
view = await new viewModule.AggregateView({ device_class: viewId }).getView();
view = await new viewModule.AggregateView({ domain, device_class: viewId }).getView();

} else {

Expand Down
2 changes: 1 addition & 1 deletion src/types/strategy/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export namespace views {
controllerCardOptions?: cards.ControllerCardOptions;
}

export type AggregateViewOptions = { device_class: string } & ViewConfig;
export type AggregateViewOptions = { domain: string, device_class?: string } & ViewConfig;
export type InputViewOptions = { domain: string } & ViewConfig;
}

Expand Down
30 changes: 26 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import MagicAreaRegistryEntry = generic.MagicAreaRegistryEntry;
import StrategyFloor = generic.StrategyFloor;
import StrategyArea = generic.StrategyArea;
import { ActionConfig } from "./types/homeassistant/data/lovelace";
import { DEVICE_CLASSES, AGGREGATE_DOMAINS, GROUP_DOMAINS, LIGHT_DOMAIN, UNDISCLOSED } from "./variables";
import { DEVICE_CLASSES, AGGREGATE_DOMAINS, GROUP_DOMAINS, LIGHT_DOMAIN, UNDISCLOSED, LIGHT_GROUPS } from "./variables";
import { LovelaceChipConfig } from "./types/lovelace-mushroom/utils/lovelace/chip/types";
import { UnavailableChip } from "./chips/UnavailableChip";
import { chips } from "./types/strategy/chips";

/**
Expand Down Expand Up @@ -91,7 +90,7 @@ export function getMAEntity(magic_device_id: string, domain: string, device_clas
if (domain === LIGHT_DOMAIN) return magicAreaDevice?.entities?.[''] ?? magicAreaDevice?.entities?.['all_lights']
if (GROUP_DOMAINS.includes(domain)) return magicAreaDevice?.entities?.[`${domain}_group` as 'cover_group']
if (device_class && [...DEVICE_CLASSES.binary_sensor, ...DEVICE_CLASSES.sensor].includes(device_class)) return magicAreaDevice?.entities?.[`aggregate_${device_class}` as 'aggregate_motion']
return undefined
return magicAreaDevice?.entities?.[domain] ?? undefined
}


Expand Down Expand Up @@ -139,7 +138,7 @@ export async function createChipsFromList(chipsList: string[], chipOptions?: Par
let chipModule;
if ([...DEVICE_CLASSES.binary_sensor, ...DEVICE_CLASSES.sensor].includes(chipType)) {
chipModule = await import("./chips/AggregateChip");
const chip = new chipModule.AggregateChip({ ...chipOptions, device_class: chipType, area_slug, magic_device_id });
const chip = new chipModule.AggregateChip({ ...chipOptions, device_class: chipType, area_slug, magic_device_id, tap_action: navigateTo(chipType) });
chips.push(chip.getChip());
} else {
chipModule = await import("./chips/" + className);
Expand Down Expand Up @@ -183,3 +182,26 @@ export function getGlobalEntitiesExceptUndisclosed(device_class: string): string
!Helper.areas[UNDISCLOSED].domains[device_class]?.includes(entity.entity_id)
).map(e => e.entity_id) ?? [];
}


export function addLightGroupsToEntities(area: generic.StrategyArea, entities: generic.StrategyEntity[]) {
const lightGroups = LIGHT_GROUPS
.map(type => getMAEntity(area.slug, type))
.filter(Boolean);

for (const lightGroup of lightGroups) {
if (!lightGroup) continue;
const lightGroupState = Helper.getEntityState(lightGroup.entity_id);
if (lightGroupState.attributes.entity_id?.length) {
entities.unshift(lightGroup as generic.StrategyEntity);
lightGroupState.attributes.entity_id.forEach((entity_id: string) => {
const index = entities.findIndex(entity => entity.entity_id === entity_id);
if (index !== -1) {
entities.splice(index, 1);
}
});
}
}

return entities;
}
1 change: 1 addition & 0 deletions src/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const UNDISCLOSED = "undisclosed";
export const TOD_ORDER = ["morning", "daytime", "evening", "night"];

export const LIGHT_DOMAIN = "light";
export const LIGHT_GROUPS = ["overhead_lights", "accent_lights", "task_lights", "sleep_lights"];
export const GROUP_DOMAINS = ["climate", "media_player", "cover"];
export const AGGREGATE_DOMAINS = ["binary_sensor", "sensor"];

Expand Down
41 changes: 23 additions & 18 deletions src/views/AbstractView.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AREA_CARDS_DOMAINS, UNDISCLOSED } from './../variables';
import { AGGREGATE_DOMAINS, AREA_CARDS_DOMAINS, UNDISCLOSED } from './../variables';
import { Helper } from "../Helper";
import { ControllerCard } from "../cards/ControllerCard";
import { LovelaceGridCardConfig, StackCardConfig } from "../types/homeassistant/lovelace/cards/types";
Expand All @@ -7,7 +7,7 @@ import { HassServiceTarget } from "home-assistant-js-websocket";
import { TemplateCardConfig } from '../types/lovelace-mushroom/cards/template-card-config';
import { ChipsCardConfig } from '../types/lovelace-mushroom/cards/chips-card';
import { SwipeCard } from '../cards/SwipeCard';
import { getAreaName, getFloorName, slugify } from '../utils';
import { addLightGroupsToEntities, getAreaName, getFloorName, slugify } from '../utils';
import { views } from '../types/strategy/views';

/**
Expand Down Expand Up @@ -111,16 +111,13 @@ abstract class AbstractView {
const floorCards = [];

for (const area of floor.areas_slug.map(area_slug => Helper.areas[area_slug])) {
const entities = Helper.getAreaEntities(area, this.#device_class ?? this.#domain);
let entities = Helper.getAreaEntities(area, this.#device_class ?? this.#domain);
const className = Helper.sanitizeClassName(this.#domain + "Card");
const cardModule = await import(`../cards/${className}`);

if (entities.length === 0 || !cardModule) continue;

let target: HassServiceTarget = { area_id: [area.slug] };
if (area.area_id === UNDISCLOSED) {
target = { entity_id: entities.map(entity => entity.entity_id) };
}
if (this.#domain === "light") entities = addLightGroupsToEntities(area, entities);

const entityCards = entities
.filter(entity => !Helper.strategyOptions.card_options?.[entity.entity_id]?.hidden
Expand All @@ -137,11 +134,16 @@ abstract class AbstractView {
subtitleNavigate: area.slug
} as any;
if (this.#domain) {
titleCardOptions.showControls = Helper.strategyOptions.domains[this.#domain].showControls;
titleCardOptions.extraControls = Helper.strategyOptions.domains[this.#domain].extraControls;
titleCardOptions.controlChipOptions = { device_class: this.#device_class, area_slug: area.slug }
if (area.slug !== UNDISCLOSED && (!AGGREGATE_DOMAINS.includes(this.#domain) || this.#device_class)) {
titleCardOptions.showControls = Helper.strategyOptions.domains[this.#domain].showControls;
titleCardOptions.extraControls = Helper.strategyOptions.domains[this.#domain].extraControls;
titleCardOptions.controlChipOptions = { device_class: this.#device_class, area_slug: area.slug }
} else {
titleCardOptions.showControls = false;
}
}
const areaControllerCard = new ControllerCard(target, titleCardOptions, this.#domain, area.slug).createCard();

const areaControllerCard = new ControllerCard(titleCardOptions, this.#domain, area.slug).createCard();

floorCards.push(...areaControllerCard, ...areaCards);
}
Expand All @@ -155,17 +157,19 @@ abstract class AbstractView {
titleNavigate: slugify(floor.name)
};
if (this.#domain) {
titleSectionOptions.showControls = Helper.strategyOptions.domains[this.#domain].showControls;
titleSectionOptions.extraControls = Helper.strategyOptions.domains[this.#domain].extraControls;
titleSectionOptions.controlChipOptions = {
device_class: this.#device_class,
area_slug: floor.areas_slug
if (!AGGREGATE_DOMAINS.includes(this.#domain) || this.#device_class) {
titleSectionOptions.showControls = Helper.strategyOptions.domains[this.#domain].showControls;
titleSectionOptions.extraControls = Helper.strategyOptions.domains[this.#domain].extraControls;
titleSectionOptions.controlChipOptions = {
device_class: this.#device_class,
area_slug: floor.areas_slug
}
} else {
titleSectionOptions.showControls = false;
}
}

const area_ids = floor.areas_slug.map(area_slug => Helper.areas[area_slug].area_id);
const floorControllerCard = floors.length > 1 ? new ControllerCard(
{ area_id: area_ids },
titleSectionOptions,
this.#domain,
floor.floor_id
Expand Down Expand Up @@ -221,3 +225,4 @@ abstract class AbstractView {
}

export { AbstractView };

Loading

0 comments on commit 5aca06f

Please sign in to comment.