Add options for all areas (#52)

Area identifier `_` sets the options for all areas.
Options for a specific area can still be overridden like before.

Also the HA area-card is added to use as a strategy area-card.

---------

Co-authored-by: DigiLive <info@digilive.nl>
This commit is contained in:
Johan Frick
2023-10-13 08:12:11 +02:00
committed by GitHub
parent 8311fa240c
commit 98ef9fed05
8 changed files with 152 additions and 42 deletions

View File

@@ -75,7 +75,7 @@ All the rounded cards can be configured using the Dashboard UI editor.
```yaml ```yaml
strategy: strategy:
type: custom:mushroom-strategy type: custom:mushroom-strategy
views: [ ] views: []
``` ```
### Hidding specific entities ### Hidding specific entities
@@ -133,7 +133,7 @@ strategy:
name: Family Room name: Family Room
icon: mdi:sofa icon: mdi:sofa
icon_color: green icon_color: green
views: [ ] views: []
``` ```
### Area Object ### Area Object
@@ -161,6 +161,7 @@ at the top of the area subview.
| `hidden` | boolean | false | Set to `true` to exclude the area from the dashboard and views. | | `hidden` | boolean | false | Set to `true` to exclude the area from the dashboard and views. |
| `order` | number | Infinity | Ordering position of the area in the list of available areas. | | `order` | number | Infinity | Ordering position of the area in the list of available areas. |
| `extra_cards` | array of cards | unset or empty | A list of cards to show on the top of the area subview. | | `extra_cards` | array of cards | unset or empty | A list of cards to show on the top of the area subview. |
| `type` | string | `default` | Set to a type of area card. (Currently supported: `default` & `HaAreaCard` |
*) `more-info` `toggle` `call-service` `navigate` `url` `none` *) `more-info` `toggle` `call-service` `navigate` `url` `none`
@@ -191,7 +192,9 @@ strategy:
order: 2 order: 2
garage_id: garage_id:
hidden: true hidden: true
views: [ ] hallway_id:
type: HaAreaCard
views: []
``` ```
#### Undisclosed Area #### Undisclosed Area
@@ -202,6 +205,22 @@ This area is enabled by default and includes the entities that aren't linked to
The area can be configured like any other area as described above. The area can be configured like any other area as described above.
To exclude this area from the dashboard and views, set its property `hidden` to `true`. To exclude this area from the dashboard and views, set its property `hidden` to `true`.
#### Setting options for all areas
Use `_` as an identifier to set the options for all areas.
The following example sets the type of all area-cards to Home Assistant's area card:
```yaml
strategy:
type: custom:mushroom-strategy
options:
areas:
_:
type: HaAreaCard
views: []
```
### Card Options ### Card Options
The `card_options` entry enables you to specify a card type for an entity or to hide the card from the dashboard. The `card_options` entry enables you to specify a card type for an entity or to hide the card from the dashboard.
@@ -222,7 +241,7 @@ strategy:
077ba0492c9bb3b31ffac34f1f3a626a: 077ba0492c9bb3b31ffac34f1f3a626a:
hidden: true hidden: true
views: [ ] views: []
``` ```
### Pre-built views ### Pre-built views
@@ -230,7 +249,7 @@ views: [ ]
![Light Views](./docs/light_view.png) ![Light Views](./docs/light_view.png)
Mushroom strategy includes pre-built views to control/view specific domains. Mushroom strategy includes pre-built views to control/view specific domains.
All devices that are in an area where `hidden` is set to false/undefined are shown. All devices that are in an area where `hidden` is set to false/undefined are shown.
By default, all pre-built views below are shown: By default, all pre-built views below are shown:
@@ -270,7 +289,7 @@ strategy:
order: 1 order: 1
hidden: true hidden: true
icon: mdi:toggle-switch icon: mdi:toggle-switch
views: [ ] views: []
``` ```
### Supported domains ### Supported domains
@@ -312,7 +331,7 @@ strategy:
showControls: false showControls: false
default: default:
hidden: true hidden: true
views: [ ] views: []
``` ```
### Chips ### Chips
@@ -475,7 +494,7 @@ strategy:
title: cool view title: cool view
path: cool-view path: cool-view
icon: mdi:emoticon-cool icon: mdi:emoticon-cool
badges: [ ] badges: []
cards: cards:
- type: markdown - type: markdown
content: I am cool content: I am cool

File diff suppressed because one or more lines are too long

View File

@@ -38,9 +38,15 @@ class AreaCard extends AbstractCard {
*/ */
constructor(area, options = {}) { constructor(area, options = {}) {
super(area); super(area);
this.#defaultOptions.primary = area.name; this.#defaultOptions.primary = area.name;
this.#defaultOptions.tap_action.navigation_path = area.area_id ?? area.name; this.#defaultOptions.tap_action.navigation_path = area.area_id ?? area.name;
// Set card type to default if a type "default" is given in strategy options.
if (options.type === "default") {
options.type = this.#defaultOptions.type;
}
this.mergeOptions( this.mergeOptions(
this.#defaultOptions, this.#defaultOptions,
options, options,

47
src/cards/HaAreaCard.js Normal file
View File

@@ -0,0 +1,47 @@
import {AbstractCard} from "./AbstractCard";
/**
* HA Area Card Class
*
* Used to create a card for an entity of the area domain using the built in type 'area'.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {HaAreaCardOptions}
* @private
*/
#defaultOptions = {
type: "area",
area: undefined,
navigation_path: undefined,
};
/**
* Class constructor.
*
* @param {areaEntity} area The area entity to create a card for.
* @param {HaAreaCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area, options = {}) {
super(area);
this.#defaultOptions.area = area.area_id ?? area.name;
this.#defaultOptions.navigation_path = area.area_id ?? area.name;
// Enforce the card type.
options.type = this.#defaultOptions.type;
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {AreaCard};

View File

@@ -112,6 +112,13 @@
* @memberOf typedefs.cards * @memberOf typedefs.cards
*/ */
/**
* @typedef {abstractOptions & Object} HaAreaCardOptions HA Area Card options.
* @property {string} area The id of the area.
* @property {string} navigation_path The id of the area to navigate to.
* @memberOf typedefs.cards
*/
/** /**
* @typedef {abstractOptions & Object} mediaPlayerCardOptions Media Player Card options. * @typedef {abstractOptions & Object} mediaPlayerCardOptions Media Player Card options.
* @property {boolean} [use_media_info=true] Use media info instead of name, state, and icon when a media is playing * @property {boolean} [use_media_info=true] Use media info instead of name, state, and icon when a media is playing

View File

@@ -191,12 +191,11 @@ class MushroomStrategy {
} }
if (!Helper.strategyOptions.domains.default.hidden) { if (!Helper.strategyOptions.domains.default.hidden) {
// TODO: Check if default is hidden
// Create cards for any other domain. // Create cards for any other domain.
// Collect device entities of the current area. // Collect device entities of the current area.
const areaDevices = Helper.devices.filter(device => device.area_id === area.area_id) const areaDevices = Helper.devices.filter(device => device.area_id === area.area_id)
.map(device => device.id); .map(device => device.id);
// Collect the remaining entities of which all conditions below are met: // Collect the remaining entities of which all conditions below are met:
// 1. The entity is linked to a device which is linked to the current area, // 1. The entity is linked to a device which is linked to the current area,
// or the entity itself is linked to the current area. // or the entity itself is linked to the current area.
@@ -207,33 +206,33 @@ class MushroomStrategy {
&& entity.disabled_by == null && entity.disabled_by == null
&& !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]); && !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]);
}); });
// Create a column of miscellaneous entity cards. // Create a column of miscellaneous entity cards.
if (miscellaneousEntities.length) { if (miscellaneousEntities.length) {
let miscellaneousCards = []; let miscellaneousCards = [];
try { try {
miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => { miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => {
/** @type Object[] */ /** @type Object[] */
const miscellaneousCards = [ const miscellaneousCards = [
new TitleCard([area], Helper.strategyOptions.domains.default).createCard(), new TitleCard([area], Helper.strategyOptions.domains.default).createCard(),
]; ];
for (const entity of miscellaneousEntities) { for (const entity of miscellaneousEntities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {}; let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {}; let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
if (!cardOptions.hidden && !deviceOptions.hidden) { if (!cardOptions.hidden && !deviceOptions.hidden) {
miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard()); miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard());
} }
} }
return miscellaneousCards; return miscellaneousCards;
}); });
} catch (e) { } catch (e) {
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!"); console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
} }
viewCards.push({ viewCards.push({
type: "vertical-stack", type: "vertical-stack",
cards: miscellaneousCards, cards: miscellaneousCards,

View File

@@ -35,6 +35,7 @@
* @property {Object[]} [extra_cards] An array of card configurations. * @property {Object[]} [extra_cards] An array of card configurations.
* The configured cards are added to the dashboard. * The configured cards are added to the dashboard.
* This property is added by the custom strategy. * This property is added by the custom strategy.
* @property {boolean} [use_ha_area_card] Set to true to use ha area card instead of mushroom.
* @memberOf typedefs.generic * @memberOf typedefs.generic
*/ */

View File

@@ -82,9 +82,9 @@ class HomeView extends AbstractView {
// Add area cards. // Add area cards.
homeViewCards.push({ homeViewCards.push({
type: "vertical-stack", type: "vertical-stack",
cards: areaCards, cards: areaCards,
}); });
// Add custom cards. // Add custom cards.
if (options.extra_cards) { if (options.extra_cards) {
@@ -158,10 +158,10 @@ class HomeView extends AbstractView {
import("../cards/PersonCard").then(personModule => { import("../cards/PersonCard").then(personModule => {
for (const person of Helper.entities.filter(entity => { for (const person of Helper.entities.filter(entity => {
return entity.entity_id.startsWith("person.") return entity.entity_id.startsWith("person.")
&& entity.hidden_by == null && entity.hidden_by == null
&& entity.disabled_by == null && entity.disabled_by == null;
})) { })) {
cards.push(new personModule.PersonCard(person).getCard()); cards.push(new personModule.PersonCard(person).getCard());
} }
}); });
@@ -176,30 +176,61 @@ class HomeView extends AbstractView {
* *
* @return {Object[]} A card object array. * @return {Object[]} A card object array.
*/ */
#createAreaCards() { async #createAreaCards() {
const groupedCards = [{ /**
type: "custom:mushroom-title-card", * Cards to be stacked vertically.
title: "Areas", *
}]; * Contains a Title card and horizontal stacks of Area cards.
*
* @type {[{}]}
*/
const groupedCards = [
{
type: "custom:mushroom-title-card",
title: "Areas",
},
];
let areaCards = [];
import("../cards/AreaCard").then(areaModule => { for (const [i, area] of Helper.areas.entries()) {
const areaCards = []; let module;
let moduleName =
Helper.strategyOptions.areas[area.area_id ?? "undisclosed"]?.type ??
Helper.strategyOptions.areas["_"]?.type ??
"default";
for (const area of Helper.areas) { // Load module by type in strategy options.
if (!Helper.strategyOptions.areas[area.area_id]?.hidden) { try {
areaCards.push( module = await import((`../cards/${moduleName}`));
new areaModule.AreaCard(area, Helper.strategyOptions.areas[area.area_id ?? "undisclosed"]).getCard()); } catch (e) {
// Fallback to the default strategy card.
module = await import("../cards/AreaCard");
if (Helper.strategyOptions.debug && moduleName !== "default") {
console.error(e);
} }
} }
// Horizontally group every two area cards. // Get a card for the area.
for (let i = 0; i < areaCards.length; i += 2) { if (!Helper.strategyOptions.areas[area.area_id]?.hidden) {
groupedCards.push({ let options = {
type: "horizontal-stack", ...Helper.strategyOptions.areas["_"],
cards: areaCards.slice(i, i + 2), ...Helper.strategyOptions.areas[area.area_id ?? "undisclosed"],
}); };
areaCards.push(new module.AreaCard(area, options).getCard());
} }
});
// Horizontally group every two area cards if all cards are created.
if (i === Helper.areas.length - 1) {
for (let i = 0; i < areaCards.length; i += 2) {
groupedCards.push({
type: "horizontal-stack",
cards: areaCards.slice(i, i + 2),
});
}
}
}
return groupedCards; return groupedCards;
} }