forked from DigiLive/mushroom-strategy
Refactor the code base from Procedural to Object-Oriented Programming. The new code base requires webpack (https://webpack.js.org/) to bundle the code into a single javascript file. Webpack can be installed with npm, using file `package-lock.json` or `package.json`. File `package.json` contains two script entries: * `build` for a *release* build (uses file`webpack.config.js`). * `build-dev` for a *debug* build (uses file `webpack.dev.config.js`). Both of the scripts will bundle the separate package files into a single javascript file in the `dist` directory. Includes fixes and changes: * The release badge in readme now displays the latest release version. * Optimize function `getStateEntities()`. * Implement #11. * Merge Home Assistant and User-defined areas. * Add contributors to readme. * Fix getCountTemplate method. The method only counted states which aren't equal to value `off`. States are now counted by specifying a comparison operator and value. * Document default HACS installation. * Fix overriding sensor card configuration. Having an `entity_config` property in the strategy options resulted in overriding each sensor card. Only the cards listed under this property should be overridden. * Cut redundant properties and constants Method `generateDashboard()` passes redundant values to generateView(). Writing those values into properties and reading them into constants is removed. * Fix undefined sensorState. Not all entities (like a RESTful sensor) are bound to a device. SensorStates did contain states which meet all following conditions: 1. The linked entity is linked to the given area or isn't linked to any area. 2. The linked device is linked to the given area. SensorStates now contains states which meet any* of the following conditions: 1. The linked entity is linked to the given area or isn't linked to any area. 2. The entity is linked to a device, and the linked device is linked to the given area. *) Whichever comes first. * Add undisclosed area. * Add ordering of area-cards. Area cards are now ordered by a custom set position first and then by the area name. * Add global sorting of entities by original_name. * Add global sorting of areas by order and name. * Add global sorting of domains by order and title. * Make production build. Known Issue: Title cards or chips won't switch entities assigned to the undisclosed area.
247 lines
7.7 KiB
JavaScript
247 lines
7.7 KiB
JavaScript
import {Helper} from "./Helper";
|
|
import {SensorCard} from "./cards/SensorCard";
|
|
import {TitleCard} from "./cards/TitleCard";
|
|
|
|
/**
|
|
* Mushroom Dashboard Strategy.<br>
|
|
* <br>
|
|
* Mushroom dashboard strategy provides a strategy for Home-Assistant to create a dashboard automatically.<br>
|
|
* The strategy makes use Mushroom, Mini Graph and WebRTC cards to represent your entities.<br>
|
|
* <br>
|
|
* Features:<br>
|
|
* 🛠 Automatically create dashboard with 3 lines of yaml.<br>
|
|
* 😍 Built-in Views for several standard domains.<br>
|
|
* 🎨 Many options to customize to your needs.<br>
|
|
* <br>
|
|
* Check the [Repository]{@link https://github.com/AalianKhan/mushroom-strategy} for more information.
|
|
*/
|
|
class MushroomStrategy {
|
|
/**
|
|
* Generate a dashboard.
|
|
*
|
|
* Called when opening a dashboard.
|
|
*
|
|
* @param {dashBoardInfo} info Dashboard strategy information object.
|
|
* @return {Promise<{views: Object[]}>}
|
|
*/
|
|
static async generateDashboard(info) {
|
|
await Helper.initialize(info);
|
|
|
|
// Create views.
|
|
const views = [];
|
|
|
|
let viewModule;
|
|
|
|
// Create a view for each exposed domain.
|
|
for (let viewId of Helper.getExposedViewIds()) {
|
|
try {
|
|
const viewType = Helper.sanitizeClassName(viewId + "View");
|
|
viewModule = await import(`./views/${viewType}`);
|
|
const view = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView();
|
|
|
|
views.push(view);
|
|
|
|
} catch (e) {
|
|
console.error(Helper.debug ? e : `View '${viewId}' couldn't be loaded!`);
|
|
}
|
|
}
|
|
|
|
// Create subviews for each area.
|
|
for (let area of Helper.areas) {
|
|
if (!area.hidden) {
|
|
views.push({
|
|
title: area.name,
|
|
path: area.area_id ?? area.name,
|
|
subview: true,
|
|
strategy: {
|
|
type: "custom:mushroom-strategy",
|
|
options: {
|
|
area,
|
|
"entity_config": Helper.strategyOptions.entity_config,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add custom views.
|
|
if (Helper.strategyOptions.extra_views) {
|
|
views.push(...Helper.strategyOptions.extra_views);
|
|
}
|
|
|
|
// Return the created views.
|
|
return {
|
|
views: views,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate a view.
|
|
*
|
|
* Called when opening a subview.
|
|
*
|
|
* @param {viewInfo} info The view's strategy information object.
|
|
* @return {Promise<{cards: Object[]}>}
|
|
*/
|
|
static async generateView(info) {
|
|
const exposedDomainIds = Helper.getExposedDomainIds();
|
|
const area = info.view.strategy.options.area;
|
|
const viewCards = [...(area.extra_cards ?? [])];
|
|
const strategyOptions = {
|
|
entityConfig: info.view.strategy.options.entity_config,
|
|
};
|
|
|
|
// Create cards for each domain.
|
|
for (const domain of exposedDomainIds) {
|
|
if (domain === "default") {
|
|
continue;
|
|
}
|
|
|
|
const className = Helper.sanitizeClassName(domain + "Card");
|
|
|
|
let domainCards = [];
|
|
|
|
try {
|
|
domainCards = await import(`./cards/${className}`).then(cardModule => {
|
|
let domainCards = [];
|
|
const entities = Helper.getDeviceEntities(area, domain);
|
|
|
|
if (entities.length) {
|
|
// Create a Title card for the current domain.
|
|
const titleCard = new TitleCard(
|
|
[area],
|
|
Helper.strategyOptions.domains[domain]
|
|
).createCard();
|
|
|
|
if (domain === "sensor") {
|
|
// Create a card for each entity-sensor of the current area.
|
|
const sensorStates = Helper.getStateEntities(area, "sensor");
|
|
const sensorCards = [];
|
|
|
|
for (const sensor of entities) {
|
|
let card = (strategyOptions.entityConfig?.find(config => config.entity_id === sensor.entity_id));
|
|
|
|
if (card) {
|
|
sensorCards.push(card);
|
|
continue;
|
|
}
|
|
|
|
// Find the state of the current sensor.
|
|
const sensorState = sensorStates.find(state => state.entity_id === sensor.entity_id);
|
|
let cardOptions = {};
|
|
|
|
if (sensorState?.attributes.unit_of_measurement) {
|
|
cardOptions = {
|
|
type: "custom:mini-graph-card",
|
|
entities: [sensor.entity_id],
|
|
};
|
|
}
|
|
|
|
sensorCards.push(new SensorCard(sensor, cardOptions).getCard());
|
|
}
|
|
|
|
domainCards.push({
|
|
type: "vertical-stack",
|
|
cards: sensorCards,
|
|
});
|
|
|
|
domainCards.unshift(titleCard);
|
|
return domainCards;
|
|
}
|
|
|
|
// Create a card for each domain-entity of the current area.
|
|
for (const entity of entities) {
|
|
const card = (Helper.strategyOptions.entity_config ?? []).find(
|
|
config => config.entity === entity.entity_id,
|
|
) ?? new cardModule[className](entity).getCard();
|
|
|
|
domainCards.push(card);
|
|
}
|
|
|
|
if (domain === "binary_sensor") {
|
|
// Horizontally group every two binary sensor cards.
|
|
const horizontalCards = [];
|
|
|
|
for (let i = 0; i < domainCards.length; i += 2) {
|
|
horizontalCards.push({
|
|
type: "horizontal-stack",
|
|
cards: domainCards.slice(i, i + 2),
|
|
});
|
|
}
|
|
|
|
domainCards = horizontalCards;
|
|
}
|
|
|
|
domainCards.unshift(titleCard);
|
|
}
|
|
|
|
return domainCards;
|
|
});
|
|
} catch (e) {
|
|
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
|
|
}
|
|
|
|
if (domainCards.length) {
|
|
viewCards.push({
|
|
type: "vertical-stack",
|
|
cards: domainCards,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create cards for any other domain.
|
|
// Collect device entities of the current area.
|
|
const areaDevices = Helper.devices.filter(device => device.area_id === area.area_id)
|
|
.map(device => device.id);
|
|
|
|
// 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,
|
|
// or the entity itself is linked to the current area.
|
|
// 2. The entity is not hidden and is not disabled.
|
|
const miscellaneousEntities = Helper.entities.filter(entity => {
|
|
return (areaDevices.includes(entity.device_id) || entity.area_id === area.area_id)
|
|
&& entity.hidden_by == null
|
|
&& entity.disabled_by == null
|
|
&& !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]);
|
|
});
|
|
|
|
// Create a column of miscellaneous entity cards.
|
|
if (miscellaneousEntities.length) {
|
|
let miscellaneousCards = [];
|
|
|
|
try {
|
|
miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => {
|
|
/** @type Object[] */
|
|
const miscellaneousCards = [
|
|
new TitleCard([area], Helper.strategyOptions.domains.default).createCard(),
|
|
];
|
|
for (const entity of miscellaneousEntities) {
|
|
const card = (Helper.strategyOptions.entity_config ?? []).find(
|
|
config => config.entity === entity.entity_id,
|
|
) ?? new cardModule.MiscellaneousCard(entity).getCard();
|
|
|
|
miscellaneousCards.push(card);
|
|
}
|
|
|
|
return miscellaneousCards;
|
|
});
|
|
} catch (e) {
|
|
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
|
|
}
|
|
|
|
viewCards.push({
|
|
type: "vertical-stack",
|
|
cards: miscellaneousCards,
|
|
});
|
|
}
|
|
|
|
// Return cards.
|
|
return {
|
|
cards: viewCards,
|
|
};
|
|
}
|
|
}
|
|
|
|
// noinspection JSUnresolvedReference
|
|
customElements.define("ll-strategy-mushroom-strategy", MushroomStrategy);
|