forked from DigiLive/mushroom-strategy
Add Device View
Entities of the same device can now be grouped under a separate view instead of showing them all on a domain or area view. If grouped, by a device id, a device card is shown instead which can be tapped to view its entities.
This commit is contained in:
@@ -6,7 +6,6 @@ import { EntityRegistryEntry } from './types/homeassistant/data/entity_registry'
|
||||
import {
|
||||
AllDomainsConfig,
|
||||
DashboardInfo,
|
||||
isSortable,
|
||||
SingleDomainConfig,
|
||||
StrategyArea,
|
||||
StrategyConfig,
|
||||
@@ -17,6 +16,9 @@ import {
|
||||
import { logMessage, lvlFatal, lvlOff, lvlWarn, setDebugLevel } from './utilities/debug';
|
||||
import setupCustomLocalize from './utilities/localize';
|
||||
import RegistryFilter from './utilities/RegistryFilter';
|
||||
import { getObjectKeysByPropertyValue } from './utilities/auxiliaries';
|
||||
import { ConfigEntry } from './types/homeassistant/data/config_entries';
|
||||
import { isSortable } from './types/strategy/type-guards';
|
||||
|
||||
/**
|
||||
* Registry Class
|
||||
@@ -36,6 +38,18 @@ class Registry {
|
||||
private static _initialized: boolean = false;
|
||||
/** The Custom strategy configuration. */
|
||||
private static _strategyOptions: StrategyConfig;
|
||||
static darkMode: boolean;
|
||||
|
||||
/** The entities which are grouped into a device view */
|
||||
// TODO: Create type or interface?
|
||||
private static _configEntries: ConfigEntry[] = [];
|
||||
|
||||
/**
|
||||
* Home Assistant's Config Entries.
|
||||
*/
|
||||
static get configEntries() {
|
||||
return Registry._configEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@@ -48,8 +62,15 @@ class Registry {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
private static _groupingDeviceIds: Set<string>;
|
||||
|
||||
/** Get the initialization status of the Registry class. */
|
||||
static get groupingDeviceIds() {
|
||||
return Registry._groupingDeviceIds;
|
||||
}
|
||||
|
||||
/** The configuration of the strategy. */
|
||||
static get strategyOptions(): StrategyConfig {
|
||||
static get strategyOptions() {
|
||||
return Registry._strategyOptions;
|
||||
}
|
||||
|
||||
@@ -59,7 +80,7 @@ class Registry {
|
||||
* @remarks
|
||||
* This module makes changes to the registry at {@link Registry.initialize}.
|
||||
*/
|
||||
static get areas(): StrategyArea[] {
|
||||
static get areas() {
|
||||
return Registry._areas;
|
||||
}
|
||||
|
||||
@@ -69,7 +90,7 @@ class Registry {
|
||||
* @remarks
|
||||
* This module makes changes to the registry at {@link Registry.initialize}.
|
||||
*/
|
||||
static get devices(): DeviceRegistryEntry[] {
|
||||
static get devices() {
|
||||
return Registry._devices;
|
||||
}
|
||||
|
||||
@@ -79,17 +100,17 @@ class Registry {
|
||||
* @remarks
|
||||
* This module makes changes to the registry at {@link Registry.initialize}.
|
||||
*/
|
||||
static get entities(): EntityRegistryEntry[] {
|
||||
static get entities() {
|
||||
return Registry._entities;
|
||||
}
|
||||
|
||||
/** Home Assistant's State registry. */
|
||||
static get hassStates(): HassEntities {
|
||||
static get hassStates() {
|
||||
return Registry._hassStates;
|
||||
}
|
||||
|
||||
/** Get the initialization status of the Registry class. */
|
||||
static get initialized(): boolean {
|
||||
static get initialized() {
|
||||
return Registry._initialized;
|
||||
}
|
||||
|
||||
@@ -105,6 +126,7 @@ class Registry {
|
||||
*/
|
||||
static async initialize(info: DashboardInfo): Promise<void> {
|
||||
setupCustomLocalize(info.hass);
|
||||
Registry.darkMode = info.hass.themes.darkMode;
|
||||
|
||||
// Import the Hass States and strategy options.
|
||||
Registry._hassStates = info.hass.states;
|
||||
@@ -121,10 +143,11 @@ class Registry {
|
||||
// Import the registries of Home Assistant.
|
||||
try {
|
||||
// noinspection ES6MissingAwait False positive? https://youtrack.jetbrains.com/issue/WEB-63746
|
||||
[Registry._entities, Registry._devices, Registry._areas] = await Promise.all([
|
||||
[Registry._entities, Registry._devices, Registry._areas, Registry._configEntries] = 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[]>,
|
||||
info.hass.callWS({ type: 'config_entries/get' }) as Promise<ConfigEntry[]>,
|
||||
]);
|
||||
} catch (e) {
|
||||
logMessage(lvlFatal, 'Error importing Home Assistant registries!', e);
|
||||
@@ -144,7 +167,7 @@ class Registry {
|
||||
.whereEntityCategory('diagnostic')
|
||||
.isNotHidden()
|
||||
.whereDisabledBy(null)
|
||||
.orderBy(['name', 'original_name'], 'asc')
|
||||
.orderBy(['name', 'original_name'])
|
||||
.toList();
|
||||
|
||||
Registry._entities = Registry.entities.map((entity) => ({
|
||||
@@ -156,7 +179,7 @@ class Registry {
|
||||
Registry._devices = new RegistryFilter(Registry.devices)
|
||||
.isNotHidden()
|
||||
.whereDisabledBy(null)
|
||||
.orderBy(['name_by_user', 'name'], 'asc')
|
||||
.orderBy(['name_by_user', 'name'])
|
||||
.toList();
|
||||
|
||||
Registry._devices = Registry.devices.map((device) => ({
|
||||
@@ -170,7 +193,7 @@ class Registry {
|
||||
} else {
|
||||
// Create and add the undisclosed area if not hidden in the strategy options.
|
||||
if (!Registry.strategyOptions.areas.undisclosed?.hidden) {
|
||||
Registry.areas.push(ConfigurationDefaults.areas.undisclosed);
|
||||
Registry._areas.push(ConfigurationDefaults.areas.undisclosed);
|
||||
}
|
||||
|
||||
// Merge area configurations of the Strategy options into the entries of the area registry.
|
||||
@@ -180,18 +203,18 @@ class Registry {
|
||||
});
|
||||
|
||||
// Ensure the custom configuration of the undisclosed area doesn't overwrite the area_id.
|
||||
Registry.strategyOptions.areas.undisclosed.area_id = 'undisclosed';
|
||||
Registry._strategyOptions.areas.undisclosed.area_id = 'undisclosed';
|
||||
|
||||
// Remove hidden areas if configured as so and sort them by name.
|
||||
|
||||
Registry._areas = new RegistryFilter(Registry.areas).isNotHidden().orderBy(['order', 'name'], 'asc').toList();
|
||||
Registry._areas = new RegistryFilter(Registry.areas).isNotHidden().orderBy(['order', 'name']).toList();
|
||||
}
|
||||
|
||||
// Sort views by order first and then by title.
|
||||
const sortViews = () => {
|
||||
const entries = Object.entries(Registry.strategyOptions.views);
|
||||
|
||||
Registry.strategyOptions.views = Object.fromEntries(
|
||||
Registry._strategyOptions.views = Object.fromEntries(
|
||||
entries.sort(([_, a], [__, b]) => {
|
||||
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? '').localeCompare(b.title ?? '');
|
||||
}),
|
||||
@@ -203,7 +226,8 @@ class Registry {
|
||||
// Sort domains by order first and then by title.
|
||||
const sortDomains = () => {
|
||||
const entries = Object.entries(Registry.strategyOptions.domains);
|
||||
Registry.strategyOptions.domains = Object.fromEntries(
|
||||
|
||||
Registry._strategyOptions.domains = Object.fromEntries(
|
||||
entries.sort(([, a], [, b]) => {
|
||||
if (isSortable(a) && isSortable(b)) {
|
||||
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? '').localeCompare(b.title ?? '');
|
||||
@@ -219,13 +243,26 @@ class Registry {
|
||||
// Sort extra views by order first and then by title.
|
||||
// TODO: Add sorting to the wiki.
|
||||
const sortExtraViews = () => {
|
||||
Registry.strategyOptions.extra_views.sort((a, b) => {
|
||||
Registry._strategyOptions.extra_views.sort((a, b) => {
|
||||
return (a.order ?? Infinity) - (b.order ?? Infinity) || (a.title ?? '').localeCompare(b.title ?? '');
|
||||
});
|
||||
};
|
||||
|
||||
sortExtraViews();
|
||||
|
||||
// Process grouping by device.
|
||||
this._groupingDeviceIds = new Set(
|
||||
Registry.devices
|
||||
.filter(
|
||||
(device) =>
|
||||
Registry.strategyOptions.device_options['_'].group_entities ||
|
||||
getObjectKeysByPropertyValue(Registry.strategyOptions.device_options, 'group_entities', true).includes(
|
||||
device.id,
|
||||
),
|
||||
)
|
||||
.map((device) => device.id),
|
||||
);
|
||||
|
||||
Registry._initialized = true;
|
||||
}
|
||||
|
||||
@@ -278,20 +315,34 @@ class Registry {
|
||||
*
|
||||
* @param {string} type The type of options to filter ("domain", "view", "chip").
|
||||
*
|
||||
* @returns {string[]} For domains and views: names of items that aren't hidden.
|
||||
* @returns {Set<string>} For domains and views: names of items that aren't hidden.
|
||||
* For chips: names of items that are explicitly set to true.
|
||||
*/
|
||||
static getExposedNames(type: 'domain' | 'view' | 'chip'): string[] {
|
||||
static getExposedNames(type: 'domain' | 'view' | 'chip'): Set<string> {
|
||||
// TODO: Align chip with other types.
|
||||
if (type === 'chip') {
|
||||
return Object.entries(Registry.strategyOptions.chips)
|
||||
const keySet = new Set<string>();
|
||||
|
||||
Object.entries(Registry.strategyOptions.chips)
|
||||
.filter(([_, value]) => value === true)
|
||||
.map(([key]) => key.split('_')[0]);
|
||||
.forEach(([key]) => {
|
||||
keySet.add(key.split('_')[0]);
|
||||
});
|
||||
|
||||
return keySet;
|
||||
}
|
||||
|
||||
const group = Registry.strategyOptions[`${type}s`] as Record<string, { hidden?: boolean }>;
|
||||
const typeOptions = Registry.strategyOptions[`${type}s`] as Record<string, { hidden?: boolean }>;
|
||||
|
||||
return Object.keys(group).filter((key) => key !== '_' && key !== 'default' && !group[key].hidden);
|
||||
const keySet = new Set<string>();
|
||||
|
||||
Object.keys(typeOptions).forEach((key) => {
|
||||
if (key !== '_' && key !== 'default' && !typeOptions[key].hidden) {
|
||||
keySet.add(key);
|
||||
}
|
||||
});
|
||||
|
||||
return keySet;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Registry } from '../Registry';
|
||||
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
|
||||
import { AbstractCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { RegistryEntry } from '../types/strategy/strategy-generics';
|
||||
import { logMessage, lvlFatal } from '../utilities/debug';
|
||||
|
||||
@@ -48,7 +47,7 @@ abstract class AbstractCard {
|
||||
*
|
||||
* The configuration should be set by any of the child classes so the card correctly reflects an entity.
|
||||
*/
|
||||
getCard(): AbstractCardConfig {
|
||||
getCard(): LovelaceCardConfig {
|
||||
return {
|
||||
...this.configuration,
|
||||
entity: 'entity_id' in this.entity ? this.entity.entity_id : undefined,
|
||||
|
69
src/cards/DeviceCard.ts
Normal file
69
src/cards/DeviceCard.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
import AbstractCard from './AbstractCard';
|
||||
import { TemplateCardConfig } from '../types/lovelace-mushroom/cards/template-card-config';
|
||||
import { localize } from '../utilities/localize';
|
||||
import { DeviceRegistryEntry } from '../types/homeassistant/data/device_registry';
|
||||
import RegistryFilter from '../utilities/RegistryFilter';
|
||||
import { Registry } from '../Registry';
|
||||
|
||||
/**
|
||||
* Device Card Class
|
||||
*
|
||||
* Used to create a card for a device.
|
||||
*
|
||||
* @class
|
||||
* @extends AbstractCard
|
||||
*/
|
||||
class DeviceCard extends AbstractCard {
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {DeviceRegistryEntry} device The device entity to create a card for.
|
||||
* @param {TemplateCardConfig} [customConfiguration] Options for the card.
|
||||
*
|
||||
* @throws {Error} If the Helper module isn't initialized.
|
||||
*/
|
||||
constructor(device: DeviceRegistryEntry, customConfiguration?: TemplateCardConfig) {
|
||||
super(device);
|
||||
|
||||
const configuration = DeviceCard.getDefaultConfig();
|
||||
const deviceName = device.name_by_user ?? device.name ?? localize('generic.unnamed', 'title');
|
||||
const integration = new RegistryFilter(Registry.configEntries)
|
||||
.where((entry) => entry.entry_id === device.config_entries[0])
|
||||
.single();
|
||||
|
||||
// Initialize the default configuration.
|
||||
configuration.primary = `${localize('generic.device', 'title')}: ${deviceName}`;
|
||||
|
||||
if (integration) {
|
||||
const iconBaseUrl = `https://brands.home-assistant.io/_/${integration.domain}/`;
|
||||
configuration.picture = Registry.darkMode ? `${iconBaseUrl}dark_icon.png` : `${iconBaseUrl}icon.png`;
|
||||
}
|
||||
|
||||
if (configuration.tap_action && 'navigation_path' in configuration.tap_action) {
|
||||
configuration.tap_action.navigation_path = device.id;
|
||||
}
|
||||
|
||||
this.configuration = { ...this.configuration, ...configuration, ...customConfiguration };
|
||||
}
|
||||
|
||||
/** Returns the default configuration object for the card. */
|
||||
static getDefaultConfig(): TemplateCardConfig {
|
||||
return {
|
||||
type: 'custom:mushroom-template-card',
|
||||
primary: undefined,
|
||||
secondary: localize('generic.tap_here', 'title') + '…',
|
||||
icon: 'mdi:view-dashboard-outline',
|
||||
icon_color: 'blue',
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '',
|
||||
},
|
||||
hold_action: {
|
||||
action: 'none',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { DeviceCard };
|
@@ -15,9 +15,11 @@ class FanCard extends AbstractCard {
|
||||
return {
|
||||
type: 'custom:mushroom-fan-card',
|
||||
icon: undefined,
|
||||
show_percentage_control: true,
|
||||
show_oscillate_control: true,
|
||||
icon_animation: true,
|
||||
collapsible_controls: true,
|
||||
show_direction_control: true,
|
||||
show_oscillate_control: true,
|
||||
show_percentage_control: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { HassServiceTarget } from 'home-assistant-js-websocket';
|
||||
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
|
||||
import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/types';
|
||||
import { CustomHeaderCardConfig, StrategyHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { localize } from '../utilities/localize';
|
||||
|
||||
/**
|
||||
* Header Card class.
|
||||
@@ -13,34 +14,41 @@ class HeaderCard {
|
||||
/** The target to control the entities of. */
|
||||
private readonly target: HassServiceTarget;
|
||||
/** The current configuration of the card after instantiating this class. */
|
||||
private readonly configuration: StrategyHeaderCardConfig;
|
||||
|
||||
/** Returns the default configuration object for the card. */
|
||||
static getDefaultConfig(): StrategyHeaderCardConfig {
|
||||
return {
|
||||
type: 'custom:mushroom-title-card',
|
||||
showControls: true,
|
||||
iconOn: 'mdi:power-on',
|
||||
iconOff: 'mdi:power-off',
|
||||
onService: 'none',
|
||||
offService: 'none',
|
||||
};
|
||||
}
|
||||
private readonly configuration: HeaderCardConfig;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param {HassServiceTarget} target The target which is optionally controlled by the card.
|
||||
* @param {CustomHeaderCardConfig} [customConfiguration] Custom card configuration.
|
||||
* @param {HeaderCardConfig} [customConfiguration] Custom card configuration.
|
||||
*
|
||||
* @remarks
|
||||
* The target object can contain one or multiple ids of different entry types.
|
||||
*/
|
||||
constructor(target: HassServiceTarget, customConfiguration?: CustomHeaderCardConfig) {
|
||||
constructor(target: HassServiceTarget, customConfiguration?: HeaderCardConfig) {
|
||||
this.target = target;
|
||||
this.configuration = { ...HeaderCard.getDefaultConfig(), ...customConfiguration };
|
||||
}
|
||||
|
||||
/** Returns the default configuration object for the card. */
|
||||
static getDefaultConfig(): HeaderCardConfig {
|
||||
return {
|
||||
type: 'custom:mushroom-title-card',
|
||||
showControls: false,
|
||||
on: {
|
||||
icon: 'mdi:power-on',
|
||||
icon_color: 'disabled',
|
||||
service: 'none',
|
||||
},
|
||||
off: {
|
||||
icon: 'mdi:power-off',
|
||||
icon_color: 'disabled',
|
||||
service: 'none',
|
||||
},
|
||||
title: localize('generic.unknown', 'title'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Header card configuration.
|
||||
*
|
||||
@@ -65,24 +73,24 @@ class HeaderCard {
|
||||
cards: [
|
||||
{
|
||||
type: 'custom:mushroom-template-card',
|
||||
icon: this.configuration.iconOff,
|
||||
icon: this.configuration.on?.icon,
|
||||
layout: 'vertical',
|
||||
icon_color: 'red',
|
||||
icon_color: 'green',
|
||||
tap_action: {
|
||||
action: 'call-service',
|
||||
service: this.configuration.offService,
|
||||
service: this.configuration.on?.service,
|
||||
target: this.target,
|
||||
data: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'custom:mushroom-template-card',
|
||||
icon: this.configuration.iconOn,
|
||||
icon: this.configuration.off?.icon,
|
||||
layout: 'vertical',
|
||||
icon_color: 'amber',
|
||||
icon_color: 'deep-orange',
|
||||
tap_action: {
|
||||
action: 'call-service',
|
||||
service: this.configuration.onService,
|
||||
service: this.configuration.off?.service,
|
||||
target: this.target,
|
||||
data: {},
|
||||
},
|
||||
|
@@ -2,8 +2,8 @@
|
||||
|
||||
import { EntityRegistryEntry } from '../types/homeassistant/data/entity_registry';
|
||||
import { LightCardConfig } from '../types/lovelace-mushroom/cards/light-card-config';
|
||||
import { isCallServiceActionConfig } from '../types/strategy/strategy-generics';
|
||||
import AbstractCard from './AbstractCard';
|
||||
import { isCallServiceActionConfig } from '../types/strategy/type-guards';
|
||||
|
||||
/**
|
||||
* Light Card Class
|
||||
|
@@ -5,7 +5,7 @@ import { EntityRegistryEntry } from '../types/homeassistant/data/entity_registry
|
||||
import { EntityCardConfig } from '../types/lovelace-mushroom/cards/entity-card-config';
|
||||
import AbstractCard from './AbstractCard';
|
||||
import SwitchCard from './SwitchCard';
|
||||
import { isCallServiceActionConfig } from '../types/strategy/strategy-generics';
|
||||
import { isCallServiceActionConfig } from '../types/strategy/type-guards';
|
||||
|
||||
/**
|
||||
* Scene Card Class
|
||||
|
@@ -23,6 +23,11 @@ export const ConfigurationDefaults: StrategyDefaults = {
|
||||
},
|
||||
},
|
||||
card_options: {},
|
||||
device_options: {
|
||||
_: {
|
||||
group_entities: false,
|
||||
},
|
||||
},
|
||||
chips: {
|
||||
// TODO: Make chips sortable.
|
||||
weather_entity: 'auto',
|
||||
@@ -57,10 +62,14 @@ export const ConfigurationDefaults: StrategyDefaults = {
|
||||
cover: {
|
||||
title: localize('cover.covers'),
|
||||
showControls: true,
|
||||
iconOn: 'mdi:arrow-up',
|
||||
iconOff: 'mdi:arrow-down',
|
||||
onService: 'cover.open_cover',
|
||||
offService: 'cover.close_cover',
|
||||
on: {
|
||||
icon: 'mdi:arrow-up-drop-circle-outline',
|
||||
service: 'cover.open_cover',
|
||||
},
|
||||
off: {
|
||||
icon: 'mdi:arrow-up-down-circle-outline',
|
||||
service: 'cover.close_cover',
|
||||
},
|
||||
hidden: false,
|
||||
},
|
||||
default: {
|
||||
@@ -71,10 +80,14 @@ export const ConfigurationDefaults: StrategyDefaults = {
|
||||
fan: {
|
||||
title: localize('fan.fans'),
|
||||
showControls: true,
|
||||
iconOn: 'mdi:fan',
|
||||
iconOff: 'mdi:fan-off',
|
||||
onService: 'fan.turn_on',
|
||||
offService: 'fan.turn_off',
|
||||
on: {
|
||||
icon: 'mdi:fan',
|
||||
service: 'fan.turn_on',
|
||||
},
|
||||
off: {
|
||||
icon: 'mdi:fan-off',
|
||||
service: 'fan.turn_off',
|
||||
},
|
||||
hidden: false,
|
||||
},
|
||||
input_select: {
|
||||
@@ -85,10 +98,14 @@ export const ConfigurationDefaults: StrategyDefaults = {
|
||||
light: {
|
||||
title: localize('light.lights'),
|
||||
showControls: true,
|
||||
iconOn: 'mdi:lightbulb',
|
||||
iconOff: 'mdi:lightbulb-off',
|
||||
onService: 'light.turn_on',
|
||||
offService: 'light.turn_off',
|
||||
on: {
|
||||
icon: 'mdi:lightbulb',
|
||||
service: 'light.turn_on',
|
||||
},
|
||||
off: {
|
||||
icon: 'mdi:lightbulb-off',
|
||||
service: 'light.turn_off',
|
||||
},
|
||||
hidden: false,
|
||||
},
|
||||
lock: {
|
||||
@@ -109,7 +126,6 @@ export const ConfigurationDefaults: StrategyDefaults = {
|
||||
scene: {
|
||||
title: localize('scene.scenes'),
|
||||
showControls: false,
|
||||
onService: 'scene.turn_on',
|
||||
hidden: false,
|
||||
},
|
||||
select: {
|
||||
@@ -125,15 +141,27 @@ export const ConfigurationDefaults: StrategyDefaults = {
|
||||
switch: {
|
||||
title: localize('switch.switches'),
|
||||
showControls: true,
|
||||
iconOn: 'mdi:power-plug',
|
||||
iconOff: 'mdi:power-plug-off',
|
||||
onService: 'switch.turn_on',
|
||||
offService: 'switch.turn_off',
|
||||
on: {
|
||||
icon: 'mdi:light-switch',
|
||||
service: 'switch.turn_on',
|
||||
},
|
||||
off: {
|
||||
icon: 'mdi:light-switch-off',
|
||||
service: 'switch.turn_off',
|
||||
},
|
||||
hidden: false,
|
||||
},
|
||||
vacuum: {
|
||||
title: localize('vacuum.vacuums'),
|
||||
showControls: true,
|
||||
on: {
|
||||
icon: 'mdi:robot-vacuum',
|
||||
service: 'vacuum.start',
|
||||
},
|
||||
off: {
|
||||
icon: 'mdi:robot-vacuum-off',
|
||||
service: 'vacuum.stop',
|
||||
},
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
|
61
src/generators/AreaCardsGenerator.ts
Normal file
61
src/generators/AreaCardsGenerator.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import DomainCardsGenerator from './domainCardsGenerator';
|
||||
import RegistryFilter from '../utilities/RegistryFilter';
|
||||
import { Registry } from '../Registry';
|
||||
import { AreaRegistryEntry } from '../types/homeassistant/data/area_registry';
|
||||
import { isSupportedDomain, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { filterNonNullValues } from '../utilities/auxiliaries';
|
||||
import { logMessage, lvlError } from '../utilities/debug';
|
||||
|
||||
/**
|
||||
* Class responsible for generating Lovelace card configurations for an area view in the Home Assistant UI.
|
||||
*
|
||||
* The generator creates configurations for each entity (e.g., sensors, switches, etc.) of a device.
|
||||
* It uses a combination of Header cards and entity-specific cards, and it handles miscellaneous entities
|
||||
* that do not fit into any supported domain.
|
||||
*/
|
||||
class AreaCardsGenerator extends DomainCardsGenerator {
|
||||
constructor(area: AreaRegistryEntry) {
|
||||
super({
|
||||
entities: new RegistryFilter(Registry.entities).whereAreaId(area.area_id).toList(),
|
||||
domains: Registry.getExposedNames('domain') as Set<SupportedDomains>,
|
||||
});
|
||||
|
||||
this.parent.type = 'area';
|
||||
this.parent.id = area.area_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of Lovelace card configurations.
|
||||
*
|
||||
* @remarks
|
||||
* Take care about the order of calling the methods.
|
||||
* Each time a card is created, the regarding entity is removed from the list of entities to process.
|
||||
*/
|
||||
public async getCards() {
|
||||
try {
|
||||
const deviceCards = await this.createDeviceCards();
|
||||
|
||||
const [sensorCards, miscellaneousCards] = await Promise.all([
|
||||
this.createSensorCards(),
|
||||
this.createMiscellaneousCards(),
|
||||
]);
|
||||
|
||||
const domainCardsPromises = [...this.domains]
|
||||
.filter((domain) => isSupportedDomain(domain) && domain !== 'sensor')
|
||||
.map((domain) => this.createSupportedDomainCards(domain));
|
||||
const supportedDomainCards = filterNonNullValues(await Promise.all(domainCardsPromises));
|
||||
|
||||
if (sensorCards) {
|
||||
const insertIndex = supportedDomainCards.findIndex((card) => card.strategy.domain > 'sensor');
|
||||
supportedDomainCards.splice(insertIndex, 0, sensorCards);
|
||||
}
|
||||
|
||||
return filterNonNullValues([deviceCards, ...supportedDomainCards, miscellaneousCards]);
|
||||
} catch (e) {
|
||||
logMessage(lvlError, 'Error creating area cards', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AreaCardsGenerator;
|
@@ -1,39 +0,0 @@
|
||||
import { ViewInfo } from '../types/strategy/strategy-generics';
|
||||
import { LovelaceViewConfig } from '../types/homeassistant/data/lovelace/config/view';
|
||||
import RegistryFilter from '../utilities/RegistryFilter';
|
||||
import { Registry } from '../Registry';
|
||||
import { generateDomainCards } from './domainCardGenerator';
|
||||
import { isAreaRegistryEntry } from '../types/strategy/type-guards';
|
||||
import { logMessage, lvlFatal } from '../utilities/debug';
|
||||
|
||||
class AreaView extends HTMLTemplateElement {
|
||||
/**
|
||||
* Generate an Area view.
|
||||
*
|
||||
* The method creates cards for each domain (e.g., sensors, switches, etc.) in the current area, using a combination
|
||||
* of Header cards and entity-specific cards.
|
||||
* It also handles miscellaneous entities that don't fit into any supported domain.
|
||||
*
|
||||
* @param {ViewInfo} info The view's strategy information object.
|
||||
*
|
||||
* @remarks
|
||||
* Called upon opening a subview.
|
||||
*/
|
||||
static async generateView(info: ViewInfo): Promise<LovelaceViewConfig> {
|
||||
const parentEntity = info.view.strategy.parentEntry;
|
||||
|
||||
if (!isAreaRegistryEntry(parentEntity)) {
|
||||
logMessage(lvlFatal, `Entity ${parentEntity?.area_id} is not recognized as an area!`);
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return {
|
||||
cards: await generateDomainCards(
|
||||
Registry.getExposedNames('domain'),
|
||||
new RegistryFilter(Registry.entities).whereAreaId(parentEntity.area_id).toList(),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default AreaView;
|
40
src/generators/AreaViewGenerator.ts
Normal file
40
src/generators/AreaViewGenerator.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ViewInfo } from '../types/strategy/strategy-generics';
|
||||
import { LovelaceViewConfig } from '../types/homeassistant/data/lovelace/config/view';
|
||||
import { isAreaRegistryEntry } from '../types/strategy/type-guards';
|
||||
import { logMessage, lvlFatal } from '../utilities/debug';
|
||||
import AreaCardsGenerator from './AreaCardsGenerator';
|
||||
|
||||
/**
|
||||
* Class responsible for generating an Area view in the Home Assistant UI.
|
||||
*
|
||||
* The generator creates cards for each domain (e.g., sensors, switches, etc.) in the current area.
|
||||
* It uses a combination of Header cards and entity-specific cards, and it handles miscellaneous entities
|
||||
* that do not fit into any supported domain.
|
||||
*
|
||||
* @remarks
|
||||
* This class is instantiated with an area registry entry and is used to generate the view when a subview is opened.
|
||||
*/
|
||||
class AreaViewGenerator extends HTMLTemplateElement {
|
||||
/**
|
||||
* Generates a view for a Home Assistant area.
|
||||
*
|
||||
* The view is generated as a list of cards, one for each domain in the current area.
|
||||
* If the parent entity associated with the view is not recognized as an area, an error is thrown.
|
||||
*
|
||||
* @param {ViewInfo} info - The object with information about the current view.
|
||||
*/
|
||||
static async generateView(info: ViewInfo): Promise<LovelaceViewConfig> {
|
||||
const parentEntity = info.view.strategy.parentEntry;
|
||||
|
||||
if (!isAreaRegistryEntry(parentEntity)) {
|
||||
logMessage(lvlFatal, `Entry ${parentEntity?.id} is not recognized as an area!`);
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return {
|
||||
cards: (await new AreaCardsGenerator(parentEntity).getCards()) || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default AreaViewGenerator;
|
56
src/generators/DeviceCardsGenerator.ts
Normal file
56
src/generators/DeviceCardsGenerator.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import DomainCardsGenerator from './domainCardsGenerator';
|
||||
import RegistryFilter from '../utilities/RegistryFilter';
|
||||
import { Registry } from '../Registry';
|
||||
import { isSupportedDomain, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { filterNonNullValues } from '../utilities/auxiliaries';
|
||||
import { DeviceRegistryEntry } from '../types/homeassistant/data/device_registry';
|
||||
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
|
||||
import { logMessage, lvlError } from '../utilities/debug';
|
||||
|
||||
/**
|
||||
* Class responsible for generating Lovelace card configurations for a device view in the Home Assistant UI.
|
||||
*
|
||||
* The generator creates configurations for each entity (e.g., sensors, switches, etc.) of a device.
|
||||
* It uses a combination of Header cards and entity-specific cards, and it handles miscellaneous entities
|
||||
* that do not fit into any supported domain.
|
||||
*/
|
||||
class DeviceCardsGenerator extends DomainCardsGenerator {
|
||||
constructor(device: DeviceRegistryEntry) {
|
||||
super({
|
||||
entities: new RegistryFilter(Registry.entities).whereDeviceId(device.id).toList(),
|
||||
domains: Registry.getExposedNames('domain') as Set<SupportedDomains>,
|
||||
});
|
||||
|
||||
this.parent.type = 'device';
|
||||
this.parent.id = device.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of Lovelace card configurations.
|
||||
*/
|
||||
public async getCards(): Promise<LovelaceCardConfig[]> {
|
||||
try {
|
||||
const domainCardsPromises = [...this.domains]
|
||||
.filter((domain) => isSupportedDomain(domain) && domain !== 'sensor')
|
||||
.map((domain) => this.createSupportedDomainCards(domain));
|
||||
|
||||
const supportedDomainCards = filterNonNullValues(await Promise.all(domainCardsPromises));
|
||||
const [sensorCards, miscellaneousCards] = await Promise.all([
|
||||
this.createSensorCards(),
|
||||
this.createMiscellaneousCards(),
|
||||
]);
|
||||
|
||||
if (sensorCards) {
|
||||
const insertIndex = supportedDomainCards.findIndex((card) => card.strategy.domain > 'sensor');
|
||||
supportedDomainCards.splice(insertIndex, 0, sensorCards);
|
||||
}
|
||||
|
||||
return filterNonNullValues([...supportedDomainCards, miscellaneousCards]);
|
||||
} catch (e) {
|
||||
logMessage(lvlError, 'Error creating device cards', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeviceCardsGenerator;
|
40
src/generators/DeviceViewGenerator.ts
Normal file
40
src/generators/DeviceViewGenerator.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ViewInfo } from '../types/strategy/strategy-generics';
|
||||
import { LovelaceViewConfig } from '../types/homeassistant/data/lovelace/config/view';
|
||||
import { isDeviceRegistryEntry } from '../types/strategy/type-guards';
|
||||
import { logMessage, lvlFatal } from '../utilities/debug';
|
||||
import DeviceCardsGenerator from './DeviceCardsGenerator';
|
||||
|
||||
/**
|
||||
* Class responsible for generating a Device view in the Home Assistant UI.
|
||||
*
|
||||
* The generator creates cards for each entity (e.g., sensors, switches, etc.) of a device.
|
||||
* It uses a combination of Header cards and entity-specific cards, and it handles miscellaneous entities
|
||||
* that do not fit into any supported domain.
|
||||
*
|
||||
* @remarks
|
||||
* This class is instantiated with a device registry entry and is used to generate the view when a subview is opened.
|
||||
*/
|
||||
class DeviceViewGenerator extends HTMLTemplateElement {
|
||||
/**
|
||||
* Generates a view for a Home Assistant device.
|
||||
*
|
||||
* The view is generated as a list of cards, one for each entity of the current device.
|
||||
* If the parent entity associated with the view is not recognized as a device, an error is thrown.
|
||||
*
|
||||
* @param {ViewInfo} info - The object with information about the current view.
|
||||
*/
|
||||
static async generateView(info: ViewInfo): Promise<LovelaceViewConfig> {
|
||||
const parentEntity = info.view.strategy.parentEntry;
|
||||
|
||||
if (!isDeviceRegistryEntry(parentEntity)) {
|
||||
logMessage(lvlFatal, `Entry ${parentEntity?.area_id} is not recognized as a device!`);
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return {
|
||||
cards: (await new DeviceCardsGenerator(parentEntity).getCards()) || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default DeviceViewGenerator;
|
@@ -1,118 +0,0 @@
|
||||
import { isSupportedDomain } from '../types/strategy/strategy-generics';
|
||||
import { sanitizeClassName } from '../utilities/auxiliaries';
|
||||
import RegistryFilter from '../utilities/RegistryFilter';
|
||||
import { Registry } from '../Registry';
|
||||
import HeaderCard from '../cards/HeaderCard';
|
||||
import SensorCard from '../cards/SensorCard';
|
||||
import { stackHorizontal } from '../utilities/cardStacking';
|
||||
import { logMessage, lvlError } from '../utilities/debug';
|
||||
import { EntityRegistryEntry } from '../types/homeassistant/data/entity_registry';
|
||||
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
|
||||
|
||||
export async function generateDomainCards(
|
||||
domains: string[],
|
||||
domainEntities: EntityRegistryEntry[],
|
||||
): Promise<LovelaceCardConfig[]> {
|
||||
const miscaleaniousCardsPromise = async (): Promise<LovelaceCardConfig[]> => {
|
||||
if (Registry.strategyOptions.domains.default.hidden) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const miscellaneousEntities = new RegistryFilter(domainEntities)
|
||||
.not()
|
||||
.where((entity) => isSupportedDomain(entity.entity_id.split('.', 1)[0]))
|
||||
.toList();
|
||||
|
||||
if (!miscellaneousEntities.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const MiscellaneousCard = (await import('../cards/MiscellaneousCard')).default;
|
||||
const miscellaneousCards = [
|
||||
new HeaderCard({}, Registry.strategyOptions.domains.default).createCard(),
|
||||
...miscellaneousEntities.map(
|
||||
(entity) =>
|
||||
new MiscellaneousCard(
|
||||
entity,
|
||||
Registry.strategyOptions.card_options?.[entity.entity_id],
|
||||
).getCard() as LovelaceCardConfig,
|
||||
),
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'vertical-stack',
|
||||
cards: miscellaneousCards,
|
||||
},
|
||||
];
|
||||
} catch (e) {
|
||||
logMessage(lvlError, 'Error creating card configurations for domain `miscellaneous`', e);
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const otherDomainCardPromises = domains
|
||||
.filter(isSupportedDomain)
|
||||
.map(async (domain): Promise<LovelaceCardConfig[]> => {
|
||||
const moduleName = sanitizeClassName(domain + 'Card');
|
||||
const module = import(`../cards/${moduleName}`);
|
||||
|
||||
const entities = new RegistryFilter(domainEntities)
|
||||
.whereDomain(domain)
|
||||
.where((entity) => !(domain === 'switch' && entity.entity_id.endsWith('_stateful_scene')))
|
||||
.toList();
|
||||
|
||||
if (!entities.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const titleCard = new HeaderCard(
|
||||
{ entity_id: entities.map((entity) => entity.entity_id) },
|
||||
Registry.strategyOptions.domains[domain],
|
||||
).createCard();
|
||||
|
||||
try {
|
||||
const DomainCard = (await module).default;
|
||||
|
||||
if (domain === 'sensor') {
|
||||
const domainCards = entities
|
||||
.filter((entity) => Registry.hassStates[entity.entity_id]?.attributes.unit_of_measurement)
|
||||
.map((entity) => {
|
||||
const options = {
|
||||
...(entity.device_id && Registry.strategyOptions.card_options?.[entity.device_id]),
|
||||
...Registry.strategyOptions.card_options?.[entity.entity_id],
|
||||
type: 'custom:mini-graph-card',
|
||||
entities: [entity.entity_id],
|
||||
};
|
||||
|
||||
return new SensorCard(entity, options).getCard();
|
||||
});
|
||||
|
||||
return domainCards.length ? [{ type: 'vertical-stack', cards: [titleCard, ...domainCards] }] : [];
|
||||
}
|
||||
|
||||
let domainCards = entities.map((entity) => {
|
||||
const cardOptions = {
|
||||
...(entity.device_id && Registry.strategyOptions.card_options?.[entity.device_id]),
|
||||
...Registry.strategyOptions.card_options?.[entity.entity_id],
|
||||
};
|
||||
|
||||
return new DomainCard(entity, cardOptions).getCard() as LovelaceCardConfig;
|
||||
});
|
||||
|
||||
if (domain === 'binary_sensor') {
|
||||
domainCards = stackHorizontal(domainCards);
|
||||
}
|
||||
|
||||
return domainCards.length ? [{ type: 'vertical-stack', cards: [titleCard, ...domainCards] }] : [];
|
||||
} catch (e) {
|
||||
logMessage(lvlError, `Error creating card configurations for domain ${domain}`, e);
|
||||
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
return (await Promise.all([...otherDomainCardPromises, miscaleaniousCardsPromise()])).flat();
|
||||
}
|
244
src/generators/domainCardsGenerator.ts
Normal file
244
src/generators/domainCardsGenerator.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import { logMessage, lvlError, lvlInfo } from '../utilities/debug';
|
||||
import { EntityRegistryEntry } from '../types/homeassistant/data/entity_registry';
|
||||
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
|
||||
import HeaderCard from '../cards/HeaderCard';
|
||||
import { DeviceCard } from '../cards/DeviceCard';
|
||||
import RegistryFilter from '../utilities/RegistryFilter';
|
||||
import { Registry } from '../Registry';
|
||||
import { isSupportedDomain, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { localize } from '../utilities/localize';
|
||||
import { filterNonNullValues, sanitizeClassName } from '../utilities/auxiliaries';
|
||||
import { stackHorizontal } from '../utilities/cardStacking';
|
||||
import { CustomCardConfiguration } from '../types/strategy/strategy-cards';
|
||||
|
||||
/**
|
||||
* Abstract class responsible for generating Lovelace card configurations for various domains in the Home Assistant UI.
|
||||
*
|
||||
* This class serves as a base for generating card configurations erp domain.
|
||||
* Subclasses should implement specific logic for creating cards based on domain requirements.
|
||||
*
|
||||
* @remarks
|
||||
* Take care about the order of calling the methods.
|
||||
* Each time a card is created, the regarding entity is removed from the list of entities to process.
|
||||
*/
|
||||
abstract class DomainCardsGenerator {
|
||||
protected domains: Set<SupportedDomains>;
|
||||
protected parent: {
|
||||
type: 'device' | 'area' | undefined;
|
||||
id: string | undefined;
|
||||
} = {
|
||||
type: undefined,
|
||||
id: undefined,
|
||||
};
|
||||
private entities: EntityRegistryEntry[];
|
||||
|
||||
/**
|
||||
* Initializes the DomainCardsGenerator with the specified entities and domains.
|
||||
*
|
||||
* @param properties - An object containing the entities and domains to be used for card generation.
|
||||
*/
|
||||
protected constructor(properties: { entities: EntityRegistryEntry[]; domains: Set<SupportedDomains> }) {
|
||||
this.entities = properties.entities;
|
||||
this.domains = properties.domains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Lovelace card configurations for devices.
|
||||
*
|
||||
* This method generates cards devices.
|
||||
*
|
||||
* @returns A promise that resolves to a Lovelace card configuration for devices or null if no devices are available.
|
||||
*/
|
||||
public async createDeviceCards(): Promise<LovelaceCardConfig | null> {
|
||||
const devices = new RegistryFilter(Registry.devices)
|
||||
.where((device) => Registry.groupingDeviceIds.has(device.id))
|
||||
.toList();
|
||||
|
||||
if (!devices.length) {
|
||||
logMessage(lvlInfo, `No sensors available for view of ${this.parent.type} ${this.parent.id}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const cards = await Promise.all(
|
||||
devices.map(async (device) => {
|
||||
try {
|
||||
const deviceCard = new DeviceCard(device).getCard();
|
||||
this.entities = this.entities.filter((entity) => entity.device_id !== device.id);
|
||||
|
||||
return deviceCard;
|
||||
} catch (e) {
|
||||
logMessage(lvlError, `Error creating card for device with id ${device.id}`, e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const headerCard = new HeaderCard(
|
||||
{},
|
||||
{
|
||||
title: localize('generic.devices', 'title'),
|
||||
showControls: false,
|
||||
},
|
||||
).createCard();
|
||||
|
||||
return {
|
||||
type: 'vertical-stack',
|
||||
cards: [headerCard, ...cards.filter((card): card is LovelaceCardConfig => card !== null)],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Lovelace card configurations for sensor entities.
|
||||
*
|
||||
* This method filters the entities to only include sensors and generates cards for them.
|
||||
*
|
||||
* @returns A promise that resolves to a Lovelace card configuration for sensors or null if no sensors are available.
|
||||
*/
|
||||
protected async createSensorCards(): Promise<LovelaceCardConfig | null> {
|
||||
const entities = new RegistryFilter(this.entities)
|
||||
.whereDomain('sensor')
|
||||
.where((entity) => Registry.hassStates[entity.entity_id]?.attributes.unit_of_measurement !== undefined)
|
||||
.toList();
|
||||
|
||||
if (!entities.length) {
|
||||
logMessage(lvlInfo, `No sensors available for view of ${this.parent.type} ${this.parent.id}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const cards = await Promise.all(
|
||||
entities.map(async (entity) => {
|
||||
return this.createEntityCard(entity, 'SensorCard', {
|
||||
...Registry.strategyOptions.card_options[entity.entity_id],
|
||||
type: 'custom:mini-graph-card',
|
||||
entities: [entity.entity_id],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const headerCard = new HeaderCard({}, Registry.strategyOptions.domains['sensor']).createCard();
|
||||
|
||||
return {
|
||||
type: 'vertical-stack',
|
||||
cards: [headerCard, ...cards.filter((card): card is LovelaceCardConfig => card !== null)],
|
||||
strategy: { domain: 'sensor' },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Lovelace card configurations for miscellaneous entities.
|
||||
*
|
||||
* This method filters the entities to include those that do not belong to any supported domain and generates cards
|
||||
* for them.
|
||||
*
|
||||
* @returns A promise that resolves to a Lovelace card configuration for miscellaneous entities or null if none are
|
||||
* available.
|
||||
*/
|
||||
protected async createMiscellaneousCards(): Promise<LovelaceCardConfig | null> {
|
||||
const entities = new RegistryFilter(this.entities)
|
||||
.where((entity) => !isSupportedDomain(entity.entity_id.split('.', 1)[0]))
|
||||
.toList();
|
||||
|
||||
if (!entities.length) {
|
||||
logMessage(lvlInfo, `No sensors available for view of ${this.parent.type} ${this.parent.id}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const cards = await Promise.all(
|
||||
entities.map(async (entity) => {
|
||||
return this.createEntityCard(
|
||||
entity,
|
||||
'MiscellaneousCard',
|
||||
Registry.strategyOptions.card_options[entity.entity_id],
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const headerCard = new HeaderCard({}, { title: Registry.strategyOptions.domains['default'].title }).createCard();
|
||||
|
||||
return {
|
||||
type: 'vertical-stack',
|
||||
cards: [headerCard, ...cards.filter((card): card is LovelaceCardConfig => card !== null)],
|
||||
strategy: { domain: 'default' },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Lovelace card configurations for entities within a supported domain.
|
||||
*
|
||||
* This method generates cards for all entities that belong to the specified domain.
|
||||
*
|
||||
* @param domainName - The name of the domain for which to create cards.
|
||||
* @returns A promise that resolves to a Lovelace card configuration for the specified domain or null if no entities
|
||||
* of that domain are available.
|
||||
*/
|
||||
protected async createSupportedDomainCards(domainName: SupportedDomains): Promise<LovelaceCardConfig | null> {
|
||||
const targets: EntityRegistryEntry['entity_id'][] = [];
|
||||
const entities = new RegistryFilter(this.entities)
|
||||
.whereDomain(domainName)
|
||||
.where((entity) => !(domainName === 'switch' && entity.entity_id.endsWith('_stateful_scene')))
|
||||
.toList();
|
||||
|
||||
if (!entities.length) {
|
||||
logMessage(lvlInfo, `No ${domainName} entities available for view of ${this.parent.type} ${this.parent.id}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let cards: (LovelaceCardConfig | null)[] = await Promise.all(
|
||||
entities.map(async (entity) => {
|
||||
targets.push(entity.entity_id);
|
||||
|
||||
return this.createEntityCard(
|
||||
entity,
|
||||
`${domainName}Card`,
|
||||
Registry.strategyOptions.card_options[entity.entity_id],
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
if (domainName === 'binary_sensor') {
|
||||
cards = stackHorizontal(filterNonNullValues(cards));
|
||||
}
|
||||
|
||||
const headerCard = new HeaderCard(
|
||||
{ entity_id: targets },
|
||||
Registry.strategyOptions.domains[domainName],
|
||||
).createCard();
|
||||
|
||||
return {
|
||||
type: 'vertical-stack',
|
||||
cards: [headerCard, ...cards],
|
||||
strategy: { domain: domainName },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Lovelace card configuration for a specified entity.
|
||||
*
|
||||
* @param entity - The entity for which to create a card.
|
||||
* @param entityClassName - The class name of the card to create.
|
||||
* @param customConfiguration - Optional custom configuration for the card.
|
||||
*
|
||||
* @returns A promise that resolves to the Lovelace card configuration or null if creation fails.
|
||||
*/
|
||||
private async createEntityCard(
|
||||
entity: EntityRegistryEntry,
|
||||
entityClassName: string,
|
||||
customConfiguration?: CustomCardConfiguration,
|
||||
): Promise<LovelaceCardConfig | null> {
|
||||
try {
|
||||
const { default: entityClass } = await import(`../cards/${sanitizeClassName(entityClassName)}`);
|
||||
const card = new entityClass(entity, customConfiguration).getCard();
|
||||
|
||||
this.entities = this.entities.filter((unprocessedEntity) => unprocessedEntity.entity_id !== entity.entity_id);
|
||||
|
||||
return card;
|
||||
} catch (e) {
|
||||
logMessage(lvlError, `Error creating a card for entity with id ${entity.entity_id}`, e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DomainCardsGenerator;
|
@@ -1,12 +1,13 @@
|
||||
import { Registry } from './Registry';
|
||||
import { LovelaceConfig } from './types/homeassistant/data/lovelace/config/types';
|
||||
import { LovelaceViewRawConfig } from './types/homeassistant/data/lovelace/config/view';
|
||||
import { LovelaceViewConfig } from './types/homeassistant/data/lovelace/config/view';
|
||||
import { DashboardInfo, isSupportedView } from './types/strategy/strategy-generics';
|
||||
import { sanitizeClassName } from './utilities/auxiliaries';
|
||||
import { filterNonNullValues, sanitizeClassName } from './utilities/auxiliaries';
|
||||
import { logMessage, lvlError, lvlFatal } from './utilities/debug';
|
||||
import AreaViewGenerator from './generators/AreaViewGenerator';
|
||||
import RegistryFilter from './utilities/RegistryFilter';
|
||||
import DeviceView from './generators/DeviceView';
|
||||
import AreaView from './generators/AreaView';
|
||||
import { localize } from './utilities/localize';
|
||||
import DeviceViewGenerator from './generators/DeviceViewGenerator';
|
||||
|
||||
/**
|
||||
* Mushroom Dashboard Strategy.<br>
|
||||
@@ -35,30 +36,53 @@ class MushroomStrategy extends HTMLTemplateElement {
|
||||
logMessage(lvlFatal, 'Error initializing the Registry!', e);
|
||||
}
|
||||
|
||||
const viewPromises = Registry.getExposedNames('view')
|
||||
.filter(isSupportedView)
|
||||
.map(async (viewName) => {
|
||||
try {
|
||||
const moduleName = sanitizeClassName(`${viewName}View`);
|
||||
const View = (await import(`./views/${moduleName}`)).default;
|
||||
const currentView = new View(Registry.strategyOptions.views[viewName]);
|
||||
const viewConfiguration = await currentView.getView();
|
||||
// Main views.
|
||||
const viewPromises = [...Registry.getExposedNames('view')].filter(isSupportedView).map(async (viewName) => {
|
||||
try {
|
||||
const moduleName = sanitizeClassName(`${viewName}View`);
|
||||
const View = (await import(`./views/${moduleName}`)).default;
|
||||
const currentView = new View(Registry.strategyOptions.views[viewName]);
|
||||
const viewConfiguration = await currentView.getView();
|
||||
|
||||
if (viewConfiguration.cards.length) {
|
||||
return viewConfiguration;
|
||||
}
|
||||
} catch (e) {
|
||||
logMessage(lvlError, `Error importing ${viewName} view!`, e);
|
||||
if (viewConfiguration.cards.length) {
|
||||
return viewConfiguration;
|
||||
}
|
||||
} catch (e) {
|
||||
logMessage(lvlError, `Error importing ${viewName} view!`, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
|
||||
const views = (await Promise.all(viewPromises)).filter(Boolean) as LovelaceViewRawConfig[];
|
||||
const views = filterNonNullValues(await Promise.all(viewPromises)) as LovelaceViewConfig[];
|
||||
|
||||
views.push(...resolvedViews);
|
||||
// Device views.
|
||||
const devices = new RegistryFilter(Registry.devices)
|
||||
.where((device) => Registry.groupingDeviceIds.has(device.id))
|
||||
.toList();
|
||||
|
||||
// Subviews for areas
|
||||
const deviceViews = devices.map((device) => {
|
||||
const deviceName = device.name_by_user || device.name || localize('generic.unknown', 'title');
|
||||
|
||||
return {
|
||||
title: `${localize('generic.device', 'title')}: ${deviceName}`,
|
||||
path: device.id,
|
||||
subview: true,
|
||||
icon: 'mdi:devices',
|
||||
strategy: {
|
||||
type: 'custom:mushroom-strategy-device-view',
|
||||
parentEntry: device,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
views.push(...deviceViews);
|
||||
|
||||
if (devices.length && !customElements.get('ll-strategy-mushroom-strategy-device-view')) {
|
||||
customElements.define('ll-strategy-mushroom-strategy-device-view', DeviceViewGenerator);
|
||||
}
|
||||
|
||||
// Area views.
|
||||
views.push(
|
||||
...Registry.areas.map((area) => ({
|
||||
title: area.name,
|
||||
@@ -71,8 +95,8 @@ class MushroomStrategy extends HTMLTemplateElement {
|
||||
})),
|
||||
);
|
||||
|
||||
if (Registry.areas.length) {
|
||||
customElements.define('ll-strategy-mushroom-strategy-area-view', AreaView);
|
||||
if (Registry.areas.length && !customElements.get('ll-strategy-mushroom-strategy-area-view')) {
|
||||
customElements.define('ll-strategy-mushroom-strategy-area-view', AreaViewGenerator);
|
||||
}
|
||||
|
||||
// Extra views
|
||||
@@ -100,4 +124,6 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
main().catch((error) => {
|
||||
throw 'Mushroom Strategy - An error occurred. Check the console (F12) for details.';
|
||||
});
|
||||
|
@@ -19,6 +19,8 @@
|
||||
"all": "Alle",
|
||||
"areas": "Bereiche",
|
||||
"busy": "Beschäftigt",
|
||||
"device": "Gerät",
|
||||
"devices": "Geräte",
|
||||
"good_afternoon": "Guten Nachmittag",
|
||||
"good_evening": "Guten Abend",
|
||||
"good_morning": "Guten Morgen",
|
||||
@@ -29,6 +31,7 @@
|
||||
"off": "Aus",
|
||||
"on": "Ein",
|
||||
"open": "Offen",
|
||||
"tap_here": "Tippen Sie hier",
|
||||
"unavailable": "Nicht verfügbar",
|
||||
"unclosed": "Nicht Geschlossen",
|
||||
"undisclosed": "Sonstiges",
|
||||
|
@@ -19,6 +19,8 @@
|
||||
"all": "All",
|
||||
"areas": "Areas",
|
||||
"busy": "Busy",
|
||||
"device": "Device",
|
||||
"devices": "Devices",
|
||||
"good_afternoon": "Good afternoon",
|
||||
"good_evening": "Good evening",
|
||||
"good_morning": "Good morning",
|
||||
@@ -29,6 +31,7 @@
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"open": "Open",
|
||||
"tap_here": "Tap here",
|
||||
"unavailable": "Unavailable",
|
||||
"unclosed": "Unclosed",
|
||||
"undisclosed": "Other",
|
||||
|
@@ -19,6 +19,8 @@
|
||||
"all": "Todo",
|
||||
"areas": "Áreas",
|
||||
"busy": "Ocupado",
|
||||
"device": "Dispositivo",
|
||||
"devices": "Dispositivos",
|
||||
"good_afternoon": "Buenas tardes",
|
||||
"good_evening": "Buenas noches",
|
||||
"good_morning": "Buenos días",
|
||||
@@ -29,6 +31,7 @@
|
||||
"off": "Apagado",
|
||||
"on": "Encendido",
|
||||
"open": "Abierto",
|
||||
"tap_here": "Toca aquí",
|
||||
"unavailable": "No Disponible",
|
||||
"unclosed": "Sin Cerrar",
|
||||
"undisclosed": "Varios",
|
||||
|
@@ -19,6 +19,8 @@
|
||||
"all": "Alle",
|
||||
"areas": "Ruimtes",
|
||||
"busy": "Bezig",
|
||||
"device": "Apparaat",
|
||||
"devices": "Apparaten",
|
||||
"good_afternoon": "Goedemiddag",
|
||||
"good_evening": "Goedeavond",
|
||||
"good_morning": "Goedemorgen",
|
||||
@@ -29,6 +31,7 @@
|
||||
"off": "Uit",
|
||||
"on": "Aan",
|
||||
"open": "Open",
|
||||
"tap_here": "Tik hier",
|
||||
"unavailable": "Onbeschikbaar",
|
||||
"unclosed": "Niet Gesloten",
|
||||
"undisclosed": "Overige",
|
||||
|
26
src/types/homeassistant/data/config_entries.ts
Normal file
26
src/types/homeassistant/data/config_entries.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export interface ConfigEntry {
|
||||
entry_id: string;
|
||||
domain: string;
|
||||
title: string;
|
||||
source: string;
|
||||
state:
|
||||
| 'loaded'
|
||||
| 'setup_error'
|
||||
| 'migration_error'
|
||||
| 'setup_retry'
|
||||
| 'not_loaded'
|
||||
| 'failed_unload'
|
||||
| 'setup_in_progress';
|
||||
supports_options: boolean;
|
||||
supports_remove_device: boolean;
|
||||
supports_unload: boolean;
|
||||
supports_reconfigure: boolean;
|
||||
supported_subentry_types: Record<string, { supports_reconfigure: boolean }>;
|
||||
num_subentries: number;
|
||||
pref_disable_new_entities: boolean;
|
||||
pref_disable_polling: boolean;
|
||||
disabled_by: 'user' | null;
|
||||
reason: string | null;
|
||||
error_reason_translation_key: string | null;
|
||||
error_reason_translation_placeholders: Record<string, string> | null;
|
||||
}
|
@@ -1,8 +1,21 @@
|
||||
import { LovelaceCardConfig } from '../homeassistant/data/lovelace/config/card';
|
||||
import { TitleCardConfig as MushroomTitleCardConfig } from '../lovelace-mushroom/cards/title-card-config';
|
||||
import { TitleCardConfig } from '../lovelace-mushroom/cards/title-card-config';
|
||||
import { ActionsSharedConfig } from '../lovelace-mushroom/shared/config/actions-config';
|
||||
import { AppearanceSharedConfig } from '../lovelace-mushroom/shared/config/appearance-config';
|
||||
import { EntitySharedConfig } from '../lovelace-mushroom/shared/config/entity-config';
|
||||
import { ChipsCardConfig } from '../lovelace-mushroom/cards/chips-card';
|
||||
import { ClimateCardConfig } from '../lovelace-mushroom/cards/climate-card-config';
|
||||
import { CoverCardConfig } from '../lovelace-mushroom/cards/cover-card-config';
|
||||
import { EntityCardConfig } from '../lovelace-mushroom/cards/entity-card-config';
|
||||
import { FanCardConfig } from '../lovelace-mushroom/cards/fan-card-config';
|
||||
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 { SelectCardConfig } from '../lovelace-mushroom/cards/select-card-config';
|
||||
import { TemplateCardConfig } from '../lovelace-mushroom/cards/template-card-config';
|
||||
import { VacuumCardConfig } from '../lovelace-mushroom/cards/vacuum-card-config';
|
||||
|
||||
/**
|
||||
* Abstract Card Config.
|
||||
@@ -10,22 +23,51 @@ import { EntitySharedConfig } from '../lovelace-mushroom/shared/config/entity-co
|
||||
export type AbstractCardConfig = LovelaceCardConfig & EntitySharedConfig & AppearanceSharedConfig & ActionsSharedConfig;
|
||||
|
||||
/**
|
||||
* Header Card Config.
|
||||
* Header Card Control Configuration.
|
||||
*
|
||||
* @property {boolean} [showControls=true] - False to hide controls.
|
||||
* @property {string} [iconOn] - Icon to show for switching entities from the off state.
|
||||
* @property {string} [iconOff] - Icon to show for switching entities to the off state.
|
||||
* @property {string} [onService=none] - Service to call for switching entities from the off state.
|
||||
* @property {string} [offService=none] - Service to call for switching entities to the off state.
|
||||
* @property {string} [icon] - Icon to display for the control.
|
||||
* @property {string} [icon_color] - Color of the icon.
|
||||
* @property {string} [service] - Service to call when the control is activated.
|
||||
*/
|
||||
export interface StrategyHeaderCardConfig extends MushroomTitleCardConfig {
|
||||
type: 'custom:mushroom-title-card';
|
||||
showControls?: boolean;
|
||||
iconOn?: string;
|
||||
iconOff?: string;
|
||||
onService?: string;
|
||||
offService?: string;
|
||||
interface HeaderCardControlConfig {
|
||||
icon?: string;
|
||||
icon_color?: string;
|
||||
service?: string;
|
||||
}
|
||||
|
||||
/** Custom Configuration of a Strategy Header Card. */
|
||||
export type CustomHeaderCardConfig = Omit<StrategyHeaderCardConfig, 'type'>;
|
||||
/**
|
||||
* Header Card Config.
|
||||
*
|
||||
* @property {string} [type] - Optional property specifying the card type, set to 'custom:mushroom-title-card'.
|
||||
* @property {boolean} [showControls] - Optional flag to show or hide controls on the card (default is true).
|
||||
* @property {HeaderCardControlConfig} [on] - Configuration for the 'on' state of the card.
|
||||
* @property {HeaderCardControlConfig} [off] - Configuration for the 'off' state of the card.
|
||||
*/
|
||||
export interface HeaderCardConfig extends Omit<TitleCardConfig, 'type'> {
|
||||
type?: 'custom:mushroom-title-card';
|
||||
showControls?: boolean;
|
||||
on?: HeaderCardControlConfig;
|
||||
off?: HeaderCardControlConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type representing the custom configuration options for the various cards.
|
||||
*
|
||||
* This type includes all possible card configurations that can be used within the Home Assistant UI for different
|
||||
* entity types.
|
||||
*/
|
||||
export type CustomCardConfiguration =
|
||||
| ChipsCardConfig
|
||||
| ClimateCardConfig
|
||||
| CoverCardConfig
|
||||
| EntityCardConfig
|
||||
| FanCardConfig
|
||||
| LightCardConfig
|
||||
| LockCardConfig
|
||||
| MediaPlayerCardConfig
|
||||
| NumberCardConfig
|
||||
| PersonCardConfig
|
||||
| SelectCardConfig
|
||||
| TemplateCardConfig
|
||||
| TitleCardConfig
|
||||
| VacuumCardConfig;
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { AreaRegistryEntry } from '../homeassistant/data/area_registry';
|
||||
import { DeviceRegistryEntry } from '../homeassistant/data/device_registry';
|
||||
import { EntityRegistryEntry } from '../homeassistant/data/entity_registry';
|
||||
import { ActionConfig, CallServiceActionConfig } from '../homeassistant/data/lovelace/config/action';
|
||||
import { LovelaceCardConfig } from '../homeassistant/data/lovelace/config/card';
|
||||
import { LovelaceConfig } from '../homeassistant/data/lovelace/config/types';
|
||||
import { LovelaceViewConfig, LovelaceViewRawConfig } from '../homeassistant/data/lovelace/config/view';
|
||||
import { HomeAssistant } from '../homeassistant/types';
|
||||
import { LovelaceChipConfig } from '../lovelace-mushroom/utils/lovelace/chip/types';
|
||||
import { StrategyHeaderCardConfig } from './strategy-cards';
|
||||
import { HeaderCardConfig } from './strategy-cards';
|
||||
import { ConfigEntry } from '../homeassistant/data/config_entries';
|
||||
|
||||
/**
|
||||
* List of supported domains.
|
||||
@@ -76,47 +76,10 @@ export type SupportedViews = (typeof SUPPORTED_VIEWS)[number];
|
||||
export type SupportedChips = (typeof SUPPORTED_CHIPS)[number];
|
||||
export type HomeViewSections = (typeof HOME_VIEW_SECTIONS)[number];
|
||||
|
||||
/**
|
||||
* Base interface for sortable items.
|
||||
*
|
||||
* @property {number} order - Numeric value used for sorting items
|
||||
*/
|
||||
interface SortableBase {
|
||||
order: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sortable item that uses a title for identification.
|
||||
* Mutually exclusive with SortableWithName.
|
||||
*
|
||||
* @property {string} title - Display title of the item
|
||||
* @property {never} [name] - Prevents using name property
|
||||
*/
|
||||
type SortableWithTitle = SortableBase & { title: string; name?: never };
|
||||
|
||||
/**
|
||||
* Sortable item that uses a name for identification.
|
||||
* Mutually exclusive with SortableWithTitle.
|
||||
*
|
||||
* @property {string} name - Identifier name of the item
|
||||
* @property {never} [title] - Prevents using title property
|
||||
*/
|
||||
type SortableWithName = SortableBase & { name: string; title?: never };
|
||||
|
||||
/**
|
||||
* Union type for items that can be sorted.
|
||||
* Items must have either a title or a name property, but not both.
|
||||
*
|
||||
* @remarks
|
||||
* This type is used to sort objects by title or by name.
|
||||
* The `order` property is used to sort the items.
|
||||
*/
|
||||
export type Sortable = SortableWithTitle | SortableWithName;
|
||||
|
||||
/**
|
||||
* An entry of a Home Assistant Registry.
|
||||
*/
|
||||
export type RegistryEntry = StrategyArea | DeviceRegistryEntry | EntityRegistryEntry;
|
||||
export type RegistryEntry = StrategyArea | DeviceRegistryEntry | EntityRegistryEntry | ConfigEntry;
|
||||
|
||||
/**
|
||||
* View Configuration of the strategy.
|
||||
@@ -189,48 +152,11 @@ export interface AllDomainsConfig {
|
||||
* @property {boolean} hidden - If True, all entities of the domain are hidden from the dashboard.
|
||||
* @property {number} [order] - Ordering position of the domains in a view.
|
||||
*/
|
||||
export interface SingleDomainConfig extends Partial<StrategyHeaderCardConfig> {
|
||||
export interface SingleDomainConfig extends Partial<HeaderCardConfig> {
|
||||
hidden: boolean;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy Configuration.
|
||||
*
|
||||
* @property {Object.<K in keyof StrategyArea, StrategyArea>} areas - List of areas.
|
||||
* @property {Object.<K in keyof RegistryEntry, CustomCardConfig>} card_options - Card options for entities.
|
||||
* @property {ChipConfiguration} chips - The configuration of chips in the Home view.
|
||||
* @property {boolean} debug - If True, the strategy outputs more verbose debug information in the console.
|
||||
* @property {Object.<string, AllDomainsConfig | SingleDomainConfig>} domains - List of domains.
|
||||
* @property {LovelaceCardConfig[]} extra_cards - List of cards to show below room cards.
|
||||
* @property {StrategyViewConfig[]} extra_views - List of custom-defined views to add to the dashboard.
|
||||
* @property {{ hidden: HomeViewSections[] | [] }} home_view - List of views to add to the dashboard.
|
||||
* @property {Record<SupportedViews, StrategyViewConfig>} views - The configurations of views.
|
||||
* @property {LovelaceCardConfig[]} quick_access_cards - List of custom-defined cards to show between the welcome card
|
||||
* and rooms cards.
|
||||
*/
|
||||
export interface StrategyConfig {
|
||||
areas: { [S: string]: StrategyArea };
|
||||
card_options: { [S: string]: CustomCardConfig };
|
||||
chips: ChipConfiguration;
|
||||
debug: boolean;
|
||||
domains: { [K in SupportedDomains]: K extends '_' ? AllDomainsConfig : SingleDomainConfig };
|
||||
extra_cards: LovelaceCardConfig[];
|
||||
extra_views: StrategyViewConfig[];
|
||||
home_view: {
|
||||
hidden: HomeViewSections[] | [];
|
||||
};
|
||||
views: Record<SupportedViews, StrategyViewConfig>;
|
||||
quick_access_cards: LovelaceCardConfig[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the default configuration for a strategy.
|
||||
*/
|
||||
export interface StrategyDefaults extends StrategyConfig {
|
||||
areas: { undisclosed: StrategyArea } & { [S: string]: StrategyArea };
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy Area.
|
||||
*
|
||||
@@ -279,29 +205,81 @@ export interface CustomCardConfig extends LovelaceCardConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given object is of a sortable type.
|
||||
* Strategy Configuration.
|
||||
*
|
||||
* Sortable types are objects that have an `order`, `title` or `name` property.
|
||||
*
|
||||
* @param {object} object - The object to check.
|
||||
* @returns {boolean} - True if the object is an instance of Sortable, false otherwise.
|
||||
* @property {Object.<K in keyof StrategyArea, StrategyArea>} areas - List of areas.
|
||||
* @property {Object.<K in keyof RegistryEntry, CustomCardConfig>} card_options - Card options for entities.
|
||||
* @property {ChipConfiguration} chips - The configuration of chips in the Home view.
|
||||
* @property {boolean} debug - If True, the strategy outputs more verbose debug information in the console.
|
||||
* @property {Object.<string, AllDomainsConfig | SingleDomainConfig>} domains - List of domains.
|
||||
* @property {LovelaceCardConfig[]} extra_cards - List of cards to show below room cards.
|
||||
* @property {StrategyViewConfig[]} extra_views - List of custom-defined views to add to the dashboard.
|
||||
* @property {{ hidden: HomeViewSections[] | [] }} home_view - List of views to add to the dashboard.
|
||||
* @property {Record<SupportedViews, StrategyViewConfig>} views - The configurations of views.
|
||||
* @property {LovelaceCardConfig[]} quick_access_cards - List of custom-defined cards to show between the welcome card
|
||||
* and rooms cards.
|
||||
*/
|
||||
export function isSortable(object: object): object is Sortable {
|
||||
return object && ('order' in object || 'title' in object || 'name' in object);
|
||||
export interface StrategyConfig {
|
||||
areas: { [S in AreaRegistryEntry['area_id']]: StrategyArea };
|
||||
device_options: { [S in DeviceRegistryEntry['id']]: { hidden?: boolean; group_entities?: boolean } };
|
||||
// TODO: Move device entries from card_options to device_options.
|
||||
card_options: { [S in EntityRegistryEntry['entity_id']]: CustomCardConfig };
|
||||
chips: ChipConfiguration;
|
||||
debug: boolean;
|
||||
domains: { [K in SupportedDomains]: K extends '_' ? AllDomainsConfig : SingleDomainConfig };
|
||||
extra_cards: LovelaceCardConfig[];
|
||||
extra_views: StrategyViewConfig[];
|
||||
home_view: {
|
||||
hidden: HomeViewSections[] | [];
|
||||
};
|
||||
views: Record<SupportedViews, StrategyViewConfig>;
|
||||
quick_access_cards: LovelaceCardConfig[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object matches the CallServiceActionConfig interface.
|
||||
*
|
||||
* @param {ActionConfig} [object] - The object to check.
|
||||
* @returns {boolean} - True if the object represents a valid service action configuration.
|
||||
* Represents the default configuration for a strategy.
|
||||
*/
|
||||
export function isCallServiceActionConfig(object?: ActionConfig): object is CallServiceActionConfig {
|
||||
return (
|
||||
!!object && (object.action === 'perform-action' || object.action === 'call-service') && 'perform_action' in object
|
||||
);
|
||||
export interface StrategyDefaults extends StrategyConfig {
|
||||
areas: { undisclosed: StrategyArea } & { [S: string]: StrategyArea };
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for sortable items.
|
||||
*
|
||||
* @property {number} order - Numeric value used for sorting items
|
||||
*/
|
||||
interface SortableBase {
|
||||
order: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sortable item that uses a title for identification.
|
||||
* Mutually exclusive with SortableWithName.
|
||||
*
|
||||
* @property {string} title - Display title of the item
|
||||
* @property {never} [name] - Prevents using name property
|
||||
*/
|
||||
type SortableWithTitle = SortableBase & { title: string; name?: never };
|
||||
|
||||
/**
|
||||
* Sortable item that uses a name for identification.
|
||||
* Mutually exclusive with SortableWithTitle.
|
||||
*
|
||||
* @property {string} name - Identifier name of the item
|
||||
* @property {never} [title] - Prevents using title property
|
||||
*/
|
||||
type SortableWithName = SortableBase & { name: string; title?: never };
|
||||
|
||||
/**
|
||||
* Union type for items that can be sorted.
|
||||
* Items must have either a title or a name property, but not both.
|
||||
*
|
||||
* @remarks
|
||||
* This type is used to sort objects by title or by name.
|
||||
* The `order` property is used to sort the items.
|
||||
*/
|
||||
export type Sortable = SortableWithTitle | SortableWithName;
|
||||
|
||||
/**
|
||||
* Type guard to check if a given identifier exists in a list of supported identifiers.
|
||||
*
|
||||
|
@@ -1,20 +1,20 @@
|
||||
import { LovelaceViewConfig } from '../homeassistant/data/lovelace/config/view';
|
||||
import { CustomHeaderCardConfig } from './strategy-cards';
|
||||
import { SupportedDomains } from './strategy-generics';
|
||||
import { HeaderCardConfig } from './strategy-cards';
|
||||
|
||||
/**
|
||||
* Options for the extended View class.
|
||||
*
|
||||
* @property {StrategyHeaderCardConfig} [headerCardConfiguration] - Options for the Header card.
|
||||
* @property {HeaderCardConfig} [headerCardConfiguration] - Options for the Header card.
|
||||
*/
|
||||
export interface ViewConfig extends LovelaceViewConfig {
|
||||
headerCardConfiguration?: CustomHeaderCardConfig;
|
||||
headerCardConfiguration?: HeaderCardConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for constructors of AbstractView subclasses that are expected to define a static domain property.
|
||||
*
|
||||
* @property {SupportedDomains | "home"} domain - The domain which the view is representing.
|
||||
* @property {SupportedDomains | 'home'} domain - The domain which the view is representing.
|
||||
*/
|
||||
export interface ViewConstructor {
|
||||
domain: SupportedDomains | 'home';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { DeviceRegistryEntry } from '../homeassistant/data/device_registry';
|
||||
import { RegistryEntry } from './strategy-generics';
|
||||
import { RegistryEntry, Sortable } from './strategy-generics';
|
||||
import { AreaRegistryEntry } from '../homeassistant/data/area_registry';
|
||||
import { ActionConfig, CallServiceActionConfig } from '../homeassistant/data/lovelace/config/action';
|
||||
|
||||
/**
|
||||
* Type guard to check if the given object is a DeviceRegistryEntry.
|
||||
@@ -21,3 +22,27 @@ export function isDeviceRegistryEntry(object?: RegistryEntry): object is DeviceR
|
||||
export function isAreaRegistryEntry(object?: RegistryEntry): object is AreaRegistryEntry {
|
||||
return !!object && 'area_id' in object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given object is of a sortable type.
|
||||
*
|
||||
* Sortable types are objects that have an `order`, `title` or `name` property.
|
||||
*
|
||||
* @param {object} object - The object to check.
|
||||
* @returns {boolean} - True if the object is an instance of Sortable, false otherwise.
|
||||
*/
|
||||
export function isSortable(object: object): object is Sortable {
|
||||
return object && ('order' in object || 'title' in object || 'name' in object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object matches the CallServiceActionConfig interface.
|
||||
*
|
||||
* @param {ActionConfig} [object] - The object to check.
|
||||
* @returns {boolean} - True if the object represents a valid service action configuration.
|
||||
*/
|
||||
export function isCallServiceActionConfig(object?: ActionConfig): object is CallServiceActionConfig {
|
||||
return (
|
||||
!!object && (object.action === 'perform-action' || object.action === 'call-service') && 'perform_action' in object
|
||||
);
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ import { logMessage, lvlWarn } from './debug';
|
||||
class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
private readonly entries: T[];
|
||||
private filters: (((entry: T) => boolean) | ((entry: T, index: number) => boolean))[] = [];
|
||||
private readonly entryIdentifier: ('entity_id' | 'floor_id' | 'id') & K;
|
||||
private readonly entryIdentifier: ('entity_id' | 'floor_id' | 'entry_id' | 'id') & K;
|
||||
private invertNext: boolean = false;
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,13 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
constructor(entries: T[]) {
|
||||
this.entries = entries;
|
||||
this.entryIdentifier = (
|
||||
entries.length === 0 || 'entity_id' in entries[0] ? 'entity_id' : 'floor_id' in entries[0] ? 'floor_id' : 'id'
|
||||
entries.length === 0 || 'entity_id' in entries[0]
|
||||
? 'entity_id'
|
||||
: 'floor_id' in entries[0]
|
||||
? 'floor_id'
|
||||
: 'entry_id' in entries[0]
|
||||
? 'entry_id'
|
||||
: 'id'
|
||||
) as ('entity_id' | 'floor_id' | 'id') & K;
|
||||
}
|
||||
|
||||
@@ -81,9 +87,14 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
*/
|
||||
whereAreaId(areaId?: string, expandToDevice: boolean = true): this {
|
||||
const predicate = (entry: T) => {
|
||||
let deviceAreaId: string | null | undefined = undefined;
|
||||
if ('entry_id' in entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entryObject = entry as EntityRegistryEntry;
|
||||
|
||||
let deviceAreaId: string | null | undefined = undefined;
|
||||
|
||||
// Retrieve the device area ID only if expandToDevice is true
|
||||
if (expandToDevice && entryObject.device_id) {
|
||||
deviceAreaId = Registry.devices.find((device) => device.id === entryObject.device_id)?.area_id;
|
||||
@@ -104,6 +115,7 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
};
|
||||
|
||||
this.filters.push(this.checkInversion(predicate));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -261,7 +273,9 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
}
|
||||
|
||||
const id = entry[this.entryIdentifier] as keyof StrategyConfig['card_options'];
|
||||
const isHiddenByConfig = Registry.strategyOptions?.card_options?.[id]?.hidden === true;
|
||||
const isHiddenByConfig =
|
||||
Registry.strategyOptions.device_options['_'].hidden ||
|
||||
Registry.strategyOptions.card_options[id]?.hidden === true;
|
||||
|
||||
return !isHiddenByProperty && !isHiddenByConfig;
|
||||
};
|
||||
@@ -302,9 +316,7 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
const predicate = (entry: T) => {
|
||||
const category = 'entity_category' in entry ? entry.entity_category : undefined;
|
||||
const hideOption =
|
||||
typeof category === 'string'
|
||||
? Registry.strategyOptions?.domains?.['_']?.[`hide_${category}_entities`]
|
||||
: undefined;
|
||||
typeof category === 'string' ? Registry.strategyOptions.domains['_']?.[`hide_${category}_entities`] : undefined;
|
||||
|
||||
if (hideOption === true) {
|
||||
return false;
|
||||
|
@@ -36,3 +36,39 @@ export function deepClone<T>(obj: T): T {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function getObjectKeysByPropertyValue(
|
||||
object: Record<string, unknown>,
|
||||
property: string,
|
||||
value: unknown,
|
||||
): string[] {
|
||||
const keys: string[] = [];
|
||||
|
||||
for (const key of Object.keys(object)) {
|
||||
if (object[key] && (object[key] as Record<string, unknown>)[property] === value) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out null values from an array.
|
||||
*
|
||||
* @template T The type of the array elements.
|
||||
* @param {Array<T | null>} arr The array to filter.
|
||||
* @returns {Array<T>} An array containing the non-null elements.
|
||||
*/
|
||||
export function filterNonNullValues<T>(arr: (T | null)[]): T[] {
|
||||
return arr.filter((item): item is T => item !== null);
|
||||
}
|
||||
|
@@ -62,12 +62,35 @@ export default function setupCustomLocalize(hass?: HomeAssistant): void {
|
||||
|
||||
/**
|
||||
* Translate a key using the globally configured localize function.
|
||||
*
|
||||
* @param key - The key to be translated.
|
||||
* @param caseType - Optional parameter to specify the case transformation:
|
||||
* - 'upper': Converts the localized key to uppercase.
|
||||
* - 'lower': Converts the localized key to lowercase.
|
||||
* - 'title': Converts the localized key to a title case (capitalizing the first letter of each word).
|
||||
*
|
||||
* @returns The translated key in the specified case format or the original key if not initialized.
|
||||
*/
|
||||
export function localize(key: string): string {
|
||||
export function localize(key: string, caseType?: 'upper' | 'lower' | 'title'): string {
|
||||
if (!_localize) {
|
||||
logMessage(lvlWarn, 'localize is not initialized! Call setupCustomLocalize first.');
|
||||
|
||||
return key;
|
||||
}
|
||||
return _localize(key);
|
||||
|
||||
const localizedKey = _localize(key);
|
||||
|
||||
// Transform the case based on the caseType parameter
|
||||
switch (caseType) {
|
||||
case 'upper':
|
||||
return localizedKey.toUpperCase();
|
||||
case 'lower':
|
||||
return localizedKey.toLowerCase();
|
||||
case 'title':
|
||||
return localizedKey
|
||||
.split(' ')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
return localizedKey; // Return the original localized key if no caseType is specified
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { SingleDomainConfig, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
|
||||
/**
|
||||
* Camera View Class.
|
||||
@@ -18,19 +18,23 @@ class CameraView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[CameraView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('camera.cameras'),
|
||||
title: domainConfig.title,
|
||||
path: 'cameras',
|
||||
icon: 'mdi:cctv',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
showControls: false,
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('camera.all_cameras'),
|
||||
subtitle:
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { SingleDomainConfig, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
|
||||
/**
|
||||
* Climate View Class.
|
||||
@@ -18,19 +18,23 @@ class ClimateView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[ClimateView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('climate.climates'),
|
||||
title: domainConfig.title,
|
||||
path: 'climates',
|
||||
icon: 'mdi:thermostat',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
showControls: false,
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('climate.all_climates'),
|
||||
subtitle:
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { SingleDomainConfig, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
|
||||
/**
|
||||
* Cover View Class.
|
||||
@@ -18,22 +18,23 @@ class CoverView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[CoverView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('cover.covers'),
|
||||
title: domainConfig.title,
|
||||
path: 'covers',
|
||||
icon: 'mdi:window-open',
|
||||
icon: 'mdi:arrow-up-down-bold-outline',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
iconOn: 'mdi:arrow-up',
|
||||
iconOff: 'mdi:arrow-down',
|
||||
onService: 'cover.open_cover',
|
||||
offService: 'cover.close_cover',
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('cover.all_covers'),
|
||||
subtitle:
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { SingleDomainConfig, SupportedDomains } from '../types/strategy/strategy-generics';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
|
||||
/**
|
||||
* Fan View Class.
|
||||
@@ -18,22 +18,23 @@ class FanView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[FanView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('fan.fans'),
|
||||
title: domainConfig.title,
|
||||
path: 'fans',
|
||||
icon: 'mdi:fan',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
iconOn: 'mdi:fan',
|
||||
iconOff: 'mdi:fan-off',
|
||||
onService: 'fan.turn_on',
|
||||
offService: 'fan.turn_off',
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('fan.all_fans'),
|
||||
subtitle:
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SingleDomainConfig } from '../types/strategy/strategy-generics';
|
||||
|
||||
/**
|
||||
* Light View Class.
|
||||
@@ -20,22 +21,23 @@ class LightView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[LightView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('light.lights'),
|
||||
title: domainConfig.title,
|
||||
path: 'lights',
|
||||
icon: 'mdi:lightbulb-group',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
iconOn: 'mdi:lightbulb',
|
||||
iconOff: 'mdi:lightbulb-off',
|
||||
onService: 'light.turn_on',
|
||||
offService: 'light.turn_off',
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('light.all_lights'),
|
||||
subtitle:
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SingleDomainConfig } from '../types/strategy/strategy-generics';
|
||||
|
||||
/**
|
||||
* Lock View Class.
|
||||
@@ -17,22 +18,23 @@ class LockView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[LockView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('locks.locks'),
|
||||
title: domainConfig.title,
|
||||
path: 'locks',
|
||||
icon: 'mdi:lock-open',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
iconOn: 'mdi:lock-open',
|
||||
iconOff: 'mdi:lock',
|
||||
onService: 'lock.lock',
|
||||
offService: 'lock.unlock',
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('lock.all_locks'),
|
||||
subtitle:
|
||||
|
@@ -1,9 +1,10 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { Registry } from '../Registry';
|
||||
import { SingleDomainConfig } from '../types/strategy/strategy-generics';
|
||||
|
||||
/**
|
||||
* Scene View Class.
|
||||
@@ -16,19 +17,23 @@ class SceneView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[SceneView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('scene.scenes'),
|
||||
title: domainConfig.title,
|
||||
path: 'scenes',
|
||||
icon: 'mdi:palette',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
showControls: false,
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SingleDomainConfig } from '../types/strategy/strategy-generics';
|
||||
|
||||
/**
|
||||
* Switch View Class.
|
||||
@@ -17,22 +18,23 @@ class SwitchView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[SwitchView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('switch.switches'),
|
||||
title: domainConfig.title,
|
||||
path: 'switches',
|
||||
icon: 'mdi:dip-switch',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
iconOn: 'mdi:power-plug',
|
||||
iconOff: 'mdi:power-plug-off',
|
||||
onService: 'switch.turn_on',
|
||||
offService: 'switch.turn_off',
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('switch.all_switches'),
|
||||
subtitle:
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||
|
||||
import { Registry } from '../Registry';
|
||||
import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { ViewConfig } from '../types/strategy/strategy-views';
|
||||
import { localize } from '../utilities/localize';
|
||||
import AbstractView from './AbstractView';
|
||||
import { HeaderCardConfig } from '../types/strategy/strategy-cards';
|
||||
import { SingleDomainConfig } from '../types/strategy/strategy-generics';
|
||||
|
||||
/**
|
||||
* Vacuum View Class.
|
||||
@@ -17,22 +18,23 @@ class VacuumView extends AbstractView {
|
||||
|
||||
/** Returns the default configuration object for the view. */
|
||||
static getDefaultConfig(): ViewConfig {
|
||||
const domainConfig = Registry.strategyOptions.domains[VacuumView.domain] as SingleDomainConfig;
|
||||
|
||||
return {
|
||||
title: localize('vacuum.vacuums'),
|
||||
title: domainConfig.title,
|
||||
path: 'vacuums',
|
||||
icon: 'mdi:robot-vacuum',
|
||||
subview: false,
|
||||
headerCardConfiguration: {
|
||||
iconOn: 'mdi:robot-vacuum',
|
||||
iconOff: 'mdi:robot-vacuum-off',
|
||||
onService: 'vacuum.start',
|
||||
offService: 'vacuum.stop',
|
||||
showControls: domainConfig.showControls,
|
||||
on: domainConfig.on,
|
||||
off: domainConfig.off,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the default configuration of the view's Header card. */
|
||||
static getViewHeaderCardConfig(): CustomHeaderCardConfig {
|
||||
static getViewHeaderCardConfig(): HeaderCardConfig {
|
||||
return {
|
||||
title: localize('vacuum.all_vacuums'),
|
||||
subtitle:
|
||||
|
Reference in New Issue
Block a user