Refactor JavaScript to TypeScript (#81)

Other changes include:

* Add Vacuum Card and View.

* Add hiding a view if no device is configured for it. Closes #24.

* Add icon to HomeView configuration.

* Add version output to console.

* Add version bumper.

* Add .editorconfig settings.

* Refactor README, because the information moved to the repository's
   Wiki. Closes #87.
This commit is contained in:
Ferry Cools
2023-11-22 21:02:56 +01:00
committed by GitHub
parent c8a9cd5618
commit 82a91c3bd4
120 changed files with 6349 additions and 4399 deletions
+196 -168
View File
@@ -1,4 +1,11 @@
import {optionDefaults} from "./optionDefaults";
import {configurationDefaults} from "./configurationDefaults";
import {HassEntities, HassEntity} from "home-assistant-js-websocket";
import deepmerge from "deepmerge";
import {EntityRegistryEntry} from "./types/homeassistant/data/entity_registry";
import {DeviceRegistryEntry} from "./types/homeassistant/data/device_registry";
import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry";
import {generic} from "./types/strategy/generic";
import StrategyArea = generic.StrategyArea;
/**
* Helper Class
@@ -9,31 +16,34 @@ class Helper {
/**
* An array of entities from Home Assistant's entity registry.
*
* @type {hassEntity[]}
* @type {EntityRegistryEntry[]}
* @private
*/
static #entities;
static #entities: EntityRegistryEntry[];
/**
* An array of entities from Home Assistant's device registry.
*
* @type {deviceEntity[]}
* @type {DeviceRegistryEntry[]}
* @private
*/
static #devices;
static #devices: DeviceRegistryEntry[];
/**
* An array of entities from Home Assistant's area registry.
*
* @type {areaEntity[]}
* @type {StrategyArea[]}
* @private
*/
static #areas = [];
static #areas: StrategyArea[] = [];
/**
* An array of state entities from Home Assistant's Hass object.
*
* @type {hassObject["states"]}
* @type {HassEntities}
* @private
*/
static #hassStates;
static #hassStates: HassEntities;
/**
* Indicates whether this module is initialized.
@@ -41,27 +51,30 @@ class Helper {
* @type {boolean} True if initialized.
* @private
*/
static #initialized = false;
static #initialized: boolean = false;
/**
* The Custom strategy configuration.
*
* @type {customStrategyOptions | {}}
* @type {generic.StrategyConfig}
* @private
*/
static #strategyOptions = {};
static #strategyOptions: generic.StrategyConfig;
/**
* Set to true for more verbose information in the console.
*
* @type {boolean}
* @private
*/
static debug = optionDefaults.debug;
static #debug: boolean;
/**
* Class constructor.
*
* This class shouldn't be instantiated directly. Instead, it should be initialized with method initialize().
* This class shouldn't be instantiated directly.
* Instead, it should be initialized with method initialize().
*
* @throws {Error} If trying to instantiate this class.
*/
constructor() {
@@ -71,127 +84,115 @@ class Helper {
/**
* Custom strategy configuration.
*
* @returns {customStrategyOptions|{}}
* @returns {generic.StrategyConfig}
* @static
*/
static get strategyOptions() {
static get strategyOptions(): generic.StrategyConfig {
return this.#strategyOptions;
}
/**
* @returns {areaEntity[]}
* Get the entities from Home Assistant's area registry.
*
* @returns {StrategyArea[]}
* @static
*/
static get areas() {
static get areas(): StrategyArea[] {
return this.#areas;
}
/**
* @returns {deviceEntity[]}
* Get the devices from Home Assistant's device registry.
*
* @returns {DeviceRegistryEntry[]}
* @static
*/
static get devices() {
static get devices(): DeviceRegistryEntry[] {
return this.#devices;
}
/**
* @returns {hassEntity[]}
* Get the entities from Home Assistant's entity registry.
*
* @returns {EntityRegistryEntry[]}
* @static
*/
static get entities() {
static get entities(): EntityRegistryEntry[] {
return this.#entities;
}
/**
* Get the current debug mode of the mushroom strategy.
*
* @returns {boolean}
* @static
*/
static get debug() {
return this.debug;
static get debug(): boolean {
return this.#debug;
}
/**
* Initialize this module.
*
* @param {dashBoardInfo | viewInfo} info Strategy information object.
* @param {generic.DashBoardInfo} info Strategy information object.
* @returns {Promise<void>}
* @static
*/
static async initialize(info) {
static async initialize(info: generic.DashBoardInfo): Promise<void> {
// Initialize properties.
this.#hassStates = info.hass.states;
this.#strategyOptions = deepmerge(configurationDefaults, info.config?.strategy?.options ?? {});
this.#debug = this.#strategyOptions.debug;
try {
// Query the registries of Home Assistant.
[this.#entities, this.#devices, this.#areas] = await Promise.all([
info.hass.callWS({type: "config/entity_registry/list"}),
info.hass.callWS({type: "config/device_registry/list"}),
info.hass.callWS({type: "config/area_registry/list"}),
// noinspection ES6MissingAwait False positive? https://youtrack.jetbrains.com/issue/WEB-63746
[Helper.#entities, Helper.#devices, Helper.#areas] = await Promise.all([
info.hass.callWS({type: "config/entity_registry/list"}) as Promise<EntityRegistryEntry[]>,
info.hass.callWS({type: "config/device_registry/list"}) as Promise<DeviceRegistryEntry[]>,
info.hass.callWS({type: "config/area_registry/list"}) as Promise<AreaRegistryEntry[]>,
]);
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while querying Home assistant's registries!");
Helper.logError("An error occurred while querying Home assistant's registries!", e);
throw 'Check the console for details';
}
// Cloning is required for the purpose of the required undisclosed area.
this.#strategyOptions = structuredClone(info.config.strategy.options || {});
this.debug = this.#strategyOptions.debug;
// Setup required configuration entries.
// TODO: Refactor to something smarter than repeating code for areas, views and domains.
this.#strategyOptions.areas = this.#strategyOptions.areas ?? {};
this.#strategyOptions.views = this.#strategyOptions.views ?? {};
this.#strategyOptions.domains = this.#strategyOptions.domains ?? {};
// Setup and add the undisclosed area if not hidden in the strategy options.
// Create and add the undisclosed area if not hidden in the strategy options.
if (!this.#strategyOptions.areas.undisclosed?.hidden) {
this.#strategyOptions.areas.undisclosed = {
...optionDefaults.areas.undisclosed,
...configurationDefaults.areas.undisclosed,
...this.#strategyOptions.areas.undisclosed,
};
// Make sure the area_id of the custom undisclosed area remains null.
this.#strategyOptions.areas.undisclosed.area_id = null;
// Make sure the custom configuration of the undisclosed area doesn't overwrite the area_id.
this.#strategyOptions.areas.undisclosed.area_id = "undisclosed";
this.#areas.push(this.#strategyOptions.areas.undisclosed);
}
// Merge custom areas of the strategy options into hass areas.
// Merge custom areas of the strategy options into strategy areas.
this.#areas = Helper.areas.map(area => {
return {...area, ...this.#strategyOptions.areas[area.area_id ?? "undisclosed"]};
return {...area, ...this.#strategyOptions.areas?.[area.area_id]};
});
// Sort hass areas by order first and then by name.
// Sort strategy areas by order first and then by name.
this.#areas.sort((a, b) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || a.name.localeCompare(b.name);
});
// Merge the views of the strategy options and the default views.
for (const view of Object.keys(optionDefaults.views)) {
this.#strategyOptions.views[view] = {
...optionDefaults.views[view],
...(this.#strategyOptions.views[view]),
};
}
// Sort views of the strategy options by order first and then by title.
// Sort custom and default views of the strategy options by order first and then by title.
this.#strategyOptions.views = Object.fromEntries(
Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || a.title?.localeCompare(b.title);
}),
Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? "undefined").localeCompare(b.title ?? "undefined");
}),
);
// Merge the domains of the strategy options and the default domains.
for (const domain of Object.keys(optionDefaults.domains)) {
this.#strategyOptions.domains[domain] = {
...optionDefaults.domains[domain],
...(this.#strategyOptions.domains[domain]),
};
}
// Sort domains of the strategy options by order first and then by title.
// Sort custom and default domains of the strategy options by order first and then by title.
this.#strategyOptions.domains = Object.fromEntries(
Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || a.title?.localeCompare(b.title);
}),
Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => {
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? "undefined").localeCompare(b.title ?? "undefined");
}),
);
this.#initialized = true;
@@ -203,7 +204,7 @@ class Helper {
* @returns {boolean} True if this module is initialized.
* @static
*/
static isInitialized() {
static isInitialized(): boolean {
return this.#initialized;
}
@@ -219,7 +220,7 @@ class Helper {
* @return {string} The template string.
* @static
*/
static getCountTemplate(domain, operator, value) {
static getCountTemplate(domain: string, operator: string, value: string): string {
// noinspection JSMismatchedCollectionQueryUpdate (False positive per 17-04-2023)
/**
* Array of entity state-entries, filtered by domain.
@@ -232,7 +233,7 @@ class Helper {
*
* @type {string[]}
*/
const states = [];
const states: string[] = [];
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
@@ -240,20 +241,20 @@ class Helper {
// Get the ID of the devices which are linked to the given area.
for (const area of this.#areas) {
const areaDeviceIds = this.#devices.filter(device => {
const areaDeviceIds = this.#devices.filter((device) => {
return device.area_id === area.area_id;
}).map(device => {
}).map((device) => {
return device.id;
});
// Get the entities of which all conditions of the callback function are met. @see areaFilterCallback.
const newStates = this.#entities.filter(
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.map(entity => `states['${entity.entity_id}']`);
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.map((entity) => `states['${entity.entity_id}']`);
states.push(...newStates);
}
@@ -261,35 +262,6 @@ class Helper {
return `{% set entities = [${states}] %} {{ entities | selectattr('state','${operator}','${value}') | list | count }}`;
}
/**
* Callback function for filtering entities.
*
* Entities of which all the conditions below are met are kept:
* 1. Or/Neither the entity's linked device (if any) or/nor the entity itself is lined to the given area.
* (See variable areaMatch)
* 2. The entity's domain matches the given domain.
* 3. The entity is not hidden and is not disabled.
*
* @param {hassEntity} entity The current hass entity to evaluate.
* @this {areaFilterContext}
*
* @return {boolean} True to keep the entity.
* @static
*/
static #areaFilterCallback(entity) {
const areaMatch = this.area.area_id
// Area is a hass entity; The entity's linked device or the entity itself is linked to the given area.
? this.areaDeviceIds.includes(entity.device_id) || entity.area_id === this.area.area_id
// Undisclosed area; Neither the entity's linked device (if any), nor the entity itself is linked to any area.
: (this.areaDeviceIds.includes(entity.device_id) || !entity.device_id) && !entity.area_id;
return (
areaMatch
&& entity.entity_id.startsWith(`${this.domain}.`)
&& entity.hidden_by == null && entity.disabled_by == null
);
}
/**
* Get device entities from the entity registry, filtered by area and domain.
*
@@ -298,36 +270,35 @@ class Helper {
*
* The result excludes hidden and disabled entities.
*
* @param {areaEntity} area Area entity.
* @param {AreaRegistryEntry} area Area entity.
* @param {string} domain The domain of the entity-id.
*
* @return {hassEntity[]} Array of device entities.
* @return {EntityRegistryEntry[]} Array of device entities.
* @static
*/
static getDeviceEntities(area, domain) {
static getDeviceEntities(area: AreaRegistryEntry, domain: string): EntityRegistryEntry[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
// Get the ID of the devices which are linked to the given area.
const areaDeviceIds = this.#devices.filter(device => {
return device.area_id === area.area_id;
}).map(device => {
const areaDeviceIds = this.#devices.filter((device) => {
return (device.area_id ?? "undisclosed") === area.area_id;
}).map((device: DeviceRegistryEntry) => {
return device.id;
});
// Return the entities of which all conditions of the callback function are met. @see areaFilterCallback.
return this.#entities.filter(
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.sort((a, b) => {
/** @type hassEntity */
return a.original_name?.localeCompare(b.original_name);
});
this.#areaFilterCallback, {
area: area,
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.sort((a, b) => {
return (a.original_name ?? "undefined").localeCompare(b.original_name ?? "undefined");
});
}
/**
@@ -335,39 +306,41 @@ class Helper {
*
* The result excludes hidden and disabled entities.
*
* @param {areaEntity} area Area entity.
* @param {AreaRegistryEntry} area Area entity.
* @param {string} domain Domain of the entity-id.
*
* @return {stateObject[]} Array of state entities.
* @return {HassEntity[]} Array of state entities.
*/
static getStateEntities(area, domain) {
static getStateEntities(area: AreaRegistryEntry, domain: string): HassEntity[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
const states = [];
const states: HassEntity[] = [];
// Create a map for the hassEntities and devices {id: object} to improve lookup speed.
/** @type {Object<string, hassEntity>} */
const entityMap = Object.fromEntries(this.#entities.map(entity => [entity.entity_id, entity]));
/** @type {Object<string, deviceEntity>} */
const deviceMap = Object.fromEntries(this.#devices.map(device => [device.id, device]));
const entityMap: {
[s: string]: EntityRegistryEntry;
} = Object.fromEntries(this.#entities.map((entity) => [entity.entity_id, entity]));
const deviceMap: {
[s: string]: DeviceRegistryEntry;
} = Object.fromEntries(this.#devices.map((device) => [device.id, device]));
// Get states whose entity-id starts with the given string.
const stateEntities = Object.values(this.#hassStates).filter(
state => state.entity_id.startsWith(`${domain}.`),
(state) => state.entity_id.startsWith(`${domain}.`),
);
for (const state of stateEntities) {
const hassEntity = entityMap[state.entity_id];
const device = deviceMap[hassEntity?.device_id];
const device = deviceMap[hassEntity?.device_id ?? ""];
// Collect states of which any (whichever comes first) of the conditions below are met:
// 1. The linked entity is linked to the given area.
// 2. The entity is linked to a device, and the linked device is linked to the given area.
if (
(hassEntity?.area_id === area.area_id)
|| (device && device.area_id === area.area_id)
(hassEntity?.area_id === area.area_id)
|| (device && device.area_id === area.area_id)
) {
states.push(state);
}
@@ -385,44 +358,22 @@ class Helper {
* @param {string} className Name of the class to sanitize.
* @returns {string} The sanitized classname.
*/
static sanitizeClassName(className) {
static sanitizeClassName(className: string): string {
className = className.charAt(0).toUpperCase() + className.slice(1);
return className.replace(/([-_][a-z])/g, group =>
group
.toUpperCase()
.replace("-", "")
.replace("_", ""),
return className.replace(/([-_][a-z])/g, (group) => group
.toUpperCase()
.replace("-", "")
.replace("_", ""),
);
}
/**
* Get the keys of nested objects by its property value.
*
* @param {Object<Object>} object An object of objects.
* @param {string|number} property The name of the property to evaluate.
* @param {*} value The value which the property should match.
*
* @return {string[]|number[]} An array with keys.
*/
static #getObjectKeysByPropertyValue(object, property, value) {
const keys = [];
for (const key of Object.keys(object)) {
if (object[key][property] === value) {
keys.push(key);
}
}
return keys;
}
/**
* Get the ids of the views which aren't set to hidden in the strategy options.
*
* @return {string[]} An array of view ids.
*/
static getExposedViewIds() {
static getExposedViewIds(): string[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
@@ -435,13 +386,90 @@ class Helper {
*
* @return {string[]} An array of domain ids.
*/
static getExposedDomainIds() {
static getExposedDomainIds(): string[] {
if (!this.isInitialized()) {
console.warn("Helper class should be initialized before calling this method!");
}
return this.#getObjectKeysByPropertyValue(this.#strategyOptions.domains, "hidden", false);
}
/**
* Callback function for filtering entities.
*
* Entities of which all the conditions below are met are kept:
* 1. The entity is not hidden and is not disabled.
* 2. The entity's domain matches the given domain.
* 3. Or/Neither the entity's linked device (if any) or/nor the entity itself is linked to the given area.
* (See variable areaMatch)
*
* @param {EntityRegistryEntry} entity The current hass entity to evaluate.
* @this {AreaFilterContext}
*
* @return {boolean} True to keep the entity.
* @static
*/
static #areaFilterCallback(
this: {
area: AreaRegistryEntry,
areaDeviceIds: string[],
domain: string,
},
entity: EntityRegistryEntry): boolean {
const entityUnhidden = entity.hidden_by === null && entity.disabled_by === null;
const domainMatches = entity.entity_id.startsWith(`${this.domain}.`);
const entityLinked = this.area.area_id === "undisclosed"
// Undisclosed area;
// nor the entity itself, neither the entity's linked device (if any) is linked to any area.
? !entity.area_id && (this.areaDeviceIds.includes(entity.device_id ?? "") || !entity.device_id)
// Area is a hass entity;
// The entity's linked device or the entity itself is linked to the given area.
: this.areaDeviceIds.includes(entity.device_id ?? "") || entity.area_id === this.area.area_id;
return (entityUnhidden && domainMatches && entityLinked);
}
/**
* Get the keys of nested objects by its property value.
*
* @param {Object<string, any>} object An object of objects.
* @param {string|number} property The name of the property to evaluate.
* @param {*} value The value which the property should match.
*
* @return {string[]} An array with keys.
*/
static #getObjectKeysByPropertyValue(
object: { [k: string]: any },
property: string, value: any
): string[] {
const keys: string[] = [];
for (const key of Object.keys(object)) {
if (object[key][property] === value) {
keys.push(key);
}
}
return keys;
}
/**
* Logs an error message to the console.
*
* @param {string} userMessage - The error message to display.
* @param {unknown} [e] - (Optional) The error object or additional information.
*
* @return {void}
*/
static logError(userMessage: string, e?: unknown): void {
if (Helper.debug) {
console.error(userMessage, e);
return;
}
console.error(userMessage);
}
}
export {Helper};
-81
View File
@@ -1,81 +0,0 @@
import {Helper} from "../Helper";
/**
* Abstract Card Class
*
* To create a new card, extend the new class with this one.
*
* @class
* @abstract
*/
class AbstractCard {
/**
* Entity to create the card for.
*
* @type {hassEntity | areaEntity}
*/
entity;
/**
* Options for creating a card.
*
* @type {abstractOptions}
*/
options = {
type: "custom:mushroom-entity-card",
icon: "mdi:help-circle",
double_tap_action: {
action: null,
},
};
/**
* Class constructor.
*
* @param {hassEntity | areaEntity} entity The hass entity to create a card for.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity) {
if (this.constructor === AbstractCard) {
throw new Error("Abstract classes can't be instantiated.");
}
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.entity = entity;
}
/**
* Merge the default options of this class and the custom options into the options of the parent class.
*
* @param {Object} [defaultOptions={}] Default options for the card.
* @param {Object} [customOptions={}] Custom Options for the card.
*/
mergeOptions(defaultOptions, customOptions) {
this.options = {
...this.options,
...defaultOptions,
...customOptions,
};
try {
this.options.double_tap_action.target.entity_id = this.entity.entity_id;
} catch { }
}
/**
* Get a card for an entity.
*
* @return {abstractOptions & Object} A card object.
*/
getCard() {
return {
entity: this.entity.entity_id,
...this.options,
};
}
}
export {AbstractCard};
+59
View File
@@ -0,0 +1,59 @@
import {Helper} from "../Helper";
import {cards} from "../types/strategy/cards";
import {generic} from "../types/strategy/generic";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
/**
* Abstract Card Class
*
* To create a new card, extend the new class with this one.
*
* @class
* @abstract
*/
abstract class AbstractCard {
/**
* Entity to create the card for.
*
* @type {generic.RegistryEntry}
*/
entity: generic.RegistryEntry;
/**
* Configuration of the card.
*
* @type {EntityCardConfig}
*/
config: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:help-circle",
};
/**
* Class constructor.
*
* @param {generic.RegistryEntry} entity The hass entity to create a card for.
* @throws {Error} If the Helper module isn't initialized.
*/
protected constructor(entity: generic.RegistryEntry) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.entity = entity;
}
/**
* Get a card.
*
* @return {cards.AbstractCardConfig} A card object.
*/
getCard(): cards.AbstractCardConfig {
return {
...this.config,
entity: "entity_id" in this.entity ? this.entity.entity_id : undefined,
};
}
}
export {AbstractCard};
-62
View File
@@ -1,62 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Area Card Class
*
* Used to create a card for an entity of the area domain.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {areaCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-template-card",
primary: undefined,
icon: "mdi:texture-box",
icon_color: "blue",
tap_action: {
action: "navigate",
navigation_path: undefined,
},
hold_action: {
action: "none",
},
};
/**
* Class constructor.
*
* @param {areaEntity} area The area entity to create a card for.
* @param {areaCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area, options = {}) {
super(area);
this.#defaultOptions.primary = 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.#defaultOptions,
options,
);
// Override the area's name with a custom name, unless a custom primary text is set.
if (!options.primary && options.name) {
this.options.primary = options.name;
}
}
}
export {AreaCard};
+62
View File
@@ -0,0 +1,62 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry";
import {TemplateCardConfig} from "../types/lovelace-mushroom/cards/template-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Area Card Class
*
* Used to create a card for an entity of the area domain.
*
* @class
* @extends AbstractCard
*/
class AreaCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {TemplateCardConfig}
* @private
*/
#defaultConfig: TemplateCardConfig = {
type: "custom:mushroom-template-card",
primary: undefined,
icon: "mdi:texture-box",
icon_color: "blue",
tap_action: {
action: "navigate",
navigation_path: "",
},
hold_action: {
action: "none",
},
};
/**
* Class constructor.
*
* @param {AreaRegistryEntry} area The area entity to create a card for.
* @param {cards.TemplateCardOptions} [options={}] Options for the card.
*
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area: AreaRegistryEntry, options: cards.TemplateCardOptions = {}) {
super(area);
// Don't override the default card type if default is set in the strategy options.
if (options.type === "default") {
delete options.type;
}
// Initialize the default configuration.
this.#defaultConfig.primary = area.name;
if (this.#defaultConfig.tap_action && ("navigation_path" in this.#defaultConfig.tap_action)) {
this.#defaultConfig.tap_action.navigation_path = area.area_id;
}
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {AreaCard};
-41
View File
@@ -1,41 +0,0 @@
import {SensorCard} from "./SensorCard";
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the binary_sensor domain.
*
* @class
* @extends SensorCard
*/
class BinarySensorCard extends SensorCard {
/**
* Default options of the card.
*
* @type {sensorCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon: "mdi:power-cycle",
icon_color: "green",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {sensorCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {BinarySensorCard};
+42
View File
@@ -0,0 +1,42 @@
import {SensorCard} from "./SensorCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the binary_sensor domain.
*
* @class
* @extends SensorCard
*/
class BinarySensorCard extends SensorCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:power-cycle",
icon_color: "green",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {BinarySensorCard};
-41
View File
@@ -1,41 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Camera Card Class
*
* Used to create a card for controlling an entity of the camera domain.
*
* @class
* @extends AbstractCard
*/
class CameraCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {cameraCardOptions}
* @private
*/
#defaultOptions = {
type: "picture-entity",
show_name: false,
show_state: false,
camera_view: "live",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {cameraCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {CameraCard};
+44
View File
@@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {PictureEntityCardConfig} from "../types/homeassistant/panels/lovelave/cards/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Camera Card Class
*
* Used to create a card for controlling an entity of the camera domain.
*
* @class
* @extends AbstractCard
*/
class CameraCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {PictureEntityCardConfig}
* @private
*/
#defaultConfig: PictureEntityCardConfig = {
entity: "",
type: "picture-entity",
show_name: false,
show_state: false,
camera_view: "live",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.PictureEntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.PictureEntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {CameraCard};
-46
View File
@@ -1,46 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Climate Card Class
*
* Used to create a card for controlling an entity of the climate domain.
*
* @class
* @extends AbstractCard
*/
class ClimateCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {climateCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-climate-card",
icon: undefined,
hvac_modes: [
"off",
"cool",
"heat",
"fan_only",
],
show_temperature_control: true,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {climateCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {ClimateCard};
+48
View File
@@ -0,0 +1,48 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {ClimateCardConfig} from "../types/lovelace-mushroom/cards/climate-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Climate Card Class
*
* Used to create a card for controlling an entity of the climate domain.
*
* @class
* @extends AbstractCard
*/
class ClimateCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {ClimateCardConfig}
* @private
*/
#defaultConfig: ClimateCardConfig = {
type: "custom:mushroom-climate-card",
icon: undefined,
hvac_modes: [
"off",
"cool",
"heat",
"fan_only",
],
show_temperature_control: true,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.ClimateCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.ClimateCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {ClimateCard};
+102
View File
@@ -0,0 +1,102 @@
import {cards} from "../types/strategy/cards";
import {StackCardConfig} from "../types/homeassistant/lovelace/cards/types";
import {LovelaceCardConfig} from "../types/homeassistant/data/lovelace";
import {HassServiceTarget} from "home-assistant-js-websocket";
/**
* Controller Card class.
*
* Used for creating a Title Card with controls.
*
* @class
*/
class ControllerCard {
/**
* @type {HassServiceTarget} The target to control the entities of.
* @private
*/
readonly #target: HassServiceTarget;
/**
* Default configuration of the card.
*
* @type {cards.ControllerCardConfig}
* @private
*/
readonly #defaultConfig: cards.ControllerCardConfig = {
type: "mushroom-title-card",
showControls: true,
iconOn: "mdi:power-on",
iconOff: "mdi:power-off",
onService: "none",
offService: "none",
};
/**
* 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 = {}) {
this.#target = target;
this.#defaultConfig = {
...this.#defaultConfig,
...options,
};
}
/**
* Create a Controller card.
*
* @return {StackCardConfig} A Controller card.
*/
createCard(): StackCardConfig {
const cards: LovelaceCardConfig[] = [
{
type: "custom:mushroom-title-card",
title: this.#defaultConfig.title,
subtitle: this.#defaultConfig.subtitle,
},
];
if (this.#defaultConfig.showControls) {
cards.push({
type: "horizontal-stack",
cards: [
{
type: "custom:mushroom-template-card",
icon: this.#defaultConfig.iconOff,
layout: "vertical",
icon_color: "red",
tap_action: {
action: "call-service",
service: this.#defaultConfig.offService,
target: this.#target,
data: {},
},
},
{
type: "custom:mushroom-template-card",
icon: this.#defaultConfig.iconOn,
layout: "vertical",
icon_color: "amber",
tap_action: {
action: "call-service",
service: this.#defaultConfig.onService,
target: this.#target,
data: {},
},
},
],
});
}
return {
type: "horizontal-stack",
cards: cards,
};
}
}
export {ControllerCard};
-42
View File
@@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Cover Card Class
*
* Used to create a card for controlling an entity of the cover domain.
*
* @class
* @extends AbstractCard
*/
class CoverCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {coverCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-cover-card",
icon: undefined,
show_buttons_control: true,
show_position_control: true,
show_tilt_position_control: true,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {coverCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {CoverCard};
+44
View File
@@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {CoverCardConfig} from "../types/lovelace-mushroom/cards/cover-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Cover Card Class
*
* Used to create a card for controlling an entity of the cover domain.
*
* @class
* @extends AbstractCard
*/
class CoverCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {CoverCardConfig}
* @private
*/
#defaultConfig: CoverCardConfig = {
type: "custom:mushroom-cover-card",
icon: undefined,
show_buttons_control: true,
show_position_control: true,
show_tilt_position_control: true,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.CoverCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.CoverCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {CoverCard};
-42
View File
@@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Fan Card Class
*
* Used to create a card for controlling an entity of the fan domain.
*
* @class
* @extends AbstractCard
*/
class FanCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {fanCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-fan-card",
icon: undefined,
show_percentage_control: true,
show_oscillate_control: true,
icon_animation: true,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {fanCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {FanCard};
+44
View File
@@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {FanCardConfig} from "../types/lovelace-mushroom/cards/fan-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Fan Card Class
*
* Used to create a card for controlling an entity of the fan domain.
*
* @class
* @extends AbstractCard
*/
class FanCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {FanCardConfig}
* @private
*/
#defaultConfig: FanCardConfig = {
type: "custom:mushroom-fan-card",
icon: undefined,
show_percentage_control: true,
show_oscillate_control: true,
icon_animation: true,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.FanCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.FanCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {FanCard};
-47
View File
@@ -1,47 +0,0 @@
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};
+49
View File
@@ -0,0 +1,49 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry";
import {AreaCardConfig} from "../types/homeassistant/lovelace/cards/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* 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 configuration of the card.
*
* @type {AreaCardConfig}
* @private
*/
#defaultConfig: AreaCardConfig = {
type: "area",
area: "",
};
/**
* Class constructor.
*
* @param {AreaRegistryEntry} area The area entity to create a card for.
* @param {cards.AreaCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(area: AreaRegistryEntry, options: cards.AreaCardOptions = {}) {
super(area);
// Initialize the default configuration.
this.#defaultConfig.area = area.area_id;
this.#defaultConfig.navigation_path = this.#defaultConfig.area;
// Enforce the card type.
delete options.type;
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {AreaCard};
-53
View File
@@ -1,53 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Light Card Class
*
* Used to create a card for controlling an entity of the light domain.
*
* @class
* @extends AbstractCard
*/
class LightCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {lightCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-light-card",
icon: undefined,
show_brightness_control: true,
show_color_control: true,
use_light_color: true,
show_color_temp_control: true,
double_tap_action: {
target: {
entity_id: undefined,
},
action: "call-service",
service: "light.turn_on",
data: {
rgb_color: [255, 255, 255],
},
},
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {lightCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {LightCard};
+67
View File
@@ -0,0 +1,67 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {LightCardConfig} from "../types/lovelace-mushroom/cards/light-card-config";
import {generic} from "../types/strategy/generic";
import isCallServiceActionConfig = generic.isCallServiceActionConfig;
import isCallServiceActionTarget = generic.isCallServiceActionTarget;
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Light Card Class
*
* Used to create a card for controlling an entity of the light domain.
*
* @class
* @extends AbstractCard
*/
class LightCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {LightCardConfig}
* @private
*/
#defaultConfig: LightCardConfig = {
type: "custom:mushroom-light-card",
icon: undefined,
show_brightness_control: true,
show_color_control: true,
show_color_temp_control: true,
use_light_color: true,
double_tap_action: {
action: "call-service",
service: "light.turn_on",
target: {
entity_id: undefined,
},
data: {
rgb_color: [255, 255, 255],
},
},
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.LightCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.LightCardOptions = {}) {
super(entity);
// Set the target for double-tap action.
if (
isCallServiceActionConfig(this.#defaultConfig.double_tap_action)
&& isCallServiceActionTarget(this.#defaultConfig.double_tap_action.target)
) {
this.#defaultConfig.double_tap_action.target.entity_id = entity.entity_id;
}
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {LightCard};
-39
View File
@@ -1,39 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Lock Card Class
*
* Used to create a card for controlling an entity of the lock domain.
*
* @class
* @extends AbstractCard
*/
class LockCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {lockCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-lock-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {lockCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {LockCard};
+41
View File
@@ -0,0 +1,41 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {LockCardConfig} from "../types/lovelace-mushroom/cards/lock-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Lock Card Class
*
* Used to create a card for controlling an entity of the lock domain.
*
* @class
* @extends AbstractCard
*/
class LockCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {LockCardConfig}
* @private
*/
#defaultConfig: LockCardConfig = {
type: "custom:mushroom-lock-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.LockCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.LockCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {LockCard};
-50
View File
@@ -1,50 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Mediaplayer Card Class
*
* Used to create a card for controlling an entity of the media_player domain.
*
* @class
* @extends AbstractCard
*/
class MediaPlayerCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {mediaPlayerCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-media-player-card",
icon: undefined,
use_media_info: true,
media_controls: [
"on_off",
"play_pause_stop",
],
show_volume_level: true,
volume_controls: [
"volume_mute",
"volume_set",
"volume_buttons",
],
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {mediaPlayerCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {MediaPlayerCard};
+51
View File
@@ -0,0 +1,51 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {MediaPlayerCardConfig} from "../types/lovelace-mushroom/cards/media-player-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Mediaplayer Card Class
*
* Used to create a card for controlling an entity of the media_player domain.
*
* @class
* @extends AbstractCard
*/
class MediaPlayerCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {MediaPlayerCardConfig}
* @private
*/
#defaultConfig: MediaPlayerCardConfig = {
type: "custom:mushroom-media-player-card",
use_media_info: true,
media_controls: [
"on_off",
"play_pause_stop",
],
show_volume_level: true,
volume_controls: [
"volume_mute",
"volume_set",
"volume_buttons",
],
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.MediaPlayerCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.MediaPlayerCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {MediaPlayerCard};
-39
View File
@@ -1,39 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Miscellaneous Card Class
*
* Used to create a card an entity of any domain.
*
* @class
* @extends AbstractCard
*/
class MiscellaneousCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {miscellaneousCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon_color: "blue-grey",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {miscellaneousCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {MiscellaneousCard};
+40
View File
@@ -0,0 +1,40 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
/**
* Miscellaneous Card Class
*
* Used to create a card an entity of any domain.
*
* @class
* @extends AbstractCard
*/
class MiscellaneousCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon_color: "blue-grey",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {MiscellaneousCard};
-37
View File
@@ -1,37 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Number Card Class
*
* Used to create a card for controlling an entity of the number domain.
*
* @class
* @extends AbstractCard
*/
class NumberCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {numberCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-number-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {numberCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(this.#defaultOptions, options);
}
}
export {NumberCard};
+41
View File
@@ -0,0 +1,41 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {NumberCardConfig} from "../types/lovelace-mushroom/cards/number-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported
/**
* Number Card Class
*
* Used to create a card for controlling an entity of the number domain.
*
* @class
* @extends AbstractCard
*/
class NumberCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {NumberCardConfig}
* @private
*/
#defaultConfig: NumberCardConfig = {
type: "custom:mushroom-number-card",
icon: undefined,
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.NumberCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.NumberCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {NumberCard};
-42
View File
@@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Person Card Class
*
* Used to create a card for an entity of the person domain.
*
* @class
* @extends AbstractCard
*/
class PersonCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {personCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-person-card",
layout: "vertical",
primary_info: "none",
secondary_info: "none",
icon_type: "entity-picture",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {personCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {PersonCard};
+43
View File
@@ -0,0 +1,43 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {PersonCardConfig} from "../types/lovelace-mushroom/cards/person-card-config";
/**
* Person Card Class
*
* Used to create a card for an entity of the Person domain.
*
* @class
* @extends AbstractCard
*/
class PersonCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {PersonCardConfig}
* @private
*/
#defaultConfig: PersonCardConfig = {
type: "custom:mushroom-person-card",
layout: "vertical",
primary_info: "none",
secondary_info: "none",
icon_type: "entity-picture",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.PersonCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.PersonCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {PersonCard};
-42
View File
@@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the sensor domain.
*
* @class
* @extends AbstractCard
*/
class SensorCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {sensorCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon: "mdi:information",
animate: true,
line_color: "green",
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {sensorCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {SensorCard};
+42
View File
@@ -0,0 +1,42 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
/**
* Sensor Card Class
*
* Used to create a card for controlling an entity of the sensor domain.
*
* @class
* @extends AbstractCard
*/
class SensorCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: "mdi:information",
animate: true,
line_color: "green",
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {SensorCard};
-42
View File
@@ -1,42 +0,0 @@
import {AbstractCard} from "./AbstractCard";
/**
* Switch Card Class
*
* Used to create a card for controlling an entity of the switch domain.
*
* @class
* @extends AbstractCard
*/
class SwitchCard extends AbstractCard {
/**
* Default options of the card.
*
* @type {switchCardOptions}
* @private
*/
#defaultOptions = {
type: "custom:mushroom-entity-card",
icon: undefined,
tap_action: {
action: "toggle",
},
};
/**
* Class constructor.
*
* @param {hassEntity} entity The hass entity to create a card for.
* @param {switchCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity, options = {}) {
super(entity);
this.mergeOptions(
this.#defaultOptions,
options,
);
}
}
export {SwitchCard};
+44
View File
@@ -0,0 +1,44 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {EntityCardConfig} from "../types/lovelace-mushroom/cards/entity-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Switch Card Class
*
* Used to create a card for controlling an entity of the switch domain.
*
* @class
* @extends AbstractCard
*/
class SwitchCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {EntityCardConfig}
* @private
*/
#defaultConfig: EntityCardConfig = {
type: "custom:mushroom-entity-card",
icon: undefined,
tap_action: {
action: "toggle",
},
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.EntityCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.EntityCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {SwitchCard};
-101
View File
@@ -1,101 +0,0 @@
/**
* Title Card class.
*
* Used for creating a Title Card.
*
* @class
*/
class TitleCard {
/**
* @type {string[]} An array of area ids.
* @private
*/
#areaIds;
/**
* @type {titleCardOptions}
* @private
*/
#options = {
title: undefined,
subtitle: undefined,
showControls: true,
iconOn: "mdi:power-on",
iconOff: "mdi:power-off",
onService: "none",
offService: "none",
};
/**
* Class constructor.
*
* @param {areaEntity[]} areas An array of area entities.
* @param {titleCardOptions} options Title Card options.
*/
constructor(areas, options = {}) {
this.#areaIds = areas.map(area => area.area_id).filter(area_id => area_id);
this.#options = {
...this.#options,
...options,
};
}
/**
* Create a Title card.
*
* @return {Object} A Title card.
*/
createCard() {
/** @type {Object[]} */
const cards = [
{
type: "custom:mushroom-title-card",
title: this.#options.title,
subtitle: this.#options.subtitle,
},
];
if (this.#options.showControls) {
cards.push({
type: "horizontal-stack",
cards: [
{
type: "custom:mushroom-template-card",
icon: this.#options.iconOff,
layout: "vertical",
icon_color: "red",
tap_action: {
action: "call-service",
service: this.#options.offService,
target: {
area_id: this.#areaIds,
},
data: {},
},
},
{
type: "custom:mushroom-template-card",
icon: this.#options.iconOn,
layout: "vertical",
icon_color: "amber",
tap_action: {
action: "call-service",
service: this.#options.onService,
target: {
area_id: this.#areaIds,
},
data: {},
},
},
],
});
}
return {
type: "horizontal-stack",
cards: cards,
};
}
}
export {TitleCard};
+46
View File
@@ -0,0 +1,46 @@
import {AbstractCard} from "./AbstractCard";
import {cards} from "../types/strategy/cards";
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {VACUUM_COMMANDS, VacuumCardConfig} from "../types/lovelace-mushroom/cards/vacuum-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Vacuum Card Class
*
* Used to create a card for controlling an entity of the vacuum domain.
*
* @class
* @extends AbstractCard
*/
class VacuumCard extends AbstractCard {
/**
* Default configuration of the card.
*
* @type {VacuumCardConfig}
* @private
*/
#defaultConfig: VacuumCardConfig = {
type: "custom:mushroom-vacuum-card",
icon: undefined,
icon_animation: true,
commands: [...VACUUM_COMMANDS],
tap_action: {
action: "more-info",
}
};
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The hass entity to create a card for.
* @param {cards.VacuumCardOptions} [options={}] Options for the card.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor(entity: EntityRegistryEntry, options: cards.VacuumCardOptions = {}) {
super(entity);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {VacuumCard};
-154
View File
@@ -1,154 +0,0 @@
/**
* @namespace typedefs.cards
*/
/**
* @typedef {Object} abstractOptions
* @property {string} [type] The type of the card.
* @property {string} [icon] Icon of the card.
* @property {Object} [double_tap_action] Home assistant action to perform on double_tap.
*/
/**
* @typedef {Object} titleCardOptions Title Card options.
* @property {string} [title] Title to render. May contain templates.
* @property {string} [subtitle] Subtitle to render. May contain templates.
* @property {boolean} [showControls=true] False to hide controls.
* @property {string} [iconOn] Icon to show for switching entities from off state.
* @property {string} [iconOff] Icon to show for switching entities to off state.
* @property {string} [onService=none] Service to call for switching entities from off state.
* @property {string} [offService=none] Service to call for switching entities to off state.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} lightCardOptions Light Card options.
* @property {boolean} [show_brightness_control=true] Show a slider to control brightness
* @property {boolean} [show_color_control=true] Show a slider to control RGB color
* @property {boolean} [use_light_color=true] Colorize the icon and slider according light temperature or color
* @property {{double_tap_action: lightDoubleTapAction}} [action] Home assistant action to perform on double_tap
* @memberOf typedefs.cards
*/
/**
* @typedef {Object} lightDoubleTapAction Home assistant action to perform on double_tap.
* @property {{entity_id: string}} target The target entity id.
* @property {"call-service"} action Calls a hass service.
* @property {"light.turn_on"} service The hass service to call
* @property {{rgb_color: [255, 255, 255]}} data The data payload for the service.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} coverCardOptions Cover Card options.
* @property {boolean} [show_buttons_control=true] Show buttons to open, close and stop cover.
* @property {boolean} [show_position_control=true] Show a slider to control position of the cover.
* @property {boolean} [show_tilt_position_control=true] Show a slider to control tilt position of the cover.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} fanCardOptions Fan Card options.
* @property {boolean} [show_percentage_control=true] Show a slider to control speed.
* @property {boolean} [show_oscillate_control=true] Show a button to control oscillation.
* @property {boolean} [icon_animation=true] Animate the icon when fan is on.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} lockCardOptions Lock Card options.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} numberCardOptions Number Card options.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} switchCardOptions Switch Card options.
* @property {{tap_action: switchTapAction}} [action] Home assistant action to perform on tap.
* @memberOf typedefs.cards
*/
/**
* @typedef {Object} switchTapAction Home assistant action to perform on tap.
* @property {"toggle"} action Toggles a hass entity.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} climateCardOptions Climate Card options.
* @property {["off", "cool", "heat", "fan_only"]} [hvac_modes] Show buttons to control target temperature.
* @property {boolean} [show_temperature_control=true] Show buttons to control target temperature.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions} cameraCardOptions Camera Card options.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} personCardOptions Person Card options.
* @property {string} [layout] Layout of the card. Vertical, horizontal, and default layouts are supported.
* @property {("name" | "state" | "last-changed" | "last-updated" | "none")} [primary_info=name] Info to show as
* primary info.
* @property {("name" | "state" | "last-changed" | "last-updated" | "none")} [secondary_info=sate] Info to show as
* secondary info.
* @property {("icon" | "entity-picture" | "none")} [icon_type]=icon Type of icon to display.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} areaCardOptions Area Card options.
* @property {string} [name] The name of the area
* @property {string} [icon] Icon to render. May contain templates.
* @property {string} [icon_color] Icon color to render. May contain templates.
* @property {string} [primary] Primary info to render. May contain templates.
* @property {areaTapAction} [tap_action] Home assistant action to perform on tap.
* @memberOf typedefs.cards
*/
/**
* @typedef {Object} areaTapAction Home assistant action to perform on tap.
* @property {"navigate"} action Toggles a hass entity.
* @property {string} navigation_path The id of the area to navigate to.
* @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.
* @property {boolean} [use_media_info=true] Use media info instead of name, state, and icon when a media is playing
* @property {string[]} [media_controls="on_off", "play_pause_stop"] List of controls to display
* (on_off, shuffle, previous, play_pause_stop, next,
* repeat)
* @property {boolean} [show_volume_level=true] Show volume level next to media state when media is playing
* @property {string[]} [volume_controls="volume_mute", "volume_set", "volume_buttons"] List of controls to display
* (volume_mute, volume_set,
* volume_buttons)
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} sensorCardOptions Sensor Card options.
* @property {string} [icon_color=green] Custom color for icon when entity is state is active.
* @property {boolean} [animate=true] Add a reveal animation to the graph.
* @property {string} [line_color=green] Set a custom color for the graph line.
* Provide a list of colors for multiple graph entries.
* @memberOf typedefs.cards
*/
/**
* @typedef {abstractOptions & Object} miscellaneousCardOptions Miscellaneous Card options.
* @property {string} [icon_color=blue-grey] Custom color for icon when entity is state is active.
* @memberOf typedefs.cards
*/
+64
View File
@@ -0,0 +1,64 @@
import {HassServiceTarget} from "home-assistant-js-websocket";
import {LovelaceChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
import {Helper} from "../Helper";
import {generic} from "../types/strategy/generic";
import isCallServiceActionConfig = generic.isCallServiceActionConfig;
/**
* Abstract Chip class.
*
* To create a new chip, extend this one.
*
* @class
* @abstract
*/
abstract class AbstractChip {
/**
* Configuration of the chip.
*
* @type {LovelaceChipConfig}
*/
config: LovelaceChipConfig = {
type: "template"
};
/**
* Class Constructor.
*/
protected constructor() {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
}
// noinspection JSUnusedGlobalSymbols Method is called on dymanically imported classes.
/**
* Get the chip.
*
* @returns {LovelaceChipConfig} A chip.
*/
getChip(): LovelaceChipConfig {
return this.config;
}
/**
* Set the target to switch.
*
* @param {HassServiceTarget} target Target to switch.
*/
setTapActionTarget(target: HassServiceTarget) {
if ("tap_action" in this.config && isCallServiceActionConfig(this.config.tap_action)) {
this.config.tap_action.target = target;
return;
}
if (Helper.debug) {
console.warn(
this.constructor.name
+ " - Target not set: Invalid target or tap action.");
}
}
}
export {AbstractChip};
-39
View File
@@ -1,39 +0,0 @@
import {Helper} from "../Helper";
class ClimateChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:thermostat",
icon_color: "orange",
content: Helper.getCountTemplate("climate", "ne", "off"),
tap_action: {
action: "navigate",
navigation_path: "climates",
},
hold_action: {
action: "navigate",
navigation_path: "climates",
},
};
}
}
export {ClimateChip};
+47
View File
@@ -0,0 +1,47 @@
import {Helper} from "../Helper";
import {AbstractChip} from "./AbstractChip";
import {chips} from "../types/strategy/chips";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Climate Chip class.
*
* Used to create a chip to indicate how many climates are operating.
*/
class ClimateChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:thermostat",
icon_color: "orange",
content: Helper.getCountTemplate("climate", "ne", "off"),
tap_action: {
action: "none",
},
hold_action: {
action: "navigate",
navigation_path: "climates",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {ClimateChip};
-35
View File
@@ -1,35 +0,0 @@
import {Helper} from "../Helper";
class CoverChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:window-open",
icon_color: "cyan",
content: Helper.getCountTemplate("cover", "eq", "open"),
tap_action: {
action: "navigate",
navigation_path: "covers",
},
};
}
}
export {CoverChip};
+47
View File
@@ -0,0 +1,47 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Cover Chip class.
*
* Used to create a chip to indicate how many covers aren't closed.
*/
class CoverChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:window-open",
icon_color: "cyan",
content: Helper.getCountTemplate("cover", "eq", "open"),
tap_action: {
action: "none",
},
hold_action: {
action: "navigate",
navigation_path: "covers",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {CoverChip};
-43
View File
@@ -1,43 +0,0 @@
import {Helper} from "../Helper";
class FanChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:fan",
icon_color: "green",
content: Helper.getCountTemplate("fan", "eq", "on"),
tap_action: {
action: "call-service",
service: "fan.turn_off",
target: {
area_id: this.#areaIds,
},
data: {},
},
hold_action: {
action: "navigate",
navigation_path: "fans",
},
};
}
}
export {FanChip};
+48
View File
@@ -0,0 +1,48 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Fan Chip class.
*
* Used to create a chip to indicate how many fans are on and to turn all off.
*/
class FanChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:fan",
icon_color: "green",
content: Helper.getCountTemplate("fan", "eq", "on"),
tap_action: {
action: "call-service",
service: "fan.turn_off",
},
hold_action: {
action: "navigate",
navigation_path: "fans",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {FanChip};
-43
View File
@@ -1,43 +0,0 @@
import {Helper} from "../Helper";
class LightChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:lightbulb-group",
icon_color: "amber",
content: Helper.getCountTemplate("light", "eq", "on"),
tap_action: {
action: "call-service",
service: "light.turn_off",
target: {
area_id: this.#areaIds,
},
data: {},
},
hold_action: {
action: "navigate",
navigation_path: "lights",
},
};
}
}
export {LightChip};
+48
View File
@@ -0,0 +1,48 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Light Chip class.
*
* Used to create a chip to indicate how many lights are on and to turn all off.
*/
class LightChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:lightbulb-group",
icon_color: "amber",
content: Helper.getCountTemplate("light", "eq", "on"),
tap_action: {
action: "call-service",
service: "light.turn_off",
},
hold_action: {
action: "navigate",
navigation_path: "lights",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {LightChip};
-43
View File
@@ -1,43 +0,0 @@
import {Helper} from "../Helper";
class SwitchChip {
#areaIds;
#options = {
// No default options.
};
constructor(areaIds, options = {}) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
this.#areaIds = areaIds.filter(areaId => areaId);
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "template",
icon: "mdi:dip-switch",
icon_color: "blue",
content: Helper.getCountTemplate("switch", "eq", "on"),
tap_action: {
action: "call-service",
service: "switch.turn_off",
target: {
area_id: this.#areaIds,
},
data: {},
},
hold_action: {
action: "navigate",
navigation_path: "switches",
},
};
}
}
export {SwitchChip};
+48
View File
@@ -0,0 +1,48 @@
import {Helper} from "../Helper";
import {chips} from "../types/strategy/chips";
import {AbstractChip} from "./AbstractChip";
import {TemplateChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Switch Chip class.
*
* Used to create a chip to indicate how many switches are on and to turn all off.
*/
class SwitchChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @type {TemplateChipConfig}
*
* @readonly
* @private
*/
readonly #defaultConfig: TemplateChipConfig = {
type: "template",
icon: "mdi:dip-switch",
icon_color: "blue",
content: Helper.getCountTemplate("switch", "eq", "on"),
tap_action: {
action: "call-service",
service: "switch.turn_off",
},
hold_action: {
action: "navigate",
navigation_path: "switches",
},
};
/**
* Class Constructor.
*
* @param {chips.TemplateChipOptions} options The chip options.
*/
constructor(options: chips.TemplateChipOptions = {}) {
super();
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {SwitchChip};
-25
View File
@@ -1,25 +0,0 @@
class WeatherChip {
#entityId;
#options = {
show_temperature: true,
show_conditions: true,
};
constructor(entityId, options = {}) {
this.#entityId = entityId;
this.#options = {
...this.#options,
...options,
};
}
getChip() {
return {
type: "weather",
entity: this.#entityId,
...this.#options,
};
}
}
export {WeatherChip};
+42
View File
@@ -0,0 +1,42 @@
import {chips} from "../types/strategy/chips";
import {WeatherChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
import {AbstractChip} from "./AbstractChip";
// noinspection JSUnusedGlobalSymbols False positive.
/**
* Weather Chip class.
*
* Used to create a chip for showing the weather.
*/
class WeatherChip extends AbstractChip {
/**
* Default configuration of the chip.
*
* @private
* @readonly
*/
readonly #defaultConfig: WeatherChipConfig = {
type: "weather",
show_temperature: true,
show_conditions: true,
};
/**
* Class Constructor.
*
* @param {string} entityId Id of a weather entity.
* @param {chips.WeatherChipOptions} options Weather Chip options.
*/
constructor(entityId: string, options: chips.WeatherChipOptions = {}) {
super();
this.#defaultConfig = {
...this.#defaultConfig,
...{entity: entityId},
...options,
};
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
}
export {WeatherChip};
@@ -1,44 +1,20 @@
export const optionDefaults = {
debug: false,
views: {
home: {
order: 1,
hidden: false,
},
light: {
order: 2,
hidden: false,
},
fan: {
order: 3,
hidden: false,
},
cover: {
order: 4,
hidden: false,
},
switch: {
order: 5,
hidden: false,
},
climate: {
order: 6,
hidden: false,
},
camera: {
order: 7,
hidden: false,
}
},
import {generic} from "./types/strategy/generic";
import StrategyDefaults = generic.StrategyDefaults;
/**
* Default configuration for the mushroom strategy.
*/
export const configurationDefaults: StrategyDefaults = {
areas: {
undisclosed: {
aliases: [],
area_id: null,
area_id: "undisclosed",
name: "Undisclosed",
picture: null,
hidden: false,
}
},
debug: false,
domains: {
default: {
title: "Miscellaneous",
@@ -116,5 +92,47 @@ export const optionDefaults = {
showControls: false,
hidden: false,
},
vacuum: {
title: "Vacuums",
showControls: true,
hidden: false,
},
},
home_view: {
hidden: [],
},
views: {
home: {
order: 1,
hidden: false,
},
light: {
order: 2,
hidden: false,
},
fan: {
order: 3,
hidden: false,
},
cover: {
order: 4,
hidden: false,
},
switch: {
order: 5,
hidden: false,
},
climate: {
order: 6,
hidden: false,
},
camera: {
order: 7,
hidden: false,
},
vacuum: {
order: 8,
hidden: false,
},
}
};
@@ -1,34 +1,40 @@
import {Helper} from "./Helper";
import {SensorCard} from "./cards/SensorCard";
import {TitleCard} from "./cards/TitleCard";
import {ControllerCard} from "./cards/ControllerCard";
import {generic} from "./types/strategy/generic";
import {LovelaceCardConfig, LovelaceConfig, LovelaceViewConfig} from "./types/homeassistant/data/lovelace";
import {StackCardConfig} from "./types/homeassistant/lovelace/cards/types";
import {EntityCardConfig} from "./types/lovelace-mushroom/cards/entity-card-config";
import {HassServiceTarget} from "home-assistant-js-websocket";
import StrategyArea = generic.StrategyArea;
/**
* 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>
* The strategy makes use Mushroom and Mini Graph cards to represent your entities.<br>
* <br>
* Features:<br>
* 🛠 Automatically create dashboard with 3 lines of yaml.<br>
* 🛠 Automatically create dashboard with three 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 {
class MushroomStrategy extends HTMLTemplateElement {
/**
* Generate a dashboard.
*
* Called when opening a dashboard.
*
* @param {dashBoardInfo} info Dashboard strategy information object.
* @return {Promise<{views: Object[]}>}
* @param {generic.DashBoardInfo} info Dashboard strategy information object.
* @return {Promise<LovelaceConfig>}
*/
static async generateDashboard(info) {
static async generateDashboard(info: generic.DashBoardInfo): Promise<LovelaceConfig> {
await Helper.initialize(info);
// Create views.
const views = [];
const views: LovelaceViewConfig[] = info.config?.views ?? [];
let viewModule;
@@ -36,13 +42,14 @@ class MushroomStrategy {
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);
viewModule = await import(`./views/${viewType}`);
const view: LovelaceViewConfig = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView();
if (view.cards?.length) {
views.push(view);
}
} catch (e) {
console.error(Helper.debug ? e : `View '${viewId}' couldn't be loaded!`);
Helper.logError(`View '${viewId}' couldn't be loaded!`, e);
}
}
@@ -79,13 +86,18 @@ class MushroomStrategy {
*
* Called when opening a subview.
*
* @param {viewInfo} info The view's strategy information object.
* @return {Promise<{cards: Object[]}>}
* @param {generic.ViewInfo} info The view's strategy information object.
* @return {Promise<LovelaceViewConfig>}
*/
static async generateView(info) {
static async generateView(info: generic.ViewInfo): Promise<LovelaceViewConfig> {
const exposedDomainIds = Helper.getExposedDomainIds();
const area = info.view.strategy.options.area;
const viewCards = [...(area.extra_cards ?? [])];
const area = info.view.strategy?.options?.area ?? {} as StrategyArea;
const viewCards: LovelaceCardConfig[] = [...(area.extra_cards ?? [])];
// Set the target for controller cards to the current area.
let target: HassServiceTarget = {
area_id: [area.area_id],
};
// Create cards for each domain.
for (const domain of exposedDomainIds) {
@@ -100,27 +112,34 @@ class MushroomStrategy {
try {
domainCards = await import(`./cards/${className}`).then(cardModule => {
let domainCards = [];
const entities = Helper.getDeviceEntities(area, domain);
const entities = Helper.getDeviceEntities(area, domain);
// Set the target for controller cards to entities without an area.
if (area.area_id === "undisclosed") {
target = {
entity_id: entities.map(entity => entity.entity_id),
}
}
if (entities.length) {
// Create a Title card for the current domain.
const titleCard = new TitleCard(
[area],
Helper.strategyOptions.domains[domain],
// Create a Controller card for the current domain.
const titleCard = new ControllerCard(
target,
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 = [];
const sensorCards: EntityCardConfig[] = [];
for (const sensor of entities) {
// Find the state of the current sensor.
const sensorState = sensorStates.find(state => state.entity_id === sensor.entity_id);
let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[sensor.device_id] ?? {};
let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[sensor.device_id ?? "null"];
if (!cardOptions.hidden && !deviceOptions.hidden) {
if (!cardOptions?.hidden && !deviceOptions?.hidden) {
if (sensorState?.attributes.unit_of_measurement) {
cardOptions = {
...{
@@ -147,12 +166,16 @@ class MushroomStrategy {
return domainCards;
}
// Create a card for each domain-entity of the current area.
// Create a card for each other domain-entity of the current area.
for (const entity of entities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
let deviceOptions;
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
if (!cardOptions.hidden && !deviceOptions.hidden) {
if (entity.device_id) {
deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id];
}
if (!cardOptions?.hidden && !deviceOptions?.hidden) {
domainCards.push(new cardModule[className](entity, cardOptions).getCard());
}
}
@@ -179,7 +202,7 @@ class MushroomStrategy {
return domainCards;
});
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
Helper.logError("An error occurred while creating the domain cards!", e);
}
if (domainCards.length) {
@@ -193,36 +216,37 @@ class MushroomStrategy {
if (!Helper.strategyOptions.domains.default.hidden) {
// 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);
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,
// 1. The entity is not hidden.
// 2. The entity's domain isn't exposed (entities of exposed domains are already included).
// 3. 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]);
const miscellaneousEntities = Helper.entities.filter((entity) => {
const entityLinked = areaDevices.includes(entity.device_id ?? "null") || entity.area_id === area.area_id;
const entityUnhidden = entity.hidden_by === null && entity.disabled_by === null;
const domainExposed = exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]);
return entityUnhidden && !domainExposed && entityLinked;
});
// Create a column of miscellaneous entity cards.
if (miscellaneousEntities.length) {
let miscellaneousCards = [];
let miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [];
try {
miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => {
/** @type Object[] */
const miscellaneousCards = [
new TitleCard([area], Helper.strategyOptions.domains.default).createCard(),
const miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [
new ControllerCard(target, Helper.strategyOptions.domains.default).createCard(),
];
for (const entity of miscellaneousEntities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"];
if (!cardOptions.hidden && !deviceOptions.hidden) {
if (!cardOptions?.hidden && !deviceOptions?.hidden) {
miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard());
}
}
@@ -230,7 +254,7 @@ class MushroomStrategy {
return miscellaneousCards;
});
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while creating the domain cards!");
Helper.logError("An error occurred while creating the domain cards!", e);
}
viewCards.push({
@@ -247,5 +271,10 @@ class MushroomStrategy {
}
}
// noinspection JSUnresolvedReference
customElements.define("ll-strategy-mushroom-strategy", MushroomStrategy);
const version = "v2.0.0";
console.info(
"%c Mushroom Strategy %c ".concat(version, " "),
"color: white; background: coral; font-weight: 700;", "color: coral; background: white; font-weight: 700;"
);
-255
View File
@@ -1,255 +0,0 @@
/**
* @namespace typedefs.generic
*/
/**
* @typedef {Object} hassEntity Home assistant entity.
* @property {string} name The name of this entity.
* @property {string} original_name The original name of this entity.
* @property {string} entity_id The id of this entity.
* @property {string} device_id The id of the device to which this entity is linked.
* @property {string} area_id The id of the area to which this entity is linked.
* @property {string[]|null} disabled_by Indicates by what this entity is disabled.
* @property {string[]|null} hidden_by Indicates by what this entity is hidden.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} deviceEntity Device Entity.
* @property {string} area_id The Area which the device is placed in.
* @property {string} id Unique ID of a device (generated by Home Assistant).
* @property {string[]|null} disabled_by Indicates by what this entity is disabled.
* @property {string[]|null} hidden_by Indicates by what this entity is hidden.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} areaEntity Area Entity.
* @property {string[]} [aliases] Array of aliases of this entity.
* @property {string|null} area_id The id of this entity.
* @property {string} name Name of this entity.
* @property {string|null} picture URL to a picture that should be used instead of showing the domain icon.
* @property {number} [order] Ordering position of the area in the list of available areas.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* This property is added by the custom strategy.
* @property {Object[]} [extra_cards] An array of card configurations.
* The configured cards are added to the dashboard.
* 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
*/
/**
* @typedef {Object} viewEntity View Entity.
* This entity is added by the custom strategy.
* @property {string} title Title of this entity.
* @property {string} icon Icon to use for the entity in the frontend.
* Example: `mdi:home`.
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object & titleCardOptions} domainEntity Domain Entity.
* This entity is added by the custom strategy.
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} titleCardOptions Title Card options.
* @property {string} [title] Title to render. May contain templates.
* @property {string} [subtitle] Subtitle to render. May contain templates.
* @property {boolean} [showControls=true] False to hide controls.
* @property {string} [iconOn] Icon to show for switching entities from off state.
* @property {string} [iconOff] Icon to show for switching entities to off state.
* @property {string} [onService=none] Service to call for switching entities from off state.
* @property {string} [offService=none] Service to call for switching entities to off state.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} dashBoardInfo Strategy information object.
* @property {dashboardConfig} config User supplied dashboard configuration, if any.
* @property {hassObject} hass The Home Assistant object.
* @property {boolean} narrow If the current user interface is rendered in narrow mode or not.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies
*/
/**
* @typedef {Object} viewInfo Strategy information object.
* @property {Object} view Configuration of the current view.
* @property {viewConfig} config Dashboard configuration.
* @property {hassObject} hass The Home Assistant object.
* @property {boolean} narrow If the current user interface is rendered in narrow mode or not.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies
*/
/**
* @typedef {Object} dashboardConfig User supplied dashboard configuration.
* @property {strategyObject} strategy User supplied dashboard configuration.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} viewConfig Dashboard configuration.
* @property {Object[]} strategy Array of views generated by the strategy.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} strategyObject User supplied dashboard configuration.
* @property {strategyOptions} options Custom strategy configuration.
* @property {string} type Strategy type.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} customStrategyOptions Custom strategy configuration.
* @property {boolean} [debug] Set to true for more verbose debugging info.
* @property {Object.<areaEntity>} [areas] List of areas.
* @property {Object.<cardOptions>} [card_options] Card options for entities.
* @property {Object.<viewEntity>} [views] List of views.
* @property {Object.<domainEntity>} [domains] List of domains.
* @property {chip[]} [chips] List of chips to show in the Home view.
* @property {Object[]} [quick_access_cards] List of cards to show between welcome card and rooms cards.
* @property {Object[]} [extra_cards] List of cards to show below room cards.
* @property {Object[]} [extra_views] List of views to add to the dashboard.
* @property {Object.<homeViewOptions>} [homeView] Options for the home view.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} chip List of chips to show in the Home view.
* @property {boolean} light_count Chip to display the number of lights on.
* @property {boolean} fan_count Chip to display the number of fans on.
* @property {boolean} cover_count Chip to display the number of unclosed covers.
* @property {boolean} switch_count Chip to display the number of switches on.
* @property {boolean} climate_count Chip to display the number of climates which are not off.
* @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only.
* @property {Object[]} extra_chips List of extra chips.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} homeViewOptions Options for the home view.
* @property {string[]} [hidden] Elements to hide from the home view.
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} cardOptions Custom card-configuration for an entity.
* @property {string} type Type of card for the entity
* @property {boolean} hidden True if the entity should be hidden from the dashboard.
* @memberOf typedefs.generic
*/
/**
* The frontend passes a single hass object around.
* This object contains the latest state and allows you to send commands back to the server.
*
* @typedef {Object} hassObject Home Assistant object.
* @property {Object<string, stateObject>} states An object containing the states of all entities in Home Assistant.
* The key is the entity_id, the value is the state object.
* @property {hassUser} user The logged-in user.
* @property {function} callWS Call a WebSocket command on the backend.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/data/
*/
/**
* The logged-in user.
*
* @typedef {Object} hassUser The logged-in user.
* @property {string} name Name of the user.
* @property {boolean} is_owner True if the user is the owner.
* @property {boolean} is_owner True if the user is an administrator.
* @property {Object[]} credentials Authentication credentials.
* @memberOf typedefs.generic
* @see https://developers.home-assistant.io/docs/frontend/data/#hassuser
*/
/**
* States are a current representation of the entity.
*
* All states will always have an entity id, a state and a timestamp when last updated and last changed.
*
* @typedef {Object} stateObject State object.
* @property {string} state String representation of the entity's current state.
* Example `off`.
* @property {string} entity_id Entity ID.
* Format: <domain>.<object_id>.
* Example: `light.kitchen`.
* @property {string} domain Domain of the entity.
* Example: `light`.
* @property {string} object_id Object ID of entity.
* Example: `kitchen`.
* @property {string} name Name of the entity.
* Based on `friendly_name` attribute with fall-back to object ID.
* Example: `Kitchen Ceiling`.
* @property {string} last_updated Time the state was written to the state machine in UTC time.
* Note that writing the exact same state including attributes will not result in this
* field being updated.
* Example: `2017-10-28 08:13:36.715874+00:00`.
* @property {string} last_changed Time the state changed in the state machine in UTC time.
* This is not updated when there are only updated attributes.
* Example: `2017-10-28 08:13:36.715874+00:00`.
* @property {stateAttributes} attributes A dictionary with extra attributes related to the current state.
* @property {stateContext} context A dictionary with extra attributes related to the context of the state.
* @memberOf typedefs.generic
* @see https://www.home-assistant.io/docs/configuration/state_object/
*/
/**
* The attributes of an entity are optional.
*
* There are a few attributes that are used by Home Assistant for representing the entity in a specific way.
* Each integration will also have its own attributes to represent extra state data about the entity.
* For example, the light integration has attributes for the current brightness and color of the light.
*
* When an attribute is not available, Home Assistant will not write it to the state.
*
* @typedef {Object} stateAttributes State attributes.
* @property {string} friendly_name Name of the entity.
* Example: `Kitchen Ceiling`.
* @property {string} icon Icon to use for the entity in the frontend.
* Example: `mdi:home`.
* @property {string} entity_picture URL to a picture that should be used instead of showing the domain icon.
* @property {string} assumed_state Boolean if the current state is an assumption.
* @property {string} unit_of_measurement The unit of measurement the state is expressed in.
* Used for grouping graphs or understanding the entity.
* Example: `°C`.
* @memberOf typedefs.generic
* @see https://www.home-assistant.io/docs/configuration/state_object/#attributes
*/
/**
* Context is used to tie events and states together in Home Assistant. Whenever an automation or user interaction
* causes states to change, a new context is assigned. This context will be attached to all events and states that
* happen as a result of the change.
*
* @typedef {Object} stateContext State context.
* @property {string} context_id Unique identifier for the context.
* @property {string} user_id Unique identifier of the user that started the change.
* Will be None if action was not started by a user (i.e., started by an automation)
* @property {string} parent_id Unique identifier of the parent context that started the change, if available.
* For example, if an automation is triggered, the context of the trigger will be set as
* parent.
* @see https://www.home-assistant.io/docs/configuration/state_object/#context
* @memberOf typedefs.generic
*/
/**
* @typedef {Object} areaFilterContext fer Card options.
* @property {areaEntity} area Area Entity.
* @property {string} domain Domain of the entity.
* Example: `light`.
* @property {string[]} areaDeviceIds The id of devices which are linked to the area entity.
* @memberOf typedefs.cards
*/
export {};
+22
View File
@@ -0,0 +1,22 @@
This directory contains partial code from
the [Home Assistant Frontend repository](https://github.com/home-assistant/frontend).
The code mainly defines Home Assistant interfaces/types which are refactored to fit this repository.
This means properties are added/removed from the originals and subtypes may have been changed.
The [Apache 2.0 License](https://github.com/home-assistant/frontend/blob/dev/LICENSE.md) applies to all files in this
directory.
Copyright 2023 Ferry Cools
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,14 @@
/**
* Area Entity.
*
* @property {string} area_id The id of the area.
* @property {string} name Name of the area.
* @property {string|null} picture URL to a picture that should be used instead of showing the domain icon.
* @property {string[]} aliases Array of aliases of the area.
*/
export interface AreaRegistryEntry {
area_id: string;
name: string;
picture: string | null;
aliases: string[];
}
+19
View File
@@ -0,0 +1,19 @@
export const HVAC_MODES = [
"auto",
"heat_cool",
"heat",
"cool",
"dry",
"fan_only",
"off",
] as const;
export type HvacMode = (typeof HVAC_MODES)[number];
HVAC_MODES.reduce(
(order, mode, index) => {
order[mode] = index;
return order;
},
{} as Record<HvacMode, number>
);
@@ -0,0 +1,38 @@
/**
* Device Entity.
*
* @property {string} id Unique ID of a device (generated by Home Assistant).
* @property {string[]} config_entries
* @property {Array} connections
* @property {Array} identifiers
* @property {string | null} manufacturer
* @property {string | null} model
* @property {string | null} name
* @property {string | null} sw_version
* @property {string | null} hw_version
* @property {string | null} serial_number
* @property {string | null} via_device_id
* @property {string} area_id The Area which the device is placed in.
* @property {string | null} name_by_user
* @property {string[] | null} entry_type
* @property {string | null} disabled_by Indicates by what this entity is disabled.
* @property {string | null} configuration_url
*/
export interface DeviceRegistryEntry {
id: string;
config_entries: string[];
connections: Array<[string, string]>;
identifiers: Array<[string, string]>;
manufacturer: string | null;
model: string | null;
name: string | null;
sw_version: string | null;
hw_version: string | null;
serial_number: string | null;
via_device_id: string | null;
area_id: string | null;
name_by_user: string | null;
entry_type: "service" | null;
disabled_by: "user" | "integration" | "config_entry" | null;
configuration_url: string | null;
}
@@ -0,0 +1,97 @@
import {LightColor} from "./light";
type EntityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry {
entity_id: string;
name?: string;
device_id?: string;
area_id?: string;
hidden?: boolean;
entity_category?: EntityCategory;
translation_key?: string;
platform?: string;
display_precision?: number;
}
/**
* Home assistant entity.
*
* @property {string} id
* @property {string} entity_id The id of this entity.
* @property {string} name The name of this entity.
* @property {string | null} icon
* @property {string | null} platform
* @property {string | null} config_entry_id
* @property {string | null} device_id The id of the device to which this entity is linked.
* @property {string | null} area_id The id of the area to which this entity is linked.
* @property {string | null} disabled_by Indicates by what this entity is disabled.
* @property {Object} hidden_by Indicates by what this entity is hidden.
* @property {EntityCategory | null} entity_category
* @property {boolean} has_entity_name
* @property {string} [original_name]
* @property {string} unique_id
* @property {string} [translation_key]
* @property {EntityRegistryOptions | null} options
*/
export interface EntityRegistryEntry {
id: string;
entity_id: string;
name: string | null;
icon: string | null;
platform: string;
config_entry_id: string | null;
device_id: string | null;
area_id: string | null;
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: EntityCategory | null;
has_entity_name: boolean;
original_name?: string;
unique_id: string;
translation_key?: string;
options: EntityRegistryOptions | null;
}
export interface SensorEntityOptions {
display_precision?: number | null;
suggested_display_precision?: number | null;
unit_of_measurement?: string | null;
}
export interface LightEntityOptions {
favorite_colors?: LightColor[];
}
export interface NumberEntityOptions {
unit_of_measurement?: string | null;
}
export interface LockEntityOptions {
default_code?: string | null;
}
export interface WeatherEntityOptions {
precipitation_unit?: string | null;
pressure_unit?: string | null;
temperature_unit?: string | null;
visibility_unit?: string | null;
wind_speed_unit?: string | null;
}
export interface SwitchAsXEntityOptions {
entity_id: string;
}
export interface EntityRegistryOptions {
number?: NumberEntityOptions;
sensor?: SensorEntityOptions;
lock?: LockEntityOptions;
weather?: WeatherEntityOptions;
light?: LightEntityOptions;
switch_as_x?: SwitchAsXEntityOptions;
conversation?: Record<string, unknown>;
"cloud.alexa"?: Record<string, unknown>;
"cloud.google_assistant"?: Record<string, unknown>;
}
+6
View File
@@ -0,0 +1,6 @@
export type LightColor =
| { color_temp_kelvin: number; }
| { hs_color: [number, number]; }
| { rgb_color: [number, number, number]; }
| { rgbw_color: [number, number, number, number]; }
| { rgbww_color: [number, number, number, number, number]; };
+118
View File
@@ -0,0 +1,118 @@
import {HassServiceTarget} from "home-assistant-js-websocket";
export type LovelaceStrategyConfig = {
type: string;
[key: string]: any;
};
export interface LovelaceConfig {
title?: string;
strategy?: LovelaceStrategyConfig;
views: LovelaceViewConfig[];
background?: string;
}
/**
* View Config.
*
* @see https://www.home-assistant.io/dashboards/views/
*/
export interface LovelaceViewConfig {
index?: number;
title?: string;
type?: string;
strategy?: LovelaceStrategyConfig;
badges?: Array<string | LovelaceBadgeConfig>;
cards?: LovelaceCardConfig[];
path?: string;
icon?: string;
theme?: string;
panel?: boolean;
background?: string;
visible?: boolean | ShowViewConfig[];
subview?: boolean;
back_path?: string;
}
export interface ShowViewConfig {
user?: string;
}
export interface LovelaceBadgeConfig {
type?: string;
[key: string]: any;
}
export interface LovelaceCardConfig {
index?: number;
view_index?: number;
view_layout?: any;
type: string;
[key: string]: any;
}
export interface ToggleActionConfig extends BaseActionConfig {
action: "toggle";
}
export interface CallServiceActionConfig extends BaseActionConfig {
action: "call-service";
service: string;
target?: HassServiceTarget;
// Property "service_data" is kept for backwards compatibility. Replaced by "data".
service_data?: Record<string, unknown>;
data?: Record<string, unknown>;
}
export interface NavigateActionConfig extends BaseActionConfig {
action: "navigate";
navigation_path: string;
navigation_replace?: boolean;
}
export interface UrlActionConfig extends BaseActionConfig {
action: "url";
url_path: string;
}
export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info";
}
export interface AssistActionConfig extends BaseActionConfig {
action: "assist";
pipeline_id?: string;
start_listening?: boolean;
}
export interface NoActionConfig extends BaseActionConfig {
action: "none";
}
export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
export interface BaseActionConfig {
action: string;
confirmation?: ConfirmationRestrictionConfig;
}
export interface ConfirmationRestrictionConfig {
text?: string;
exemptions?: RestrictionConfig[];
}
export interface RestrictionConfig {
user: string;
}
export type ActionConfig =
| ToggleActionConfig
| CallServiceActionConfig
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| AssistActionConfig
| NoActionConfig
| CustomActionConfig;
@@ -0,0 +1,26 @@
import {LovelaceCardConfig} from "../../data/lovelace";
/**
* Home Assistant Stack Card Config.
*
* @property {string} type The stack type.
* @property {Object[]} cards The content of the stack.
*
* @see https://www.home-assistant.io/dashboards/horizontal-stack/
* @see https://www.home-assistant.io/dashboards/vertical-stack/
*/
export interface StackCardConfig extends LovelaceCardConfig {
cards: LovelaceCardConfig[];
title?: string;
}
/**
* Home Assistant Area Card Config.
*
* @see https://www.home-assistant.io/dashboards/area/
*/
export interface AreaCardConfig extends LovelaceCardConfig {
area: string;
navigation_path?: string;
show_camera?: boolean;
}
@@ -0,0 +1,41 @@
import {ActionConfig, LovelaceCardConfig} from "../../../data/lovelace";
/**
* Home Assistant Picture Entity Config.
*
* @property {string} entity An entity_id used for the picture.
* @property {string} [name] Overwrite entity name.
* @property {string} [image] URL of an image.
* @property {string} [camera_image] Camera entity_id to use. (not required if entity is already a camera-entity).
* @property {string} [camera_view=auto] “live” will show the live view if stream is enabled.
* @property {Record<string, unknown>} [state_image] Map entity states to images (state: image URL).
* @property {string[]} [state_filter] State-based CSS filters.
* @property {string} [aspect_ratio] Forces the height of the image to be a ratio of the width.
* Valid formats: Height percentage value (23%) or ratio expressed with colon or “x”
* separator (16:9 or 16x9).
* For a ratio, the second element can be omitted and will default to “1”
* (1.78 equals 1.78:1).
* @property {ActionConfig} [tap_action] Action taken on card tap.
* @property {ActionConfig} [hold_action] Action taken on card tap and hold.
* @property {ActionConfig} [double_tap_action] Action taken on card double tap.
* @property {boolean} [show_name=true] Shows name in footer.
* @property {string} [theme=true] Override the used theme for this card with any loaded theme.
*
* @see https://www.home-assistant.io/dashboards/picture-entity/
*/
export interface PictureEntityCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
image?: string;
camera_image?: string;
camera_view?: "live" | "auto";
state_image?: Record<string, unknown>;
state_filter?: string[];
aspect_ratio?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
show_name?: boolean;
show_state?: boolean;
theme?: string;
}
+95
View File
@@ -0,0 +1,95 @@
import {Auth, Connection, HassConfig, HassEntities, HassServices, MessageBase,} from "home-assistant-js-websocket";
import {AreaRegistryEntry} from "./data/area_registry";
import {DeviceRegistryEntry} from "./data/device_registry";
import {EntityRegistryDisplayEntry} from "./data/entity_registry";
export interface Credential {
auth_provider_type: string;
auth_provider_id: string;
}
export interface MFAModule {
id: string;
name: string;
enabled: boolean;
}
export interface CurrentUser {
id: string;
is_owner: boolean;
is_admin: boolean;
name: string;
credentials: Credential[];
mfa_modules: MFAModule[];
}
export interface PanelInfo<T = Record<string, any> | null> {
component_name: string;
config: T;
icon: string | null;
title: string | null;
url_path: string;
config_panel_domain?: string;
}
export interface Panels {
[name: string]: PanelInfo;
}
export interface Translation {
nativeName: string;
isRTL: boolean;
hash: string;
}
export interface TranslationMetadata {
fragments: string[];
translations: {
[lang: string]: Translation;
};
}
export interface Resources {
[language: string]: Record<string, string>;
}
export interface HomeAssistant {
auth: Auth & { external?: any };
connection: Connection;
connected: boolean;
states: HassEntities;
entities: { [id: string]: EntityRegistryDisplayEntry };
devices: { [id: string]: DeviceRegistryEntry };
areas: { [id: string]: AreaRegistryEntry };
services: HassServices;
config: HassConfig;
themes: { [k: string]: any };
selectedTheme: { [k: string]: any } | null;
panels: Panels;
panelUrl: string;
// i18n
// current effective language in that order:
// - backend saved user selected language
// - language in local app storage
// - browser language
// - english (en)
language: string;
// local stored language, keep that name for backward compatibility
selectedLanguage: string | null;
locale: { [k: string]: any };
resources: Resources;
localize: Function;
translationMetadata: TranslationMetadata;
suspendWhenHidden: boolean;
enableShortcuts: boolean;
vibrate: boolean;
debugConnection: boolean;
dockedSidebar: "docked" | "always_hidden" | "auto";
defaultPanel: string;
moreInfoEntityId: string | null;
user?: CurrentUser;
userData?: { [k: string]: any } | null;
callWS<T>(msg: MessageBase): Promise<T>;
}
+22
View File
@@ -0,0 +1,22 @@
This directory contains partial code from
the [Lovelace Mushroom repository](https://github.com/piitaya/lovelace-mushroom).
The code mainly defines Home Assistant interfaces/types which are refactored to fit this repository.
This means properties are added/removed from the originals and subtypes may have been changed.
The [Apache 2.0 License](https://github.com/home-assistant/frontend/blob/dev/LICENSE.md) applies to all files in this
directory.
Copyright 2023 Ferry Cools
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,15 @@
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {LovelaceChipConfig} from "../utils/lovelace/chip/types";
/**
* Chips Card Configuration
*
* @param {LovelaceChipConfig[]} chips Chips Array
* @param {string} [alignment=start] Chips alignment (end, center, justify), when empty default behavior is start.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md
*/
export interface ChipsCardConfig extends LovelaceCardConfig {
chips: LovelaceChipConfig[];
alignment?: string;
}
@@ -0,0 +1,23 @@
import {HvacMode} from "../../homeassistant/data/climate";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
import {ActionsSharedConfig} from "../shared/config/actions-config";
/**
* Climate Card Config.
*
* @property {boolean} [show_temperature_control=false] Show buttons to control target temperature.
* @property {HvacMode[]} [hvac_modes] List of hvac modes to display (auto, heat_cool, heat, cool, dry, fan_only, off).
* @property {boolean} [collapsible_controls] Collapse controls when off.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/climate.md
*/
export type ClimateCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
show_temperature_control?: boolean;
hvac_modes?: HvacMode[];
collapsible_controls?: boolean;
};
@@ -0,0 +1,22 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Cover Card Config.
*
* @property {boolean} [show_buttons_control=false] Show buttons to open, close and stop cover.
* @property {boolean} [show_position_control=false] Show a slider to control position of the cover.
* @property {boolean} [show_tilt_position_control=false] Show a slider to control tilt position of the cover.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/cover.md
*/
export type CoverCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
show_buttons_control?: boolean;
show_position_control?: boolean;
show_tilt_position_control?: boolean;
};
@@ -0,0 +1,18 @@
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {ActionsSharedConfig} from "../shared/config/actions-config";
/**
* Entity Card Config.
*
* @property {string} [icon_color=blue] Custom color for icon when entity is state is active.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/entity.md
*/
export type EntityCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_color?: string;
};
@@ -0,0 +1,24 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Fan Card Config.
*
* @property {boolean} [icon_animation=false] Animate the icon when fan is on.
* @property {boolean} [show_percentage_control=false] Show a slider to control speed.
* @property {boolean} [show_oscillate_control=false] Show a button to control oscillation.
* @property {boolean} [icon_animation=false] Animate the icon when fan is on.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/fan.md
*/
export type FanCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_animation?: boolean;
show_percentage_control?: boolean;
show_oscillate_control?: boolean;
collapsible_controls?: boolean;
};
@@ -0,0 +1,29 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Light Card Config.
*
* @property {string} [icon_color=blue] Custom color for icon and brightness bar when the lights are on and
* use_light_color is false.
* @property {boolean} [show_brightness_control=false] Show a slider to control brightness.
* @property {boolean} [show_color_temp_control=false] Show a slider to control temperature color.
* @property {boolean} [show_color_control=false] Show a slider to control RGB color.
* @property {boolean} [collapsible_controls=false] Collapse controls when off.
* @property {boolean} [use_light_color=false] Colorize the icon and slider according light temperature or color.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/light.md
*/
export type LightCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_color?: string;
show_brightness_control?: boolean;
show_color_temp_control?: boolean;
show_color_control?: boolean;
collapsible_controls?: boolean;
use_light_color?: boolean;
};
@@ -0,0 +1,14 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Lock Card Config.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/lock.md
*/
export type LockCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig;
@@ -0,0 +1,45 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
export const MEDIA_LAYER_MEDIA_CONTROLS = [
"on_off",
"shuffle",
"previous",
"play_pause_stop",
"next",
"repeat",
] as const;
export type MediaPlayerMediaControl = (typeof MEDIA_LAYER_MEDIA_CONTROLS)[number];
export const MEDIA_PLAYER_VOLUME_CONTROLS = [
"volume_mute",
"volume_set",
"volume_buttons",
] as const;
export type MediaPlayerVolumeControl = (typeof MEDIA_PLAYER_VOLUME_CONTROLS)[number];
/**
* Media Player Card Config.
*
* @property {boolean} [use_media_info=false] Use media info instead of name, state, and icon when media is playing.
* @property {boolean} [show_volume_level=false] Show volume level next to media state when media is playing.
* @property {MediaPlayerVolumeControl[]} [volume_controls] List of controls to display (volume_mute, volume_set, volume_buttons)
* @property {MediaPlayerMediaControl[]} [media_controls] List of controls to display (on_off, shuffle, previous, play_pause_stop, next, repeat)
* @property {boolean} [collapsible_controls=false] Collapse controls when off
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/media-player.md
*/
export type MediaPlayerCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
use_media_info?: boolean;
show_volume_level?: boolean;
volume_controls?: MediaPlayerVolumeControl[];
media_controls?: MediaPlayerMediaControl[];
collapsible_controls?: boolean;
};
@@ -0,0 +1,24 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
export const DISPLAY_MODES = ["slider", "buttons"] as const;
type DisplayMode = (typeof DISPLAY_MODES)[number];
/**
* Number Card Config.
*
* @property {string} [icon_color=blue] Custom color for icon when entity state is active.
* @property {DisplayMode} [display_mode=slider] Slider or Button controls.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/number.md
*/
export type NumberCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_color?: string;
display_mode?: DisplayMode;
};
@@ -0,0 +1,14 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Person Card Config.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/person.md
*/
export type PersonCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig;
@@ -0,0 +1,36 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
/**
* Template Card Config.
*
* @property {string} [entity]
* @property {string} [icon] Icon to render. May contain templates.
* @property {string} [icon_color] Icon color to render. May contain templates.
* @property {string} [primary] Primary info to render. May contain templates.
* @property {string} [secondary] Secondary info to render. May contain templates.
* @property {string} [badge_icon] Badge icon to render. May contain templates.
* @property {string} [badge_color] Badge icon color to render. May contain templates.
* @property {string} [picture] Picture to render. May contain templates.
* @property {boolean} [multiline_secondary] Enables support for multiline text for the secondary info.
* @property {string | string[]} [entity_id] Only reacts to the state changes of these entities.
* This can be used if the automatic analysis fails to find all relevant
* entities.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/template.md
*/
export type TemplateCardConfig = LovelaceCardConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
entity?: string;
icon?: string;
icon_color?: string;
primary?: string;
secondary?: string;
badge_icon?: string;
badge_color?: string;
picture?: string;
multiline_secondary?: boolean;
entity_id?: string | string[];
};
@@ -0,0 +1,21 @@
import {ActionConfig, LovelaceCardConfig} from "../../homeassistant/data/lovelace";
/**
* Title Card Config.
*
* @property {string} [title] Title to render. May contain templates.
* @property {string} [subtitle] Subtitle to render. May contain templates.
* @property {ActionConfig} [title_tap_action=none] Home assistant action to perform on title tap.
* @property {ActionConfig} [subtitle_tap_action=none] Home assistant action to perform on subtitle tap.
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/title.md
*/
export interface TitleCardConfig extends LovelaceCardConfig {
title?: string;
subtitle?: string;
alignment?: string;
title_tap_action?: ActionConfig;
subtitle_tap_action?: ActionConfig;
}
@@ -0,0 +1,31 @@
import {ActionsSharedConfig} from "../shared/config/actions-config";
import {LovelaceCardConfig} from "../../homeassistant/data/lovelace";
import {EntitySharedConfig} from "../shared/config/entity-config";
import {AppearanceSharedConfig} from "../shared/config/appearance-config";
export const VACUUM_COMMANDS = [
"on_off",
"start_pause",
"stop",
"locate",
"clean_spot",
"return_home",
] as const;
export type VacuumCommand = (typeof VACUUM_COMMANDS)[number];
/**
* Vacuum Card Config.
*
* @param {boolean} icon_animation Animate the icon when vacuum is cleaning.
* @param {VacuumCommand[]} commands List of commands to display (start_pause, stop, locate, clean_spot, return_home).
*
* @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/vacuum.md
*/
export type VacuumCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig & {
icon_animation?: boolean;
commands?: VacuumCommand[];
};
@@ -0,0 +1,7 @@
import {ActionConfig} from "../../../homeassistant/data/lovelace";
export type ActionsSharedConfig = {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
@@ -0,0 +1,13 @@
import {boolean, enums, Infer, object, optional} from "superstruct";
import {layoutStruct} from "./utils/layout";
import {ICON_TYPES, INFOS} from "./utils/info";
export const appearanceSharedConfigStruct = object({
layout: optional(layoutStruct),
fill_container: optional(boolean()),
primary_info: optional(enums(INFOS)),
secondary_info: optional(enums(INFOS)),
icon_type: optional(enums(ICON_TYPES)),
});
export type AppearanceSharedConfig = Infer<typeof appearanceSharedConfigStruct>;
@@ -0,0 +1,9 @@
import {Infer, object, optional, string} from "superstruct";
export const entitySharedConfigStruct = object({
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
});
export type EntitySharedConfig = Infer<typeof entitySharedConfigStruct>;
@@ -0,0 +1,2 @@
export const INFOS = ["name", "state", "last-changed", "last-updated", "none"] as const;
export const ICON_TYPES = ["icon", "entity-picture", "none"] as const;
@@ -0,0 +1,3 @@
import {literal, union} from "superstruct";
export const layoutStruct = union([literal("horizontal"), literal("vertical"), literal("default")]);
@@ -0,0 +1,2 @@
export const INFOS = ["name", "state", "last-changed", "last-updated", "none"] as const;
export type Info = (typeof INFOS)[number];
@@ -0,0 +1,202 @@
import {ActionConfig} from "../../../../homeassistant/data/lovelace";
import {Info} from "../../info";
/**
* Action Chip Config
*
* @property {"action"} type Type of the chip.
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type ActionChipConfig = {
type: "action";
icon?: string;
icon_color?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Alarm Control Panel Chip Config
*
* @property {"alarm-control-panel"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {string} [name] Custom name.
* @property {string} [content_info] Custom content.
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type AlarmControlPanelChipConfig = {
type: "alarm-control-panel";
entity?: string;
name?: string;
content_info?: Info;
icon?: string;
icon_color?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Back Chip Config
*
* @property {"back"} type Type of the chip.
* @property {string} [icon] Custom icon.
*/
export type BackChipConfig = {
type: "back";
icon?: string;
};
/**
* Entity Chip Config
*
* @property {"entity"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {string} [name] Custom name.
* @property {string} [content_info] Custom content.
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {boolean} [use_entity_picture]
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type EntityChipConfig = {
type: "entity";
entity?: string;
name?: string;
content_info?: Info;
icon?: string;
icon_color?: string;
use_entity_picture?: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Menu Chip Config
*
* @property {"menu"} type Type of the chip.
* @property {string} [icon] Custom icon.
*/
export type MenuChipConfig = {
type: "menu";
icon?: string;
};
/**
* Weather Chip Config
*
* @property {"weather"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
* @property {boolean} [show_temperature] Show the temperature.
* @property {boolean} [show_conditions] Show the conditions.
*/
export type WeatherChipConfig = {
type: "weather";
entity?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
show_temperature?: boolean;
show_conditions?: boolean;
};
/**
* Template Chip Config
*
* @property {"template"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
* @property {string} [content]
* @property {string} [icon] Custom icon.
* @property {string} [icon_color] Custom color for icon.
* @property {string} [picture]
* @property {string | string[]} [entity_id]
*/
export type TemplateChipConfig = {
type: "template";
entity?: string;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
content?: string;
icon?: string;
icon_color?: string;
picture?: string;
entity_id?: string | string[];
};
/**
* Conditional Chip Config
*
* @property {"conditional"} type Type of the chip.
* @property {LovelaceChipConfig} [chip] A chip configuration.
* @property {[]} conditions
*/
export interface ConditionalChipConfig {
type: "conditional";
chip?: LovelaceChipConfig;
conditions: any[];
}
/**
* Light Chip Config
*
* @property {"light"} type Type of the chip.
* @property {string} [entity] Entity.
* @property {string} [name]
* @property {Info} [content_info]
* @property {string} [icon] Custom icon.
* @property {boolean} [use_light_color] Colorize the icon and slider according light temperature or color.
* @property {ActionConfig} [tap_action] Home assistant action to perform on tap.
* @property {ActionConfig} [hold_action] Home assistant action to perform on hold.
* @property {ActionConfig} [double_tap_action] Home assistant action to perform on double_tap.
*/
export type LightChipConfig = {
type: "light";
entity?: string;
name?: string;
content_info?: Info;
icon?: string;
use_light_color?: boolean;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
};
/**
* Spacer Chip Config
*
* @property {"spacer"} type Type of the chip.
*/
export type SpacerChipConfig = {
type: "spacer";
};
export type LovelaceChipConfig =
| ActionChipConfig
| AlarmControlPanelChipConfig
| BackChipConfig
| EntityChipConfig
| MenuChipConfig
| WeatherChipConfig
| TemplateChipConfig
| ConditionalChipConfig
| LightChipConfig
| SpacerChipConfig;
+70
View File
@@ -0,0 +1,70 @@
import {LovelaceCardConfig} from "../homeassistant/data/lovelace";
import {TitleCardConfig} from "../lovelace-mushroom/cards/title-card-config";
import {EntitySharedConfig} from "../lovelace-mushroom/shared/config/entity-config";
import {AppearanceSharedConfig} from "../lovelace-mushroom/shared/config/appearance-config";
import {ActionsSharedConfig} from "../lovelace-mushroom/shared/config/actions-config";
import {TemplateCardConfig} from "../lovelace-mushroom/cards/template-card-config";
import {EntityCardConfig} from "../lovelace-mushroom/cards/entity-card-config";
import {PictureEntityCardConfig} from "../homeassistant/panels/lovelave/cards/types";
import {ClimateCardConfig} from "../lovelace-mushroom/cards/climate-card-config";
import {CoverCardConfig} from "../lovelace-mushroom/cards/cover-card-config";
import {FanCardConfig} from "../lovelace-mushroom/cards/fan-card-config";
import {AreaCardConfig} from "../homeassistant/lovelace/cards/types";
import {LightCardConfig} from "../lovelace-mushroom/cards/light-card-config";
import {LockCardConfig} from "../lovelace-mushroom/cards/lock-card-config";
import {MediaPlayerCardConfig} from "../lovelace-mushroom/cards/media-player-card-config";
import {NumberCardConfig} from "../lovelace-mushroom/cards/number-card-config";
import {PersonCardConfig} from "../lovelace-mushroom/cards/person-card-config";
import {VacuumCardConfig} from "../lovelace-mushroom/cards/vacuum-card-config";
export namespace cards {
/**
* Abstract Card Config.
*/
export type AbstractCardConfig = LovelaceCardConfig &
EntitySharedConfig &
AppearanceSharedConfig &
ActionsSharedConfig;
/**
* Controller Card Config.
*
* @property {boolean} [showControls=true] False to hide controls.
* @property {string} [iconOn] Icon to show for switching entities from off state.
* @property {string} [iconOff] Icon to show for switching entities to off state.
* @property {string} [onService=none] Service to call for switching entities from off state.
* @property {string} [offService=none] Service to call for switching entities to off state.
*/
export interface ControllerCardConfig extends TitleCardConfig {
type: "mushroom-title-card",
showControls?: boolean;
iconOn?: string;
iconOff?: string;
onService?: string;
offService?: string;
}
export type AreaCardOptions = Omit<AreaCardConfig, "type">;
export type ClimateCardOptions = Omit<ClimateCardConfig, "type">;
export type ControllerCardOptions = Omit<ControllerCardConfig, "type">;
export type CoverCardOptions = Omit<CoverCardConfig, "type">;
export type EntityCardOptions = Omit<EntityCardConfig, "type">;
export type FanCardOptions = Omit<FanCardConfig, "type">;
export type LightCardOptions = Omit<LightCardConfig, "type">;
export type LockCardOptions = Omit<LockCardConfig, "type">;
export type MediaPlayerCardOptions = Omit<MediaPlayerCardConfig, "type">;
export type NumberCardOptions = Omit<NumberCardConfig, "type">;
export type PersonCardOptions = Omit<PersonCardConfig, "type">;
export type PictureEntityCardOptions = Omit<PictureEntityCardConfig, "type">;
export type TemplateCardOptions = Omit<TemplateCardConfig, "type">;
export type VacuumCardOptions = Omit<VacuumCardConfig, "type">;
}
+6
View File
@@ -0,0 +1,6 @@
import {TemplateChipConfig, WeatherChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types";
export namespace chips {
export type TemplateChipOptions = Omit<TemplateChipConfig, "type">;
export type WeatherChipOptions = Omit<WeatherChipConfig, "type">;
}
+214
View File
@@ -0,0 +1,214 @@
import {
CallServiceActionConfig,
LovelaceCardConfig,
LovelaceConfig,
LovelaceViewConfig
} from "../homeassistant/data/lovelace";
import {HomeAssistant} from "../homeassistant/types";
import {AreaRegistryEntry} from "../homeassistant/data/area_registry";
import {cards} from "./cards";
import {EntityRegistryEntry} from "../homeassistant/data/entity_registry";
import {LovelaceChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types";
import {HassServiceTarget} from "home-assistant-js-websocket";
export namespace generic {
/**
* An entry out of a Home Assistant Register.
*/
export type RegistryEntry =
| AreaRegistryEntry
| DataTransfer
| EntityRegistryEntry
/**
* View Entity.
*
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
*/
export interface ViewConfig extends LovelaceViewConfig {
hidden?: boolean;
order?: number;
}
/**
* Domain Configuration.
*
* @property {number} [order] Ordering position of the entity in the list of available views.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
*/
export interface DomainConfig extends Partial<cards.ControllerCardConfig> {
hidden?: boolean;
order?: number;
}
/**
* Dashboard Information Object.
*
* Home Assistant passes this object to the Dashboard Generator method.
*
* @property {LovelaceConfig} config Dashboard configuration.
* @property {HomeAssistant} hass The Home Assistant object.
*
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies
*/
export interface DashBoardInfo {
config?: LovelaceConfig & {
strategy: {
options?: StrategyConfig
}
};
hass: HomeAssistant;
}
/**
* View Information Object.
*
* Home Assistant passes this object to the View Generator method.
*
* @property {LovelaceViewConfig} view View configuration.
* @property {LovelaceConfig} config Dashboard configuration.
* @property {HomeAssistant} hass The Home Assistant object.
*
* @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies
*/
export interface ViewInfo {
view: LovelaceViewConfig & {
strategy?: {
options?: StrategyConfig & { area: StrategyArea }
}
};
config: LovelaceConfig
hass: HomeAssistant;
}
/**
* Strategy Configuration.
*
* @property {Object.<AreaRegistryEntry>} areas List of areas.
* @property {Object.<CustomCardConfig>} [card_options] Card options for entities.
* @property {chips} [chips] List of chips to show in the Home view.
* @property {boolean} [debug] Set to true for more verbose debugging info.
* @property {Object.<DomainConfig>} domains List of domains.
* @property {object[]} [extra_cards] List of cards to show below room cards.
* @property {object[]} [extra_views] List of views to add to the dashboard.
* @property {object[]} [quick_access_cards] List of cards to show between welcome card and rooms cards.
* @property {Object.<ViewConfig>} views List of views.
*/
export interface StrategyConfig {
areas: { [k: string]: StrategyArea };
card_options?: { [k: string]: CustomCardConfig };
chips?: Chips;
debug: boolean;
domains: { [k: string]: DomainConfig };
extra_cards?: LovelaceCardConfig[];
extra_views?: ViewConfig[];
home_view: {
hidden: HiddenSectionType[]
}
quick_access_cards?: LovelaceCardConfig[];
views: { [k: string]: ViewConfig };
}
const hiddenSectionList = ["chips", "persons", "greeting", "areas", "areasTitle"] as const;
export type HiddenSectionType = typeof hiddenSectionList[number];
/**
* Represents the default configuration for a strategy.
*/
export interface StrategyDefaults extends StrategyConfig {
areas: {
undisclosed: StrategyArea & {
area_id: "undisclosed",
},
[k: string]: StrategyArea,
},
domains: {
default: DomainConfig,
[k: string]: DomainConfig,
}
}
/**
* Strategy Area.
*
* @property {number} [order] Ordering position of the area in the list of available areas.
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @property {object[]} [extra_cards] An array of card configurations.
* The configured cards are added to the dashboard.
* @property {string} [type=default] The type of area card.
*/
export interface StrategyArea extends AreaRegistryEntry {
order?: number;
hidden?: boolean;
extra_cards?: LovelaceCardConfig[];
type?: string;
}
/**
* A list of chips to show in the Home view.
*
* @property {boolean} light_count Chip to display the number of lights on.
* @property {boolean} fan_count Chip to display the number of fans on.
* @property {boolean} cover_count Chip to display the number of unclosed covers.
* @property {boolean} switch_count Chip to display the number of switches on.
* @property {boolean} climate_count Chip to display the number of climates which are not off.
* @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only.
* @property {object[]} extra_chips List of extra chips.
*/
export interface Chips {
extra_chips: LovelaceChipConfig[];
light_count: boolean;
fan_count: boolean;
cover_count: boolean;
switch_count: boolean;
climate_count: boolean;
weather_entity: string;
[key: string]: any;
}
/**
* Custom Card Configuration for an entity.
*
* @property {boolean} hidden True if the entity should be hidden from the dashboard.
*/
export interface CustomCardConfig extends LovelaceCardConfig {
hidden?: boolean;
}
/**
* Area Filter Context.
*
* @property {AreaRegistryEntry} area Area Entity.
* @property {string[]} areaDeviceIds The id of devices which are linked to the area entity.
* @property {string} domain Domain of the entity.
* Example: `light`.
*/
export interface AreaFilterContext {
area: AreaRegistryEntry;
areaDeviceIds: string[];
domain: string;
}
/**
* Checks if the given object is an instance of CallServiceActionConfig.
*
* @param {any} obj - The object to be checked.
* @return {boolean} - Returns true if the object is an instance of CallServiceActionConfig, otherwise false.
*/
export function isCallServiceActionConfig(obj: any): obj is CallServiceActionConfig {
return obj && obj.action === "call-service" && ["action", "service"].every(key => key in obj);
}
/**
* Checks if the given object is an instance of HassServiceTarget.
*
* @param {any} obj - The object to check.
* @return {boolean} - True if the object is an instance of HassServiceTarget, false otherwise.
*/
export function isCallServiceActionTarget(obj: any): obj is HassServiceTarget {
return obj && ["entity_id", "device_id", "area_id"].some(key => key in obj);
}
}
+17
View File
@@ -0,0 +1,17 @@
import {cards} from "./cards";
import {LovelaceViewConfig} from "../homeassistant/data/lovelace";
export namespace views {
/**
* Options for the extended View class.
*
* @property {cards.ControllerCardConfig} [controllerCardOptions] Options for the Controller card.
*/
export interface ViewConfig extends LovelaceViewConfig {
controllerCardOptions?: cards.ControllerCardOptions;
}
}
-131
View File
@@ -1,131 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
/**
* Abstract View Class.
*
* To create a new view, extend the new class with this one.
*
* @class
* @abstract
*/
class AbstractView {
/**
* Options for creating a view.
*
* @type {abstractOptions}
*/
options = {
title: null,
path: null,
icon: "mdi:view-dashboard",
subview: false,
};
/**
* A card to switch all entities in the view.
*
* @type {Object}
*/
viewTitleCard;
/**
* Class constructor.
*
* @throws {Error} If trying to instantiate this class.
* @throws {Error} If the Helper module isn't initialized.
*/
constructor() {
if (this.constructor === AbstractView) {
throw new Error("Abstract classes can't be instantiated.");
}
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
}
/**
* Merge the default options of this class and the custom options into the options of the parent class.
*
* @param {Object} [defaultOptions={}] Default options for the card.
* @param {Object} [customOptions={}] Custom Options for the card.
*/
mergeOptions(defaultOptions, customOptions) {
this.options = {
...defaultOptions,
...customOptions,
};
}
/**
* Create the cards to include in the view.
*
* @return {Object[] | Promise} An array of card objects.
*/
async createViewCards() {
/** @type Object[] */
const viewCards = [];
// Create cards for each area.
for (const area of Helper.areas) {
const areaCards = [];
const entities = Helper.getDeviceEntities(area, this["domain"]);
const className = Helper.sanitizeClassName(this["domain"] + "Card");
const cardModule = await import(`../cards/${className}`);
// Create a card for each domain-entity of the current area.
for (const entity of entities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id] ?? {};
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id] ?? {};
if (cardOptions.hidden || deviceOptions.hidden) {
continue;
}
areaCards.push(new cardModule[className](entity, cardOptions).getCard());
}
if (areaCards.length) {
// Create a Title card for the current area if it has entities.
areaCards.unshift(new TitleCard(
[area],
{
title: area.name,
...this.options["titleCard"],
},
this["domain"],
).createCard());
viewCards.push({
type: "vertical-stack",
cards: areaCards,
});
}
}
viewCards.unshift(viewCards.length ? this.viewTitleCard : {
type: "custom:mushroom-title-card",
title: "No Entities Available",
subtitle: "They're either hidden by the configuration or by Home Assistant.",
});
return viewCards;
}
/**
* Get a view object.
*
* The view includes the cards which are created by method createViewCards().
*
* @returns {viewOptions & {cards: Object[]}} The view object.
*/
async getView() {
return {
...this.options,
cards: await this.createViewCards(),
};
}
}
export {AbstractView};
+158
View File
@@ -0,0 +1,158 @@
import {Helper} from "../Helper";
import {ControllerCard} from "../cards/ControllerCard";
import {StackCardConfig} from "../types/homeassistant/lovelace/cards/types";
import {LovelaceCardConfig, LovelaceViewConfig} from "../types/homeassistant/data/lovelace";
import {cards} from "../types/strategy/cards";
import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config";
import {HassServiceTarget} from "home-assistant-js-websocket";
import abstractCardConfig = cards.AbstractCardConfig;
/**
* Abstract View Class.
*
* To create a new view, extend the new class with this one.
*
* @class
* @abstract
*/
abstract class AbstractView {
/**
* Configuration of the view.
*
* @type {LovelaceViewConfig}
*/
config: LovelaceViewConfig = {
icon: "mdi:view-dashboard",
subview: false,
};
/**
* A card to switch all entities in the view.
*
* @type {StackCardConfig}
*/
viewControllerCard: StackCardConfig = {
cards: [],
type: "",
};
/**
* The domain of which we operate the devices.
*
* @private
* @readonly
*/
readonly #domain?: string;
/**
* Class constructor.
*
* @param {string} [domain] The domain which the view is representing.
*
* @throws {Error} If trying to instantiate this class.
* @throws {Error} If the Helper module isn't initialized.
*/
protected constructor(domain: string = "") {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
if (domain) {
this.#domain = domain;
}
}
/**
* Create the cards to include in the view.
*
* @return {Promise<(StackCardConfig | TitleCardConfig)[]>} An array of card objects.
*/
async createViewCards(): Promise<(StackCardConfig | TitleCardConfig)[]> {
const viewCards: LovelaceCardConfig[] = [];
// Create cards for each area.
for (const area of Helper.areas) {
const areaCards: abstractCardConfig[] = [];
const entities = Helper.getDeviceEntities(area, this.#domain ?? "");
const className = Helper.sanitizeClassName(this.#domain + "Card");
const cardModule = await import(`../cards/${className}`);
// Set the target for controller cards to the current area.
let target: HassServiceTarget = {
area_id: [area.area_id],
};
// Set the target for controller cards to entities without an area.
if (area.area_id === "undisclosed") {
target = {
entity_id: entities.map(entity => entity.entity_id),
}
}
// Create a card for each domain-entity of the current area.
for (const entity of entities) {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"];
if (cardOptions?.hidden || deviceOptions?.hidden) {
continue;
}
areaCards.push(new cardModule[className](entity, cardOptions).getCard());
}
// Vertical stack the area cards if it has entities.
if (areaCards.length) {
const titleCardOptions = ("controllerCardOptions" in this.config) ? this.config.controllerCardOptions : {};
// Create and insert a Controller card.
areaCards.unshift(new ControllerCard(target, Object.assign({title: area.name}, titleCardOptions)).createCard());
viewCards.push({
type: "vertical-stack",
cards: areaCards,
} as StackCardConfig);
}
}
// Add a Controller Card for all the entities in the view.
if (viewCards.length) {
viewCards.unshift(this.viewControllerCard);
}
return viewCards;
}
/**
* Get a view object.
*
* The view includes the cards which are created by method createViewCards().
*
* @returns {Promise<LovelaceViewConfig>} The view object.
*/
async getView(): Promise<LovelaceViewConfig> {
return {
...this.config,
cards: await this.createViewCards(),
};
}
/**
* Get a target of entity IDs for the given domain.
*
* @param {string} domain - The target domain to retrieve entity IDs from.
* @return {HassServiceTarget} - A target for a service call.
*/
targetDomain(domain: string): HassServiceTarget {
return {
entity_id: Helper.entities.filter(
entity =>
entity.entity_id.startsWith(domain + ".")
&& !entity.hidden_by
&& !Helper.strategyOptions.card_options?.entity_id.hidden
).map(entity => entity.entity_id),
};
}
}
export {AbstractView};
-70
View File
@@ -1,70 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
import {AbstractView} from "./AbstractView";
/**
* Camera View Class.
*
* Used to create a view for entities of the camera domain.
*
* @class CameraView
* @extends AbstractView
*/
class CameraView extends AbstractView {
/**
* Domain of the view's entities.
* @type {string}
*/
#domain = "camera";
/**
* Default options for the view.
*
* @type {viewOptions}
* @private
*/
#defaultOptions = {
title: "Cameras",
path: "cameras",
icon: "mdi:cctv",
subview: false,
titleCard: {
showControls: false,
},
};
/**
* Options for the view's title card.
*
* @type {viewTitleCardOptions}
*/
#viewTitleCardOption = {
title: "All Cameras",
...this.options["titleCard"],
};
/**
* Class constructor.
*
* @param {viewOptions} [options={}] Options for the view.
*/
constructor(options = {}) {
super();
this.mergeOptions(
this.#defaultOptions,
options,
);
// Create a title card to switch all entities of the domain.
this.viewTitleCard = new TitleCard(Helper.areas, {
...this.#viewTitleCardOption,
...this.options["titleCard"],
}).createCard();
}
get domain() {
return this.#domain;
}
}
export {CameraView};
+73
View File
@@ -0,0 +1,73 @@
import {ControllerCard} from "../cards/ControllerCard";
import {AbstractView} from "./AbstractView";
import {views} from "../types/strategy/views";
import {cards} from "../types/strategy/cards";
import {Helper} from "../Helper";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Camera View Class.
*
* Used to create a view for entities of the camera domain.
*
* @class CameraView
* @extends AbstractView
*/
class CameraView extends AbstractView {
/**
* Domain of the view's entities.
*
* @type {string}
* @static
* @private
*/
static #domain: string = "camera";
/**
* Default configuration of the view.
*
* @type {views.ViewConfig}
* @private
*/
#defaultConfig: views.ViewConfig = {
title: "Cameras",
path: "cameras",
icon: "mdi:cctv",
subview: false,
controllerCardOptions: {
showControls: false,
},
};
/**
* Default configuration of the view's Controller card.
*
* @type {cards.ControllerCardOptions}
* @private
*/
#viewControllerCardConfig: cards.ControllerCardOptions = {
title: "All Cameras",
subtitle: Helper.getCountTemplate(CameraView.#domain, "ne", "off") + " cameras on",
};
/**
* Class constructor.
*
* @param {views.ViewConfig} [options={}] Options for the view.
*/
constructor(options: views.ViewConfig = {}) {
super(CameraView.#domain);
this.config = Object.assign(this.config, this.#defaultConfig, options);
// Create a Controller card to switch all entities of the domain.
this.viewControllerCard = new ControllerCard(
{},
{
...this.#viewControllerCardConfig,
...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig,
}).createCard();
}
}
export {CameraView};
-71
View File
@@ -1,71 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
import {AbstractView} from "./AbstractView";
/**
* Climate View Class.
*
* Used to create a view for entities of the climate domain.
*
* @class ClimateView
* @extends AbstractView
*/
class ClimateView extends AbstractView {
/**
* Domain of the view's entities.
* @type {string}
*/
#domain = "climate";
/**
* Default options for the view.
*
* @type {viewOptions}
* @private
*/
#defaultOptions = {
title: "Climates",
path: "climates",
icon: "mdi:thermostat",
subview: false,
titleCard: {
showControls: false,
},
};
/**
* Options for the view's title card.
*
* @type {viewTitleCardOptions}
*/
#viewTitleCardOption = {
title: "All Climates",
subtitle: Helper.getCountTemplate(this.domain, "ne", "off") + " climates on",
...this.options["titleCard"],
};
/**
* Class constructor.
*
* @param {viewOptions} [options={}] Options for the view.
*/
constructor(options = {}) {
super();
this.mergeOptions(
this.#defaultOptions,
options,
);
// Create a title card to switch all entities of the domain.
this.viewTitleCard = new TitleCard(Helper.areas, {
...this.#viewTitleCardOption,
...this.options["titleCard"],
}).createCard();
}
get domain() {
return this.#domain;
}
}
export {ClimateView};
+73
View File
@@ -0,0 +1,73 @@
import {Helper} from "../Helper";
import {ControllerCard} from "../cards/ControllerCard";
import {AbstractView} from "./AbstractView";
import {views} from "../types/strategy/views";
import {cards} from "../types/strategy/cards";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Climate View Class.
*
* Used to create a view for entities of the climate domain.
*
* @class ClimateView
* @extends AbstractView
*/
class ClimateView extends AbstractView {
/**
* Domain of the view's entities.
*
* @type {string}
* @static
* @private
*/
static #domain: string = "climate";
/**
* Default configuration of the view.
*
* @type {views.ViewConfig}
* @private
*/
#defaultConfig: views.ViewConfig = {
title: "Climates",
path: "climates",
icon: "mdi:thermostat",
subview: false,
controllerCardOptions: {
showControls: false,
},
};
/**
* Default configuration of the view's Controller card.
*
* @type {cards.ControllerCardOptions}
* @private
*/
#viewControllerCardConfig: cards.ControllerCardOptions = {
title: "All Climates",
subtitle: Helper.getCountTemplate(ClimateView.#domain, "ne", "off") + " climates on",
};
/**
* Class constructor.
*
* @param {views.ViewConfig} [options={}] Options for the view.
*/
constructor(options: views.ViewConfig = {}) {
super(ClimateView.#domain);
this.config = Object.assign(this.config, this.#defaultConfig, options);
// Create a Controller card to switch all entities of the domain.
this.viewControllerCard = new ControllerCard(
this.targetDomain(ClimateView.#domain),
{
...this.#viewControllerCardConfig,
...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig,
}).createCard();
}
}
export {ClimateView};
-73
View File
@@ -1,73 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
import {AbstractView} from "./AbstractView";
/**
* Cover View Class.
*
* Used to create a view for entities of the cover domain.
*
* @class CoverView
* @extends AbstractView
*/
class CoverView extends AbstractView {
/**
* Domain of the view's entities.
* @type {string}
*/
#domain = "cover";
/**
* Default options for the view.
*
* @type {viewOptions}
* @private
*/
#defaultOptions = {
title: "Covers",
path: "covers",
icon: "mdi:window-open",
subview: false,
titleCard: {
iconOn: "mdi:arrow-up",
iconOff: "mdi:arrow-down",
onService: "cover.open_cover",
offService: "cover.close_cover",
},
};
/**
* Options for the view's title card.
*
* @type {viewTitleCardOptions}
*/
#viewTitleCardOption = {
title: "All Covers",
subtitle: Helper.getCountTemplate(this.domain, "eq", "open") + " covers open",
};
/**
* Class constructor.
*
* @param {viewOptions} [options={}] Options for the view.
*/
constructor(options = {}) {
super();
this.mergeOptions(
this.#defaultOptions,
options,
);
// Create a title card to switch all entities of the domain.
this.viewTitleCard = new TitleCard(Helper.areas, {
...this.#viewTitleCardOption,
...this.options["titleCard"],
}).createCard();
}
get domain() {
return this.#domain;
}
}
export {CoverView};
+76
View File
@@ -0,0 +1,76 @@
import {Helper} from "../Helper";
import {ControllerCard} from "../cards/ControllerCard";
import {AbstractView} from "./AbstractView";
import {views} from "../types/strategy/views";
import {cards} from "../types/strategy/cards";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Cover View Class.
*
* Used to create a view for entities of the cover domain.
*
* @class CoverView
* @extends AbstractView
*/
class CoverView extends AbstractView {
/**
* Domain of the view's entities.
*
* @type {string}
* @static
* @private
*/
static #domain: string = "cover";
/**
* Default configuration of the view.
*
* @type {views.ViewConfig}
* @private
*/
#defaultConfig: views.ViewConfig = {
title: "Covers",
path: "covers",
icon: "mdi:window-open",
subview: false,
controllerCardOptions: {
iconOn: "mdi:arrow-up",
iconOff: "mdi:arrow-down",
onService: "cover.open_cover",
offService: "cover.close_cover",
},
};
/**
* Default configuration of the view's Controller card.
*
* @type {cards.ControllerCardOptions}
* @private
*/
#viewControllerCardConfig: cards.ControllerCardOptions = {
title: "All Covers",
subtitle: Helper.getCountTemplate(CoverView.#domain, "eq", "open") + " covers open",
};
/**
* Class constructor.
*
* @param {views.ViewConfig} [options={}] Options for the view.
*/
constructor(options: views.ViewConfig = {}) {
super(CoverView.#domain);
this.config = Object.assign(this.config, this.#defaultConfig, options);
// Create a Controller card to switch all entities of the domain.
this.viewControllerCard = new ControllerCard(
this.targetDomain(CoverView.#domain),
{
...this.#viewControllerCardConfig,
...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig,
}).createCard();
}
}
export {CoverView};
-73
View File
@@ -1,73 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
import {AbstractView} from "./AbstractView";
/**
* Fan View Class.
*
* Used to create a view for entities of the fan domain.
*
* @class FanView
* @extends AbstractView
*/
class FanView extends AbstractView {
/**
* Domain of the view's entities.
* @type {string}
*/
#domain = "fan";
/**
* Default options for the view.
*
* @type {viewOptions}
* @private
*/
#defaultOptions = {
title: "Fans",
path: "fans",
icon: "mdi:fan",
subview: false,
titleCard: {
iconOn: "mdi:fan",
iconOff: "mdi:fan-off",
onService: "fan.turn_on",
offService: "fan.turn_off",
},
};
/**
* Options for the view's title card.
*
* @type {viewTitleCardOptions}
*/
#viewTitleCardOption = {
title: "All Fans",
subtitle: Helper.getCountTemplate(this.domain, "eq", "on") + " fans on",
};
/**
* Class constructor.
*
* @param {viewOptions} [options={}] Options for the view.
*/
constructor(options = {}) {
super();
this.mergeOptions(
this.#defaultOptions,
options,
);
// Create a title card to switch all entities of the domain.
this.viewTitleCard = new TitleCard(Helper.areas, {
...this.#viewTitleCardOption,
...this.options["titleCard"],
}).createCard();
}
get domain() {
return this.#domain;
}
}
export {FanView};
+76
View File
@@ -0,0 +1,76 @@
import {Helper} from "../Helper";
import {ControllerCard} from "../cards/ControllerCard";
import {AbstractView} from "./AbstractView";
import {views} from "../types/strategy/views";
import {cards} from "../types/strategy/cards";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Fan View Class.
*
* Used to create a view for entities of the fan domain.
*
* @class FanView
* @extends AbstractView
*/
class FanView extends AbstractView {
/**
* Domain of the view's entities.
*
* @type {string}
* @static
* @private
*/
static #domain: string = "fan";
/**
* Default configuration of the view.
*
* @type {views.ViewConfig}
* @private
*/
#defaultConfig: views.ViewConfig = {
title: "Fans",
path: "fans",
icon: "mdi:fan",
subview: false,
controllerCardOptions: {
iconOn: "mdi:fan",
iconOff: "mdi:fan-off",
onService: "fan.turn_on",
offService: "fan.turn_off",
},
};
/**
* Default configuration of the view's Controller card.
*
* @type {cards.ControllerCardOptions}
* @private
*/
#viewControllerCardConfig: cards.ControllerCardOptions = {
title: "All Fans",
subtitle: Helper.getCountTemplate(FanView.#domain, "eq", "on") + " fans on",
};
/**
* Class constructor.
*
* @param {views.ViewConfig} [options={}] Options for the view.
*/
constructor(options: views.ViewConfig = {}) {
super(FanView.#domain);
this.config = Object.assign(this.config, this.#defaultConfig, options);
// Create a Controller card to switch all entities of the domain.
this.viewControllerCard = new ControllerCard(
this.targetDomain(FanView.#domain),
{
...this.#viewControllerCardConfig,
...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig,
}).createCard();
}
}
export {FanView};
+78 -75
View File
@@ -1,6 +1,16 @@
import {Helper} from "../Helper";
import {AbstractView} from "./AbstractView";
import {views} from "../types/strategy/views";
import {LovelaceChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types";
import {ChipsCardConfig} from "../types/lovelace-mushroom/cards/chips-card";
import {AreaCardConfig, StackCardConfig} from "../types/homeassistant/lovelace/cards/types";
import {TemplateCardConfig} from "../types/lovelace-mushroom/cards/template-card-config";
import {ActionConfig} from "../types/homeassistant/data/lovelace";
import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config";
import {PersonCardConfig} from "../types/lovelace-mushroom/cards/person-card-config";
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
/**
* Home View Class.
*
@@ -11,13 +21,14 @@ import {AbstractView} from "./AbstractView";
*/
class HomeView extends AbstractView {
/**
* Default options for the view.
* Default configuration of the view.
*
* @type {viewOptions}
* @type {views.ViewConfig}
* @private
*/
#defaultOptions = {
#defaultConfig: views.ViewConfig = {
title: "Home",
icon: "mdi:home-assistant",
path: "home",
subview: false,
};
@@ -25,73 +36,65 @@ class HomeView extends AbstractView {
/**
* Class constructor.
*
* @param {viewOptions} [options={}] Options for the view.
* @param {views.ViewConfig} [options={}] Options for the view.
*/
constructor(options = {}) {
constructor(options: views.ViewConfig = {}) {
super();
this.mergeOptions(
this.#defaultOptions,
options,
);
this.config = Object.assign(this.config, this.#defaultConfig, options);
}
/**
* Create the cards to include in the view.
*
* @return {Promise} A promise of a card object array.
* @return {Promise<(StackCardConfig | TemplateCardConfig | ChipsCardConfig)[]>} Promise a View Card array.
* @override
*/
async createViewCards() {
async createViewCards(): Promise<(StackCardConfig | TemplateCardConfig | ChipsCardConfig)[]> {
return await Promise.all([
this.#createChips(),
this.#createPersonCards(),
this.#createAreaCards(),
this.#createAreaSection(),
]).then(([chips, personCards, areaCards]) => {
const options = Helper.strategyOptions;
const homeViewCards = [];
// Add chips to the view.
if (chips.length) {
// TODO: Create the Chip card at this.#createChips()
homeViewCards.push({
type: "custom:mushroom-chips-card",
alignment: "center",
chips: chips,
});
} as ChipsCardConfig)
}
// Add persons to the view
if (personCards.length) {
// TODO: Create the stack at this.#createPersonCards()
homeViewCards.push({
type: "horizontal-stack",
cards: personCards,
});
} as StackCardConfig);
}
// Add greeting to the view if not hidden.
if (!Helper.strategyOptions.homeView?.hidden?.includes("greeting")) {
if (!Helper.strategyOptions.home_view.hidden.includes("greeting")) {
homeViewCards.push({
type: "custom:mushroom-template-card",
primary:
"{% set time = now().hour %}" +
"{% if (time >= 18) %} Good Evening, {{user}}!" +
"{% elif (time >= 12) %} Good Afternoon, {{user}}!" +
"{% elif (time >= 5) %} Good Morning, {{user}}!" +
"{% else %} Hello, {{user}}!" +
"{% endif %}",
primary: "{% set time = now().hour %} {% if (time >= 18) %} Good Evening, {{user}}! {% elif (time >= 12) %} Good Afternoon, {{user}}! {% elif (time >= 5) %} Good Morning, {{user}}! {% else %} Hello, {{user}}! {% endif %}",
icon: "mdi:hand-wave",
icon_color: "orange",
tap_action: {
action: "none",
},
} as ActionConfig,
double_tap_action: {
action: "none",
},
} as ActionConfig,
hold_action: {
action: "none",
},
});
} as ActionConfig,
} as TemplateCardConfig);
}
// Add quick access cards.
if (options.quick_access_cards) {
homeViewCards.push(...options.quick_access_cards);
@@ -101,7 +104,7 @@ class HomeView extends AbstractView {
homeViewCards.push({
type: "vertical-stack",
cards: areaCards,
});
} as StackCardConfig);
// Add custom cards.
if (options.extra_cards) {
@@ -115,49 +118,53 @@ class HomeView extends AbstractView {
/**
* Create the chips to include in the view.
*
* @return {Object[]} A chip object array.
* @return {Promise<LovelaceChipConfig[]>} Promise a chip array.
*/
async #createChips() {
if (Helper.strategyOptions.homeView?.hidden?.includes("chips")) {
// Chips section is hidden; Return 0 chips.
async #createChips(): Promise<LovelaceChipConfig[]> {
if (Helper.strategyOptions.home_view.hidden.includes("chips")) {
// Chips section is hidden.
return [];
}
const chips = [];
const chips: LovelaceChipConfig[] = [];
const chipOptions = Helper.strategyOptions.chips;
// TODO: Get domains from config.
const exposed_chips = ["light", "fan", "cover", "switch", "climate"];
const exposedChips = ["light", "fan", "cover", "switch", "climate"];
// Create a list of area-ids, used for switching all devices via chips
const areaIds = Helper.areas.map(area => area.area_id);
const areaIds = Helper.areas.map(area => area.area_id ?? "");
let chipModule;
// Weather chip.
const weatherEntityId = chipOptions?.weather_entity ?? Helper.entities.find(
entity => entity.entity_id.startsWith("weather.") && entity.disabled_by == null && entity.hidden_by == null,
(entity) => entity.entity_id.startsWith("weather.") && entity.disabled_by === null && entity.hidden_by === null,
)?.entity_id;
if (weatherEntityId) {
try {
chipModule = await import("../chips/WeatherChip");
const weatherChip = new chipModule.WeatherChip(weatherEntityId);
chips.push(weatherChip.getChip());
} catch (e) {
console.error(Helper.debug ? e : "An error occurred while creating the weather chip!");
Helper.logError("An error occurred while creating the weather chip!", e);
}
}
// Numeric chips.
for (let chipType of exposed_chips) {
if (chipOptions?.[`${chipType}_count`] ?? true) {
for (let chipType of exposedChips) {
if (chipOptions?.[`${chipType}_count` as string] ?? true) {
const className = Helper.sanitizeClassName(chipType + "Chip");
try {
chipModule = await import((`../chips/${className}`));
const chip = new chipModule[className](areaIds);
const chip = new chipModule[className]();
chip.setTapActionTarget({area_id: areaIds});
chips.push(chip.getChip());
} catch (e) {
console.error(Helper.debug ? e : `An error occurred while creating the ${chipType} chip!`);
Helper.logError(`An error occurred while creating the ${chipType} chip!`, e);
}
}
}
@@ -173,18 +180,19 @@ class HomeView extends AbstractView {
/**
* Create the person cards to include in the view.
*
* @return {Object[]} A card object array.
* @return {PersonCardConfig[]} A Person Card array.
*/
#createPersonCards() {
if (Helper.strategyOptions.homeView?.hidden?.includes("persons")) {
// Person section is hidden; Return 0 cards.
#createPersonCards(): PersonCardConfig[] {
if (Helper.strategyOptions.home_view.hidden.includes("persons")) {
// Person section is hidden.
return [];
}
const cards = [];
const cards: PersonCardConfig[] = [];
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.")
&& entity.hidden_by == null
&& entity.disabled_by == null;
@@ -201,40 +209,35 @@ class HomeView extends AbstractView {
*
* Area cards are grouped into two areas per row.
*
* @return {Object[]} A card object array.
* @return {Promise<(TitleCardConfig | StackCardConfig)[]>} Promise an Area Card Section.
*/
async #createAreaCards() {
if (Helper.strategyOptions.homeView?.hidden?.includes("areas")) {
// Area section is hidden; Return 0 cards.
async #createAreaSection(): Promise<(TitleCardConfig | StackCardConfig)[]> {
if (Helper.strategyOptions.home_view.hidden.includes("areas")) {
// Areas section is hidden.
return [];
}
/**
* Cards to be stacked vertically.
*
* Contains a Title card and horizontal stacks of Area cards.
*
* @type {[{}]}
*/
const groupedCards = [];
const groupedCards: (TitleCardConfig | StackCardConfig)[] = [];
let areaCards = [];
let areaCards: (TemplateCardConfig | AreaCardConfig)[] = [];
// Add title to the area section.
if (!Helper.strategyOptions.homeView?.hidden?.includes("areasTitle")) {
if (!Helper.strategyOptions.home_view.hidden.includes("areasTitle")) {
groupedCards.push({
type: "custom:mushroom-title-card",
title: "Areas",
});
type: "custom:mushroom-title-card",
title: "Areas",
},
);
}
// Add cards to the area section.
for (const [i, area] of Helper.areas.entries()) {
let module;
type ModuleType = typeof import("../cards/AreaCard");
let module: ModuleType;
let moduleName =
Helper.strategyOptions.areas[area.area_id ?? "undisclosed"]?.type ??
Helper.strategyOptions.areas["_"]?.type ??
"default";
Helper.strategyOptions.areas[area.area_id]?.type ??
Helper.strategyOptions.areas["_"]?.type ??
"default";
// Load module by type in strategy options.
try {
@@ -249,10 +252,10 @@ class HomeView extends AbstractView {
}
// Get a card for the area.
if (!Helper.strategyOptions.areas[area.area_id]?.hidden) {
if (!Helper.strategyOptions.areas[area.area_id as string]?.hidden) {
let options = {
...Helper.strategyOptions.areas["_"],
...Helper.strategyOptions.areas[area.area_id ?? "undisclosed"],
...Helper.strategyOptions.areas[area.area_id],
};
areaCards.push(new module.AreaCard(area, options).getCard());
@@ -264,7 +267,7 @@ class HomeView extends AbstractView {
groupedCards.push({
type: "horizontal-stack",
cards: areaCards.slice(i, i + 2),
});
} as StackCardConfig);
}
}
}
-73
View File
@@ -1,73 +0,0 @@
import {Helper} from "../Helper";
import {TitleCard} from "../cards/TitleCard";
import {AbstractView} from "./AbstractView";
/**
* Light View Class.
*
* Used to create a view for entities of the light domain.
*
* @class LightView
* @extends AbstractView
*/
class LightView extends AbstractView {
/**
* Domain of the view's entities.
* @type {string}
*/
#domain = "light";
/**
* Default options for the view.
*
* @type {viewOptions}
* @private
*/
#defaultOptions = {
title: "Lights",
path: "lights",
icon: "mdi:lightbulb-group",
subview: false,
titleCard: {
iconOn: "mdi:lightbulb",
iconOff: "mdi:lightbulb-off",
onService: "light.turn_on",
offService: "light.turn_off",
},
};
/**
* Options for the view's title card.
*
* @type {viewTitleCardOptions}
*/
#viewTitleCardOption = {
title: "All Lights",
subtitle: Helper.getCountTemplate(this.domain, "eq", "on") + " lights on",
};
/**
* Class constructor.
*
* @param {viewOptions} [options={}] Options for the view.
*/
constructor(options = {}) {
super();
this.mergeOptions(
this.#defaultOptions,
options,
);
// Create a title card to switch all entities of the domain.
this.viewTitleCard = new TitleCard(Helper.areas, {
...this.#viewTitleCardOption,
...this.options["titleCard"],
}).createCard();
}
get domain() {
return this.#domain;
}
}
export {LightView};

Some files were not shown because too many files have changed in this diff Show More